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 /// Sections define protocol <
> () getter section-container (section ::
) => (container :: false-or()); getter section-home-buffer (section ::
, #key editor) => (buffer :: false-or()); getter section-start-line (section ::
) => (line :: false-or()); setter section-start-line-setter (line :: false-or(), section ::
) => (line :: false-or()); getter section-end-line (section ::
) => (line :: false-or()); setter section-end-line-setter (line :: false-or(), section ::
) => (line :: false-or()); getter section-nodes (section ::
) => (nodes :: ); setter section-nodes-setter (nodes :: , section ::
) => (nodes :: ); getter section-modification-tick (section ::
) => (tick :: ); setter section-modification-tick-setter (tick :: , section ::
) => (tick :: ); getter section-sectionization-tick (section ::
) => (tick :: ); setter section-sectionization-tick-setter (tick :: , section ::
) => (tick :: ); getter section-compilation-tick (section ::
) => (tick :: ); setter section-compilation-tick-setter (tick :: , section ::
) => (tick :: ); getter section-lock (section ::
) => (lock :: false-or()); function add-line! (section ::
, line :: , #key after) => (); function remove-line! (section ::
, line :: ) => (); function note-section-changed (section ::
) => (); function note-section-compiled (section ::
) => (); function resectionize-section (section ::
) => (resectionized? :: ); getter section-defining-line (section ::
) => (line :: false-or()); function section-definition-signature (section ::
) => (signature); function section-definition-name (section ::
) => (name :: false-or()); function section-definition-type (section ::
) => (type :: false-or()); end protocol <
>; define constant = one-of(#"always", #"requested", #"never"); // A section is the basic unit of user data, which is represented as a sequence // of lines. A section can be contained by more than one node, but a section node // can contain only one section. This containment of sections by section nodes // means that the same section can simultaneously appear in multiple buffers. define open abstract primary class (
) sealed slot section-container :: false-or() = #f, init-keyword: container:; // The start and end of a linked list of lines // When both are #f, the section is empty sealed slot section-start-line :: false-or(), required-init-keyword: start-line:; sealed slot section-end-line :: false-or(), required-init-keyword: end-line:; // All of the nodes in which this section is contained. // We represent this as a list because the usual length is 1 sealed slot section-nodes :: = #(), init-keyword: nodes:; // The section is considered changed when the modification tick is greater // than the sectionization tick. They are both initialized to the same value // when the container is sectionized. sealed slot section-modification-tick :: = *tick*; sealed slot section-sectionization-tick :: = *tick*; sealed slot section-compilation-tick :: = *tick*; // It's reasonable for every section in a container to share the // container's single recursive lock... sealed constant slot section-lock :: false-or() = #f, init-keyword: lock:; // Cached so that redisplay doesn't get slowed down sealed slot %n-lines :: false-or() = #f; end class ; define sealed method section-home-buffer (section :: , #key editor = frame-editor(*editor-frame*)) => (buffer :: false-or()) let container = section-container(section); if (container) // The section came from a source container, so return the buffer // into which the container was originally read. container-home-buffer(container, editor: editor) else // The section isn't in a source container. We could try to return // the section's original node, but that is often not the right thing, // so just return #f. Higher-level code can always insert the section // into a dummy source container if this is not adequate. #f end end method section-home-buffer; define sealed inline method make (class ==
, #rest initargs, #key, #all-keys) => (section :: ) apply(make, , initargs) end method make; define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define function make-empty-section (#key section-class =
) => (section ::
) let section = make(section-class, container: #f, start-line: #f, end-line: #f); let line = make(, contents: "", section: section); section-start-line(section) := line; section-end-line(section) := line; section end function make-empty-section; // A class upon which language-specific definition sections are built define open abstract class () end class ; define method section-defining-line (section :: ) => (line :: false-or()) #f end method section-defining-line; define method section-definition-signature (section :: ) => (signature); #f end method section-definition-signature; define method section-definition-name (section :: ) => (name :: false-or()); #f end method section-definition-name; define method section-definition-type (section :: ) => (type :: false-or()); #f end method section-definition-type; define method do-lines (function :: , section :: , #key from-end? = #f, skip-test = line-for-display-only?) => () let (start-line, end-line, step :: ) = if (from-end?) values(section-end-line(section), section-start-line(section), line-previous) else values(section-start-line(section), section-end-line(section), line-next) end; when (start-line) // the section might be empty block (break) for (line = start-line then step(line)) when (line & (~skip-test | ~skip-test(line))) let si = 0; let ei = line-length(line); function(line, si, ei, line == end-line) end; when (~line | line == end-line) break() end end end end end method do-lines; define method count-lines (section :: , #key skip-test = line-for-display-only?, cache-result? = #f) => (nlines :: ) (cache-result? & section.%n-lines) | begin let n :: = 0; do-lines(method (line :: , si, ei, last?) ignore(line, si, ei, last?); inc!(n) end method, section, skip-test: skip-test); when (cache-result?) section.%n-lines := n end; n end end method count-lines; // Note that this _does_ include a '\n' character at the end of each line define method as (class :: subclass(), section :: ) => (string :: ) let bp1 = line-start(section-start-line(section)); let bp2 = line-end(section-end-line(section)); let interval = make-interval(bp1, bp2, in-order?: #t); as(, interval) end method as; define method note-section-changed (section :: ) => () section-modification-tick(section) := tick(); // Notify the source container that some of its data changed let container = section-container(section); when (container) note-container-changed(container) end; // Then tell all the nodes using this section that they have changed, too for (node :: in section-nodes(section)) note-node-changed(node) end end method note-section-changed; define method note-section-compiled (section :: ) => () section-compilation-tick(section) := section-modification-tick(section) end method note-section-compiled; define sealed method resectionize-section (section :: ) => (resectionized? :: ) when (section-sectionization-tick(section) < section-modification-tick(section)) let container = section-container(section); // If the container has hard sections, those sections are definitive, // so don't go resectionizing this section when (container & ~container-has-hard-sections?(container)) let mode = find-mode-from-pathname(container-pathname(container)); let resectionized? = do-resectionize-section(mode, section); section-sectionization-tick(section) := section-modification-tick(section); // If we resectionized this section, arrange to redisplay it when (resectionized?) do-associated-windows (window :: = *editor-frame*) let buffer = window-buffer(window); when (buffer & buffer-contains-section?(buffer, section)) queue-redisplay(window, $display-all) end end end; resectionized? end end end method resectionize-section; /// Adding and removing lines to sections define sealed method add-line! (section :: , line :: , #key after :: type-union(, one-of(#f, #"start", #"end")) = #"end") => () assert(~line-section(line), "The line %= is already in the section %=", line, line-section(line)); let (next, prev) = select (after) #f, #"start" => values(section-start-line(section), #f); #"end" => values(#f, section-end-line(section)); otherwise => assert(line-section(after) == section, "The 'after' line %= is not in the section %=", after, section); values(line-next(after), after); end; line-section(line) := section; line-next(line) := next; line-previous(line) := prev; if (next) line-previous(next) := line else section-end-line(section) := line; // Update the end BPs of any nodes using this section for (node :: in section-nodes(section)) move-bp!(interval-end-bp(node), line, line-length(line)) end end; if (prev) line-next(prev) := line else section-start-line(section) := line; // Update the start BPs of any nodes using this section for (node :: in section-nodes(section)) move-bp!(interval-start-bp(node), line, 0) end end; update-section-line-count(section, 1) end method add-line!; define sealed method remove-line! (section :: , line :: ) => () assert(line-section(line) == section, "The line %= is not in the section %=", line, section); let (next, prev) = values(line-next(line), line-previous(line)); if (next) line-previous(next) := prev else section-end-line(section) := prev; for (node :: in section-nodes(section)) move-bp!(interval-end-bp(node), prev, line-length(prev)) end end; if (prev) line-next(prev) := next else section-start-line(section) := next; for (node :: in section-nodes(section)) move-bp!(interval-start-bp(node), next, 0) end end; //---*** Set 'line-section' to #f when we're sure nobody is relying on it line-next(line) := #f; line-previous(line) := #f; // If any windows are starting their redisplay at the line // we just removed, we better fix them up now //--- This is dubious modularity, but at least it's very centralized do-associated-windows (window :: = *editor-frame*) when (window-initial-line(window) == line) window-initial-line(window) := next end end; update-section-line-count(section, -1) end method remove-line!; define sealed method update-section-line-count (section :: , delta :: ) => () // Update the line count for this section when (section.%n-lines) section.%n-lines := section.%n-lines + delta end; // Update every window which has a buffer containing this section //--- This looping is probably not too bad, since most sections are in only one node for (node :: in section-nodes(section)) let buffer = node-buffer(node); when (buffer) do-associated-windows (window :: = *editor-frame*) let buffer = window-buffer(window); when (buffer & window.%total-lines & buffer-contains-section?(buffer, section)) window-total-lines(window) := window.%total-lines + delta end end end end end method update-section-line-count;