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 /// XML syntax tables define constant $xml-word-syntax :: = make(); define constant $xml-atom-syntax :: = make(); define constant $xml-list-syntax :: = make(); define function initialize-xml-syntax-tables () => () copy-syntax-table-into!($default-word-syntax, $xml-word-syntax); // In atom syntax table, '<' and '>' are separator characters copy-syntax-table-into!($default-atom-syntax, $xml-atom-syntax); let table = $xml-atom-syntax.%syntax-table; table[as(, '<')] := $atom-delimiter; table[as(, '>')] := $atom-delimiter; // In list syntax table, '<' and '>' are open and close characters copy-syntax-table-into!($default-list-syntax, $xml-list-syntax); let table = $xml-list-syntax.%syntax-table; table[as(, '<')] := $list-open; table[as(, '>')] := $list-close; #f end function initialize-xml-syntax-tables; initialize-xml-syntax-tables(); /// XML mode define open class () keyword word-syntax: = $xml-word-syntax; keyword atom-syntax: = $xml-atom-syntax; keyword list-syntax: = $xml-list-syntax; end class ; define method initialize-major-mode (mode :: , #key command-set = mode-command-set(mode)) => () next-method(); // Install some XML mode commands let nothing = 0; let shift = $shift-key; let control = $control-key; let meta = $meta-key; let control+meta = logior($control-key, $meta-key); let control+shift = logior($control-key, $shift-key); let meta+shift = logior($meta-key, $shift-key); let command-set = copy-command-set(command-set); mode-command-set(mode) := command-set; //--- It would be nice to share the following with , //--- perhaps by putting these bindings into select (command-set-name(command-set)) #"emacs" => let command-table = standard-command-table(command-set); add-commands!(command-table, vector('f', control+meta, forward-expression), vector('b', control+meta, backward-expression), vector('u', control+meta, up-expression), vector('d', control+meta, down-expression), vector('a', control+meta, start-of-definition), vector('e', control+meta, end-of-definition), vector(#"right", meta, forward-expression), vector(#"left", meta, backward-expression), vector(#"up", meta, up-expression), vector(#"down", meta, down-expression), vector(#"prior", meta, start-of-definition), vector(#"next", meta, end-of-definition), vector(#"delete", control+meta, delete-expression), vector(#"backspace", control+meta, rubout-expression), vector('k', control+meta, delete-expression), vector('t', control+meta, transpose-expressions), vector(';', control, insert-comment), vector(#"tab", control, insert-tab), vector(#"tab", nothing, indent-line), vector('\\', control+meta, indent-region), vector('q', control+meta, indent-expression), vector(')', meta, xml-insert-close-element), vector('i', control+meta, complete-name)); let command-table = control-X-command-table(command-set); add-commands!(command-table, vector(';', control, comment-region)); let command-table = escape-command-table(command-set); add-commands!(command-table, vector('f', control, forward-expression), vector('b', control, backward-expression), vector('u', control, up-expression), vector('d', control, down-expression), vector('a', control, start-of-definition), vector('e', control, end-of-definition), vector(#"delete", control, delete-expression), vector(#"backspace", control, rubout-expression), vector('k', control, delete-expression), vector('t', control, transpose-expressions), vector('\\', control, indent-region), vector('q', control, indent-expression), vector(')', nothing, xml-insert-close-element), vector('i', control, complete-name)); #"windows" => let command-table = standard-command-table(command-set); add-commands!(command-table, vector(#"right", meta, forward-expression), vector(#"left", meta, backward-expression), vector(#"up", meta, up-expression), vector(#"down", meta, down-expression), vector(#"prior", meta, start-of-definition), vector(#"next", meta, end-of-definition), vector(#"tab", nothing, indent-region), vector('i', control, insert-tab)); otherwise => #[]; end; command-set end method initialize-major-mode; begin gethash(*keyword->major-mode*, #"xml") := ; gethash(*file-type->major-mode*, #"xml") := ; gethash(*file-type->major-mode*, #"xsl") := ; end; define method mode-name (mode :: ) => (name :: ) "XML" end method mode-name; define method source-file-type (mode :: ) => (file-type) #"xml" end method source-file-type; /// XML sections define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); /// XML nodes define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); /// XML "expressions" define method do-atom-under-bp (mode :: , bp :: ) => (sbp :: , ebp :: ) let node = bp-node(bp) | bp-buffer(bp); let sbp = if (atom-syntax(bp-character-before(bp)) == $atom-delimiter) forward-over(bp, #[' ', '\t', '\f'], interval: node) else move-over-atoms(bp, -1, interval: node) end; let ebp = move-over-atoms(sbp, 1, interval: node); // Some characters are magic at the beginning of an atom // Skip them, unless the user is right on top of one when (bp ~= sbp & member?(bp-character(sbp), #['+', '-', '~', '?', '/'])) increment-bp!(sbp) end; values(sbp, ebp) end method do-atom-under-bp; define sealed method do-move-over-expressions (mode :: , bp :: , n :: , #key fixup? = #t, interval = bp-buffer(bp)) => (bp :: false-or()) move-over-xml-tags(bp, n, fixup?: fixup?, interval: interval) end method do-move-over-expressions; define sealed method do-move-up-or-down-expressions (mode :: , bp :: , n :: , #key fixup? = #t, interval = bp-buffer(bp)) => (bp :: false-or()) move-up-or-down-xml-tags(bp, n, fixup?: fixup?, interval: interval) end method do-move-up-or-down-expressions; define command xml-insert-close-element (frame) "Insert a close element to match the previous open element." //---*** Find the previous unclosed begin tag, then close it end command xml-insert-close-element; /// XML motion primitives define sealed method move-over-xml-tags (bp :: , n :: , #key fixup? = #t, interval = bp-buffer(bp)) => (bp :: false-or()) block (return) let bp :: = copy-bp(bp); unless (n = 0) let reverse? = (n < 0); let limit :: = if (reverse?) interval-start-bp(interval) else interval-end-bp(interval) end; local method true (ch) ignore(ch); values(#t, #f) end method, method non-whitespace? (ch :: ) if (any-whitespace-char?(ch)) values(#f, #f) else values(#t, #t) end end method; for (i :: from 0 below abs(n)) when (bp = limit) return(fixup? & limit) end; move-forward-or-backward!(bp, non-whitespace?, reverse?, interval: interval); unless (bp = limit) let start-char = if (reverse?) bp-character-before(bp) else bp-character(bp) end; let syntax = list-syntax(start-char); let nbp = case syntax == $list-double-quote | syntax == $list-single-quote => move-over-matching-thing!(bp, start-char, reverse?, interval: interval); syntax == (if (reverse?) $list-close else $list-open end) => move-over-balanced-thing!(bp, start-char, reverse?, interval: interval); otherwise => move-over-atom!(bp, reverse?, interval: interval); end; nbp | return(fixup? & limit) end end end; bp end end method move-over-xml-tags; define sealed method move-up-or-down-xml-tags (bp :: , n :: , #key fixup? = #t, interval = bp-node(bp) | bp-buffer(bp)) => (bp :: false-or()) //---*** Do this for real by moving in to or out of begin/end tags move-up-or-down-lists(bp, n, fixup?: #t, interval: interval) end method move-up-or-down-xml-tags; define sealed method xml-tag-name (bp :: ) => (tag-name :: false-or(), empty-element? :: , end-tag? :: ) select (bp-character(bp)) '<' => let bp1 = increment-bp!(copy-bp(bp)); let bp2 = move-over-atoms(bp1, 1); let empty? :: = (bp-character(bp1) == '/'); when (empty?) increment-bp!(bp1) end; when (bp-character(bp2) == '>') decrement-bp!(bp2) end; let end? :: = (bp-character(bp2) == '/'); when (end?) decrement-bp!(bp2) end; values(as(, make-interval(bp1, bp2, in-order?: #t)), empty?, end?); '>' => let bp = search(bp, "<", reverse?: #t); bp & xml-tag-name(bp); otherwise => values(#f, #f, #f); end end method xml-tag-name; /// XML comments define method do-insert-comment (mode :: , line :: , #key column) => (bp :: false-or()) if (column) //--- This should attempt to insert the comment at the given column let bp1 = line-end(line); let bp2 = insert-moving!(bp1, "\t"); move-over-characters(bp2, -3) else let bp1 = line-start(line); let bp2 = insert-moving!(bp1, "\t"); move-over-characters(bp2, -3) end end method do-insert-comment; define method do-comment-region (mode :: , region :: , #key comment?) => () let first-line = bp-line(interval-start-bp(region)); let last-line = bp-line(interval-end-bp(region)); local method comment (line :: , si, ei, last?) ignore(last?); when (line == first-line) insert-into-line(line, si, "") end; end method, method uncomment (line :: , si, ei, last?) ignore(last?); //---*** How to do this? end method; do-lines(if (comment?) comment else uncomment end, region) end method do-comment-region; /// XML indentation define method do-indent-line (mode :: , line :: ) => (bp :: false-or(), dx :: , nchars :: ) indent-xml-line(mode, line) end method do-indent-line; //--- Can we merge this with 'do-indent-line' and then move the method //--- up into , sharing the new method with ? //--- If so, 'xxx-line-indentation' should be part of the protocol... define sealed method indent-xml-line (mode :: , line :: , #key definition-type) => (bp :: false-or(), dx :: , nchars :: ) let bp1 = line-start(line); let bp2 = forward-over(bp1, #[' ', '\t']); local method insert-indentation! (line :: , indentation :: ) => (bp :: , nchars :: ) if (text-line?(line)) let interval = make-interval(bp1, bp2, in-order?: #t); let old-n = count-characters(interval); let new-n = max(indentation, 0); delete!(interval); let spaces = make(, size: new-n, fill: ' '); values(insert-moving!(bp1, spaces), new-n - old-n) else values(bp1, 0) end end method; let (old-indentation, new-indentation) = xml-line-indentation(mode, line, definition-type: definition-type); let dx = new-indentation - old-indentation; if (dx = 0) values(bp2, 0, 0) else let (bp, nchars) = insert-indentation!(line, new-indentation); values(bp, nchars, dx) end end method indent-xml-line; //--- Hey, why is this on ? define sealed method xml-line-indentation (mode :: , line :: , #key definition-type) => (old-indentation :: , new-indentation :: ) let window :: = frame-window(*editor-frame*); let margin = line-margin(line, mode, window); let space-width = string-size(window, " "); local method line-indentation (sbp :: ) => (indentation :: ) if (text-line?(bp-line(sbp))) let indentation = index->position(bp-line(sbp), mode, window, bp-index(sbp)) - margin; floor/(indentation, space-width) else 0 end end method; let section = line-section(line); let this-bp = line-start(line); let this-sbp = forward-over(this-bp, #[' ', '\t']); let this-indentation :: = line-indentation(this-sbp); let (this-name, this-empty?, this-end?) = xml-tag-name(this-sbp); let prev-line = line-previous(line); let prev-bp = prev-line & line-start(prev-line); let prev-sbp = prev-bp & forward-over(prev-bp, #[' ', '\t']); let prev-indentation :: = if (prev-sbp) line-indentation(prev-sbp) else 0 end; let (prev-name, prev-empty?, prev-end?) = xml-tag-name(prev-sbp); //--- Crude, but not entirely ineffective block (return) if (this-name) if (this-end?) return(this-indentation, prev-indentation - 2) else if (prev-name & prev-empty?) return(this-indentation, prev-indentation) else return(this-indentation, prev-indentation + 2) end end else if (~prev-name | (prev-name & prev-empty?)) return(this-indentation, prev-indentation) else return(this-indentation, prev-indentation + 2) end end end end method xml-line-indentation;