Module: environment-deuce Synopsis: Searching Text Files 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 /// Project Sources search domain; searches sources of current project // Search domain for searching through text files that may be open // in the editor, or on disk define abstract class () end class ; // Search domain for searching project source files define abstract class () constant slot search-domain-project :: , required-init-keyword: project:; end class ; // Search domain for direct project source files that are text files define class () end class ; // Search domain for all project source files (including subproject sources) define class () end class ; // Create search domains for a project when it is opened define method search-domain-project-opened-receiver (message :: ) => () let project = message.message-project; let direct-domain = make(, project: project); let all-domain = make(, project: project); register-search-domain(direct-domain); register-search-domain(all-domain); end method search-domain-project-opened-receiver; // Remove domain when its project is closed define method search-domain-project-closed-receiver (message :: ) => () let project = message.message-project; let obsolete-domains = make(); do-search-domains (method (domain :: ) when (instance?(domain, ) & domain.search-domain-project == project) add!(obsolete-domains, domain) end end method); for (domain :: in obsolete-domains) unregister-search-domain(domain) end end method search-domain-project-closed-receiver; tune-in($project-channel, search-domain-project-opened-receiver, message-type: ); tune-in($project-channel, search-domain-project-closed-receiver, message-type: ); // Search domain UI labels define constant $text-file-search-domain-target-kind-label = "file"; define constant $direct-project-sources-search-domain-label = "Project '%s' Sources"; define constant $all-project-sources-search-domain-label = "Project '%s' Sources" " && Subproject Sources"; define method project-search-domain-base-label (domain :: ) => (label :: ) $direct-project-sources-search-domain-label end method project-search-domain-base-label; define method project-search-domain-base-label (domain :: ) => (label :: ) $all-project-sources-search-domain-label end method project-search-domain-base-label; define method search-domain-label (domain :: ) => (label :: ) let project = domain.search-domain-project; format-to-string(project-search-domain-base-label(domain), environment-object-primitive-name(project, project)) end method search-domain-label; define function direct-project-text-source-files (project :: ) => (locators :: ) let locators = make(); // Locators are relative to the project directory let directory = project-directory(project); // Get Dylan source records for (source :: in project-sources(project)) let locator = source.source-record-location; when (locator & object-has-source?(project, locator)) let merged-locator = merge-locators(locator, directory); locators := add!(locators, merged-locator) end; end; // Get other source files for (locator :: in project-other-sources(project)) when (object-has-source?(project, locator)) let merged-locator = merge-locators(locator, directory); locators := add!(locators, merged-locator) end; end; locators end function direct-project-text-source-files; // Return direct project text source files define method search-domain-targets (domain :: ) => (locators :: ) let project = domain.search-domain-project; direct-project-text-source-files(project) end method search-domain-targets; // Return direct and subproject text source files define method search-domain-targets (domain :: ) => (locators :: ) let project = domain.search-domain-project; let locators = make(); local method add-project-source-files (project :: ) => () for (locator :: in direct-project-text-source-files(project)) locators := add!(locators, locator); end; end method; add-project-source-files(project); for (used-project in project-used-projects(project, indirect?: #t)) add-project-source-files(used-project); end; locators end method search-domain-targets; define method search-domain-target-label (domain :: , locator :: ) => (label :: ) locator-name(locator) end method search-domain-target-label; define method search-domain-target-kind-label (domain :: , locator :: ) => (label :: ) $text-file-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 :: , locator :: ) => (icon :: false-or()) // TBD: Return the same icon displayed in the Sources page #f end method search-domain-target-icon; */ define method search-domain-target-can-find? (domain :: , locator :: ) => (can-find? :: ) #t end method search-domain-target-can-find?; define method search-domain-target-can-replace? (domain :: , locator :: ) => (can-replace? :: ) // Always return #t. If a read-only file is encountered during a replace operation, // complain about it then. #t end method search-domain-target-can-replace?; define method search-domain-find (domain :: , locator :: , search-string :: , #rest keys, #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ) ignore(domain); // Find an existing buffer or create on in Fundamental mode //--- Not clear to me if the expense of creating a buffer is worth it... let (buffer, new-buffer?) = find-buffer-for-file(locator); // If searching from selection (and the buffer is not new, so there // is a 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? & ~new-buffer?) 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); object & pair(locator, object) else let object = find-in-buffer(buffer, search-string, backwards?: backwards?, wrap?: wrap?, match-case?: match-case?, match-word?: match-word?); case object == #f & new-buffer? => // If no match and the buffer is new, close it kill-buffer(buffer, frame: #f, editor: $environment-editor); new-buffer? => // We got a match, put the new buffer into the right major mode // and sectionize it. We delay sectionization until a successful // match just to make things a bit faster. let container = buffer-source-container(buffer); let major-mode = find-mode-from-pathname(container-pathname(container)); enter-mode(buffer, major-mode); sectionize-buffer(buffer); end; object & pair(locator, 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 :: , locator :: , 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 :: , locator :: , search-string :: , replace-string :: , #rest keys, #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ) ignore(domain); let buffer = find-buffer-for-file(locator); 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(replace-in-frame, frame, search-string, replace-string, keys); // Update the display call-in-frame(frame, redisplay-window, window); object & pair(locator, pair(buffer, object)) end method search-domain-replace-selection; define method search-domain-replace-all (domain :: , locator :: , search-string :: , replace-string :: , #rest keys, #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (replace-count :: ) // Don't do anything unless we're replacing from the selection // or there is at least one match if (from-selection? | apply(search-domain-find, domain, locator, search-string, keys)) let buffer = find-buffer-for-file(locator); 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); let locator :: = object.head; let buffer = find-buffer-for-file(locator); let frame = 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, object.tail) 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; */