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 /// Source containers define protocol <> () getter container-pathname (container :: ) => (pathname :: ); setter container-pathname-setter (pathname :: , container :: , #key editor) => (pathname :: ); getter container-sections (container :: ) => (sections :: ); setter container-sections-setter (sections :: , container :: ) => (sections :: ); getter container-has-hard-sections? (container :: ) => (hard-sections? :: ); getter container-modified? (container :: ) => (modified? :: ); getter container-read-only? (container :: ) => (read-only? :: ); function container-home-buffer (container :: , #key editor) => (buffer :: false-or()); getter container-buffers (container :: ) => (buffers :: ); setter container-buffers-setter (buffers :: , container :: , #key editor) => (buffers :: ); getter container-undo-history (container :: ) => (undo-history :: false-or()); getter container-lock (container :: ) => (lock :: false-or()); getter container-properties (container :: ) => (properties :: ); setter container-properties-setter (properties :: , container :: ) => (properties :: ); function note-container-changed (container :: ) => (); function read-container-contents (container :: , buffer :: ) => (); function sectionize-container (container :: ) => (sectionized? :: ); function add-section! (container :: , section ::
, #key after) => (); function remove-section! (container :: , section ::
) => (); end protocol <>; // A source container is an object that models the primary place from // which a set of sections came from. A simple flat file, for example, // is a kind of source container from which sections are derived. define open abstract primary class () sealed slot container-pathname :: , setter: %pathname-setter, required-init-keyword: pathname:; sealed slot container-sections :: = make(); sealed slot container-modified? :: = #f; // Backpointer to all buffers that use any sections in this container // We represent this as a list because the usual length is 1 sealed slot container-buffers :: = #(), setter: %buffers-setter; sealed slot container-undo-history :: = make(), init-keyword: undo-history:; sealed slot container-modification-date :: false-or() = #f, init-keyword: modification-date:; sealed slot container-properties :: = #(), init-keyword: properties:; sealed constant slot container-lock :: false-or() = #f, init-keyword: lock:; end class ; define method find-source-container (editor :: , class :: subclass(), pathname, #rest initargs) => (container :: ) let namestring = as(, pathname); let containers = editor-source-containers(editor); let container = gethash(containers, namestring) | begin let new-container = apply(make, class, pathname: pathname, initargs); gethash(containers, namestring) := new-container; new-container end; container end method find-source-container; define method container-home-buffer (container :: , #key editor = frame-editor(*editor-frame*)) => (buffer :: false-or()) let pathname = container-pathname(container); pathname & find-buffer-from-pathname(editor, pathname) end method container-home-buffer; define method container-pathname-setter (pathname :: , container :: , #key editor) => (pathname :: ) let old-namestring = as(, container-pathname(container)); let new-namestring = as(, pathname); container.%pathname := pathname; // Make the editor's set of source containers be keyed by the new name let editor = editor | (*editor-frame* & frame-editor(*editor-frame*)); when (editor) remhash(editor-source-containers(editor), old-namestring); gethash(editor-source-containers(editor), new-namestring) := container end; pathname end method container-pathname-setter; define method container-buffers-setter (buffers :: , container :: , #key editor) => (buffers :: ) container.%buffers := buffers; // If this is the last buffer using this source container, // get rid of the source container too let editor = editor | (*editor-frame* & frame-editor(*editor-frame*)); when (editor & empty?(buffers)) let namestring = as(, container-pathname(container)); remhash(editor-source-containers(editor), namestring) end; buffers end method container-buffers-setter; //--- How do we decide whether to use a database file? define method source-container-class (pathname :: ) => (class :: subclass()) end method source-container-class; define method note-container-changed (container :: ) => () container-modified?(container) := #t end method note-container-changed; define sealed method sectionize-container (container :: ) => (sectionized? :: ) // If the container has hard sections, those sections are definitive, // so don't go sectionizing it unless (container-has-hard-sections?(container)) let mode = find-mode-from-pathname(container-pathname(container)); do-sectionize-container(mode, container) end end method sectionize-container; define method add-section! (container :: , section :: , #key after :: type-union(, one-of(#f, #"start", #"end")) = #"end") => () assert(~section-container(section), "The section %= is already in the container %=", section, section-container(section)); let index = select (after) #f, #"start" => #"start"; #"end" => #"end"; otherwise => assert(section-container(after) == container, "The 'after' section %= is not in the container %=", after, container); position(container-sections(container), after) + 1; end; section-container(section) := container; insert-at!(container-sections(container), section, index) end method add-section!; define method remove-section! (container :: , section :: ) => () assert(section-container(section) == container, "The section %= is not in the container %=", section, section-container(section)); //---*** This needs to notify all the nodes using this section section-container(section) := #f; container-sections(container) := remove!(container-sections(container), section) end method remove-section!; /// Flat file source containers define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed inline method container-has-hard-sections? (container :: ) => (hard-sections? :: singleton(#f)) #f end method container-has-hard-sections?; define sealed method container-read-only? (container :: ) => (read-only? :: ) let pathname = container-pathname(container); let writeable? = get-file-property(pathname, #"writeable?", default: #t); ~writeable? end method container-read-only?; // Read the contents of the file into a single-node buffer define sealed method read-container-contents (container :: , buffer :: ) => () let pathname = container-pathname(container); // Make a section to hold all the lines of the file let section = make(
, container: container, start-line: #f, end-line: #f); container-sections(container) := make(, size: 1, fill: section); unless (member?(buffer, container-buffers(container))) push!(container-buffers(container), buffer) end; // Now read the file contents into the section let stream :: false-or() = pathname & file-exists?(pathname) & make(, locator: pathname, direction: #"input"); block () read-buffer-contents-from-stream(buffer, section, stream) cleanup when (stream & stream-open?(stream)) close(stream) end end; // Remember the file modification date container-modification-date(container) := get-file-property(pathname, #"modification-date", default: current-date()); container-modified?(container) := #f end method read-container-contents; // Read all the lines from the stream into the single provided section define method read-buffer-contents-from-stream (buffer :: , section :: , stream :: false-or()) => () // Fill up the section read-section-contents-from-stream(section, stream); // Now make a node for the buffer that holds the single section let node = make-section-node(buffer, section); node-buffer(node) := buffer; section-nodes(section) := list(node); buffer-start-node(buffer) := node; buffer-end-node(buffer) := node end method read-buffer-contents-from-stream; define method read-section-contents-from-stream (section :: , stream :: false-or()) => () let first-line :: false-or() = #f; let last-line :: false-or() = #f; when (stream) block (break) while (#t) let contents = read-line(stream, on-end-of-stream: #f); if (contents) let line = make(, contents: contents, section: section); unless (first-line) first-line := line end; line-previous(line) := last-line; when (last-line) line-next(last-line) := line end; last-line := line else break() end end end end; // Watch out for empty or non-existent files unless (first-line) first-line := make(, contents: "", section: section); last-line := first-line end; section-start-line(section) := first-line; section-end-line(section) := last-line end method read-section-contents-from-stream; /// Database source containers define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed inline method container-has-hard-sections? (container :: ) => (hard-sections? :: singleton(#t)) #t end method container-has-hard-sections?; define method read-container-contents (container :: , buffer :: ) => () //---*** Database files have hard sections, just read them in and create nodes container-modified?(container) := #f end method read-container-contents; define method add-section! (container :: , section :: , #key after :: type-union(, one-of(#f, #"start", #"end")) = #"end") => () //---*** What else does this need to do? next-method() end method add-section!; define method remove-section! (container :: , section :: ) => () //---*** What else does this need to do? next-method() end method remove-section!;