Module: environment-deuce Synopsis: Searching Editor Buffers Author: Chris Page 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 /// Searching within a buffer // Search a buffer for a string, starting at start-bp. If start-bp is // false, search from the start or end of the buffer, depending on // the direction of the search. Returns a pair of bps representing the // start/end of the found text, or false if no match was found. define method find-in-buffer (buffer :: , string :: , #key start-bp :: false-or(), backwards? :: , wrap? :: , match-case? :: , match-word? :: , progress-callback :: false-or()) => (object :: false-or()) let object = #f; dynamic-bind (*buffer* = buffer) let bp = start-bp | if (backwards?) interval-end-bp(buffer) else interval-start-bp(buffer) end; let syntax-table = match-word? & list-syntax-table(buffer-major-mode(buffer)); let test = if (match-case?) \= else char-equal? end; //---*** cpage: 1998.07.28 We need to find an appropriate way to cache these // tables somewhere. let (skip-table, reoccurrence-table) = compute-boyer-tables(string, // skip-table: editor-skip-table(editor), // reoccurrence-table: editor-reoccurrence-table(editor), test: test); //--- cpage: 1988.07.29 This code was filched from Deuce's find-next-or-previous-string // and I'm not sure why it uses 'state' to terminate searching, or why it // assigns 'wrap?' to 'state'. We may want to clean this up a bit. let state :: = #t; while (state) bp := search(bp, string, test: test, reverse?: backwards?, syntax-table: syntax-table, skip-table: skip-table, reoccurrence-table: reoccurrence-table); if (bp) let length = size(string); // Return #( point . mark ) with point at the start of the match // for reverse searches. object := if (backwards?) pair(bp, move-over-characters(bp, length)) else pair(move-over-characters(bp, length), bp) end; state := #f // force the search to end else if (wrap?) bp := if (backwards?) interval-end-bp(buffer); else interval-start-bp(buffer); end; state := wrap?; wrap? := #f; // give up next time around else state := #f; // done searching end end end end; object end method find-in-buffer; /// All Buffers search domain; searches all open editor files define class () end class ; define constant $all-buffers-search-domain = make(); register-search-domain($all-buffers-search-domain); // Search domain UI labels define constant $all-buffers-search-domain-label = "All Open Text Documents"; define constant $all-buffers-search-domain-target-kind-label = "text document"; define method search-domain-label (domain :: ) => (label :: ) $all-buffers-search-domain-label end method search-domain-label; define method search-domain-targets (domain :: ) => (buffers :: ) // Return all non-anonymous buffers choose(complement(buffer-anonymous?), editor-buffers($environment-editor)) end method search-domain-targets; define method search-domain-target-label (domain :: , buffer :: ) => (label :: ) buffer-title(buffer, show-path?: #f) end method search-domain-target-label; define method search-domain-target-kind-label (domain :: , buffer :: ) => (label :: ) $all-buffers-search-domain-target-kind-label end method search-domain-target-kind-label; //--- cpage: 1998.07.27 This is not yet called, since we have no UI that displays this. /* define method search-domain-target-icon (domain :: , buffer :: ) => (icon :: false-or()) // TBD: Return the same icon used in the window title bar #f end method search-domain-target-icon; */ define method search-domain-target-can-find? (domain :: , buffer :: ) => (can-find? :: ) #t end method search-domain-target-can-find?; define method search-domain-target-can-replace? (domain :: , buffer :: ) => (can-replace? :: ) ~buffer-read-only?(buffer) end method search-domain-target-can-replace?; define method search-domain-find (domain :: , buffer :: , search-string :: , #rest keys, #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ) ignore(domain); // If searching from selection, get a frame for the buffer, then defer // searching to find-in-frame; else search in the buffer and only get // a frame if there's a match. This way, we don't affect a frame's // current buffer setting if there's no match. if (from-selection?) let frame = find-frame-for-buffer(buffer); let window = frame-window(frame); // Make sure the buffer is the current frame buffer when (buffer ~= frame-buffer(frame)) select-buffer(window, buffer); queue-redisplay(window, $display-all); end; let object = apply(find-in-frame, frame, search-string, keys); // Update the display call-in-frame(frame, redisplay-window, window); // Pair with the frame, so that we guarantee to reveal in the // same frame, since this frame's selection was used. object & pair(frame, object) else let object = find-in-buffer(buffer, search-string, backwards?: backwards?, wrap?: wrap?, match-case?: match-case?, match-word?: match-word?); // Pair with the buffer; we'll find a frame when we need to reveal object & pair(#f, pair(buffer, object)) end end method search-domain-find; //---*** cpage: 1997.07.27 This is not yet used. This will be "batch" searching. /* define method search-domain-find-all (domain :: , buffer :: , register-object :: , search-string :: , #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => () // TBD: I think this should be handled by a method on , though // it may be faster to do it in a custom method. next-method(); end method search-domain-find-all; */ define method search-domain-replace-selection (domain :: , buffer :: , search-string :: , replace-string :: , #rest keys, #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ) ignore(domain); let frame = find-frame-for-buffer(buffer); let window = frame-window(frame); // Make sure the buffer is the current frame buffer when (buffer ~= frame-buffer(frame)) select-buffer(window, buffer); queue-redisplay(window, $display-all); end; let replacement-object = apply(replace-in-frame, frame, search-string, replace-string, keys); // Update the display call-in-frame(frame, redisplay-window, window); replacement-object & pair(frame, replacement-object) end method search-domain-replace-selection; define method search-domain-replace-all (domain :: , buffer :: , search-string :: , replace-string :: , #rest keys, #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (replace-count :: ) ignore(domain); // Don't do anything unless we're replacing from the selection // or there is at least one match if (from-selection? | find-in-buffer(buffer, search-string, backwards?: backwards?, wrap?: wrap?, match-case?: match-case?, match-word?: match-word?, progress-callback: progress-callback)) let frame = find-frame-for-buffer(buffer); let window = frame-window(frame); // Make sure the buffer is the current frame buffer when (buffer ~= frame-buffer(frame)) select-buffer(window, buffer); queue-redisplay(window, $display-all); end; let replace-count :: = apply(replace-all-in-frame, frame, search-string, replace-string, keys); // Update the display call-in-frame(frame, redisplay-window, window); replace-count else 0 end end method search-domain-replace-all; //---*** cpage: 1998.07.29 This is just about exactly what // does. Perhaps we should forward some of this to $frame-search-domain. define method search-domain-reveal-search-object (domain :: , object :: ) => (revealed? :: ) ignore(domain); // If we searched from a particular frame's selection, use that frame; // else search for a frame in which to display the buffer. let buffer-and-bps = object.tail; let buffer = buffer-and-bps.head; let frame = object.head | find-frame-for-buffer(buffer); when (frame & frame.frame-state ~= #"destroyed") call-in-frame(frame, method () => () deiconify-frame(frame); raise-frame(frame); end); frame-reveal-search-object(frame, buffer-and-bps) end end method search-domain-reveal-search-object; //---*** cpage: 1998.07.27 These two functions are not yet called, anyway, as // they are for displaying found items for "batch" searching. /* define method search-domain-search-object-label (domain :: , object :: ) => (label :: false-or()) // TBD: Return the short file name, line number or range, and perhaps an excerpt // of the text. Actually, we should probably break this into two functions, // one for a "location" and the other for a "description". #f end method search-domain-search-object-label; define method search-domain-search-object-icon (domain :: , object :: ) => (icon :: false-or()) // TBD: Return the editor title bar icon, probably. #f end method search-domain-search-object-icon; */