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 /// Simple mail composition and sending define sealed class (, ) end class ; define sealed domain make (singleton()); define sealed domain initialize (); define variable *mail-buffer-count* :: = 0; define function make-mail-buffer (#rest buffer-initargs, #key name, editor = frame-editor(*editor-frame*), to, subject, body, #all-keys) => (buffer :: ) ignore(editor); unless (name) inc!(*mail-buffer-count*); name := format-to-string("Mail %d", *mail-buffer-count*) end; with-keywords-removed (buffer-initargs = buffer-initargs, #[name:, to:, subject:, body:]) let buffer = apply(make, , name: name, major-mode: find-mode(), buffer-initargs); initialize-mail-buffer(buffer, to: to, subject: subject, body: body); buffer end end function make-mail-buffer; define sealed method initialize-mail-buffer (buffer :: , #key to, subject, body: body-text) => () // Make the header section let header = make-empty-section(section-class: ); let to = make(, contents: format-to-string("To: %s", to | "")); let subject = make(, contents: format-to-string("Subject: %s", subject | "")); line-section(to) := header; section-start-line(header) := to; section-end-line(header) := to; add-line!(header, subject); // Make the body section let body = make-empty-section(section-class: ); let divider = make(); let stream = make(, contents: body-text | ""); read-section-contents-from-stream(body, stream); add-line!(body, divider, after: #"start"); // Build the mail buffer, which consists of the header followed by the body let header-node = make-section-node(buffer, header); let body-node = make-section-node(buffer, body); node-buffer(header-node) := buffer; buffer-start-node(buffer) := header-node; buffer-end-node(buffer) := header-node; add-node!(buffer, body-node); end method initialize-mail-buffer; define sealed method revert-buffer (buffer :: , #key buffer-filler :: false-or() = #f, major-mode) => (reverted? :: ) ignore(buffer-filler, major-mode); initialize-mail-buffer(buffer); #t end method revert-buffer; define sealed method buffer-initial-point (buffer :: , #key point :: false-or() = #f) => (bp :: false-or()) point | line-end(bp-line(interval-start-bp(buffer))) end method buffer-initial-point; define sealed method buffer-modified? (buffer :: ) => (modified? :: ) // If there's any data in the body of the mail, claim that it's modified let header = buffer-start-node(buffer); let body = header & node-next(header); let section = body & node-section(body); when (section) let nlines = count-lines(section); let line = nlines > 1 & line-next(section-start-line(section)); nlines > 2 | (line & line-length(line) > 0) end end method buffer-modified?; /// Parsing the contents of a mail buffer define sealed method parse-mail-buffer (buffer :: ) => (from :: false-or(), to :: false-or(), cc :: false-or(), subject :: false-or(), body :: false-or(), other-headers :: ) let header-node = buffer-start-node(buffer); let body-node = header-node & node-next(header-node); let header = header-node & node-section(header-node); let body = body-node & node-section(body-node); let from :: false-or() = #f; let to :: false-or() = #f; let cc :: false-or() = #f; let subj :: false-or() = #f; let body :: false-or() = body & as(, body); let other-headers :: = make(); //--- I admit it, this is a pretty cheesy header parser when (header) local method looking-at? (contents :: , length :: , string :: ) let _start = 0; let _end = min(size(string), length); string-equal?(contents, string, start1: _start, end1: _end) end method; for (line = section-start-line(header) then line-next(line), until: ~line) let contents = line-contents(line); let length = line-length(line); case looking-at?(contents, length, "From:") => from := trim-whitespace(copy-sequence(contents, start: 5)); looking-at?(contents, length, "To:") => to := trim-whitespace(copy-sequence(contents, start: 3)); looking-at?(contents, length, "cc:") => cc := trim-whitespace(copy-sequence(contents, start: 3)); looking-at?(contents, length, "Subject:") => subj := trim-whitespace(copy-sequence(contents, start: 8)); otherwise => let colon = position(contents, ':'); when (colon) let name = copy-sequence(contents, end: colon); let key = as(, name); let value = trim-whitespace(copy-sequence(contents, start: colon + 1)); add!(other-headers, vector(key, name, value)) end; end end end; values(from, to, cc, subj, body, other-headers) end method parse-mail-buffer; /// Mail mode define sealed class () end class ; define sealed method initialize-major-mode (mode :: , #key command-set = mode-command-set(mode)) => () next-method(); let control = $control-key; let command-set = copy-command-set(command-set); mode-command-set(mode) := command-set; select (command-set-name(command-set)) #"emacs" => let command-table = control-C-command-table(command-set); add-commands!(command-table, vector('s', control, mail-send), vector('c', control, mail-send-and-exit), vector('w', control, mail-signature)); #"windows" => #f; otherwise => #[]; end end method initialize-major-mode; define sealed method mode-name (mode :: ) => (name :: ) "Mail" end method mode-name; begin gethash(*keyword->major-mode*, #"mail") := ; gethash(*file-type->major-mode*, #"mail") := end; /// Mail header section define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); /// Mail body section define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); /// Divider lines // This is a subclass of because it is meant to be part of the // structure of the buffer. If it were a subclass of , // users could accidently delete the line, which we don't want. define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define method initialize (line :: , #key) next-method(); line-read-only?(line) := #t end method initialize; define sealed method do-characters (function :: , line :: , #key start: _start, end: _end, from-end?, skip-test) => () ignore(_start, _end, from-end?, skip-test); #f end method do-characters; define sealed method dump-line (line :: , stream :: ) => () write-line(stream, "--text follows this line--"); end method dump-line; define variable $divider-line-color = make-color(150, 150, 150); define sealed method display-line (line :: , mode :: , window :: , x :: , y :: , #key start: _start :: = 0, end: _end, align-y = #"top") => () ignore(_start, _end); let (width, height) = window-viewport-size(window); ignore(height); let (fw, height, baseline, fd) = font-metrics(window, window-default-font(window)); ignore(fw, fd); when (align-y == #"baseline") dec!(y, baseline) end; let offset = 5; let line-y = y + floor/(height, 2); draw-line(window, x + offset, line-y, x + width - offset, line-y, thickness: 1, color: $divider-line-color); end method display-line; define sealed method line-size (line :: , mode :: , window :: , #key start: _start, end: _end) => (width :: , height :: , baseline :: ) ignore(_start, _end); let (width, height) = window-viewport-size(window); ignore(height); let (fw, height, baseline, fd) = font-metrics(window, window-default-font(window)); ignore(fw, fd); values(width, height, baseline) end method line-size; define sealed method line-for-display-only? (line :: ) => (display-only? :: ) #t end method line-for-display-only?; /// Mail sending commands define command send-mail (frame, #key to, subject, body) "Compose and send a mail message." let window :: = frame-window(frame); let buffer :: = make-mail-buffer(to: to, subject: subject, body: body); select-buffer-in-appropriate-window(window, buffer); frame-last-command-type(frame) := #"mail" end command send-mail; define command mail-send (frame) "Send the current mail message." let window :: = frame-window(frame); let buffer :: = frame-buffer(frame); when (instance?(buffer, )) let (from, to, cc, subject, body, other-headers) = parse-mail-buffer(buffer); let (success?, message) = do-send-mail(window, to, subject, body, from: from, cc: cc, other-headers: other-headers); if (success?) display-message(window, "Mail sent") else warning-dialog(window, "Mail failed:\n%s", message | "No reason given"); command-error("Mail failed") end end; frame-last-command-type(frame) := #"mail" end command mail-send; define command mail-send-and-exit (frame) "Send the current mail message and close the mail buffer." let window :: = frame-window(frame); let buffer :: = frame-buffer(frame); when (instance?(buffer, )) mail-send(frame); if (fixed-frame-buffer?(editor-policy(frame-editor(frame)))) // Kill the buffer and close the window kill-buffer(buffer) else // Or in Emacs mode, just "bury" this buffer at the bottom //--- Too bad 'select-buffer' doesn't know how to bury it! let prev-buffer = previously-selected-buffer(window, 1); when (prev-buffer) select-buffer-in-appropriate-window(window, prev-buffer) end end end; frame-last-command-type(frame) := #"mail" end command mail-send-and-exit; define command mail-signature (frame) "Insert a signature." //---*** Insert the signature frame-last-command-type(frame) := #"mail" end command mail-signature; // For specialization by back-ends define open generic do-send-mail (window :: , to :: , subject :: , body :: , #key from :: false-or(), cc :: false-or(), other-headers) => (success? :: , message :: false-or()); define method do-send-mail (window :: , to :: , subject :: , body :: , #key from :: false-or(), cc :: false-or(), other-headers) => (success? :: , message :: false-or()) values(#f, "Don't know how to send mail!") end method do-send-mail;