; -*- package:steve; readtable:cl; mode:lisp; -*- ;Copyright (c) May 1983 by Christopher Eliot ; and Massachusetts Institute of Technology. ;Permission to copy all or part of this material is granted, provided ; that the copies are not made or distributed for resale, the MIT ; copyright notice and reference to the source file and the software ; distribution version appear, and that notice is given that copying ; is by permission of Massachusetts Institute of Technology. ;Search functions. ; (defvar *last-search-string* "") (defvar saved-search-start-mark nil) (editor-bind-key #\control-s incremental-search) (defun incremental-search () (setq saved-search-start-mark (copy-bp *editor-cursor*)) (with-query-line (catch 'search-done (i-search-internal "" :forward nil 0))) (send saved-search-start-mark :expire) nil) (editor-bind-key #\control-r reverse-search) (defun reverse-search () (setq saved-search-start-mark (copy-bp *editor-cursor*)) (with-query-line (catch 'search-done (i-search-internal "" :backward nil 0))) (send saved-search-start-mark :expire) nil) (defun i-search-internal (string dir failed? chars-just-read) (unless (or (eq dir :backward) failed?) (send *editor-cursor* :advance-pos-no-error (-& chars-just-read (string-length string)))) (loop with status = (and (null failed?) (search-buffer-for-string string *editor-cursor* dir)) and mark = (send *editor-cursor* :get-mark) and stand = nil initially (when (and (null failed?) (null status) (eq dir :forward)) ;;Fix adjustment in case of failure. (send *editor-cursor* :advance-pos-no-error (-& (string-length string) chars-just-read))) do (when (and (null status) (null failed?)) (send terminal-io :write-char #\bell)) (unless (type-ahead-p) (with-query-line-remaining (when (null status) (oustr "Failing " terminal-io)) (if (eq dir :backward) (oustr "Reverse I-Search: " terminal-io) (oustr "I-Search: " terminal-io)) (oustr string terminal-io) (make-screen-image))) (unless (type-ahead-p) (if (null status) (send (edit-cursor-window *editor-cursor*) :make-cursor-visible) (setq stand (cond ((null status) 1) ((eq dir :backward) (string-length string)) (t (-& (string-length string))))) (set-string-graphic-rendition :standout t *editor-cursor* stand) (send (edit-cursor-window *editor-cursor*) :make-cursor-visible) (peek-char&save) (set-string-graphic-rendition :standout nil *editor-cursor* stand))) thereis (null (i-search-parser string dir (not status))) ;;Rubout. do (send *editor-cursor* :goto-mark mark))) (defun i-search-parser (string dir failed?) (let ((chr (read-char&save terminal-io))) (case chr (#\rubout nil) (#\^S (cond ((and (string-equal "" string) (eq dir :forward)) (setq string *last-search-string*) (i-search-internal string :forward (and failed? (eq dir :forward)) (string-length string))) (t (unless (and failed? (eq dir :forward)) (send *editor-cursor* :advance-pos-no-error 1)) (i-search-internal string :forward (and failed? (eq dir :forward)) 0)))) (#\^R (cond ((and (string-equal "" string) (eq dir :backward)) (setq string *last-search-string*) (i-search-internal string :backward (and failed? (eq dir :backward)) (string-length string))) (t (unless (and failed? (eq dir :backward)) (send *editor-cursor* :advance-pos-no-error -1)) (i-search-internal string :backward (and failed? (eq dir :backward)) 0)))) (#\^Q (i-search-internal (string-append string (string (read-char&save terminal-io))) dir failed? 1)) (#\^G (goto-mark saved-search-start-mark) (setq *last-search-string* string) (ed-warn "Aborted Search")) (t (if (or (graphic-charp chr) (char= chr #\tab) (char= chr #\return)) (i-search-internal (string-append string (string chr)) dir failed? 1) (progn (or (char= chr #\altmode) (unread-char&save terminal-io)) (setq *last-search-string* string) (throw 'search-done t))))))) ; ;Now the actual search primatives. ; ;This actually changes the POINT it receives as an argument. (defun search-buffer-for-string (string point dir) (if (eq dir :backward) (search-back string point) (search-forward string point))) (defun search-forward (string point &aux p1 p2 chars length line-length (string-len (string-length string))) (cond ((0p string-len)) ((%string-posq #\return string 0 string-len) (multi-line-search-forward (string-lineify string) point)) (t (block search-forward (do ((line (bp-line point) (line-next line)) (pos (bp-position point) 0) (char1 (char-upcase (char string 0))) (char2 (char-downcase (char string 0)))) ((null line) nil) (setq chars (line-chars line) line-length (line-char-count line)) (do () (nil) (setq p1 (%string-posq char1 chars pos (-& line-length pos)) p2 (%string-posq char2 chars pos (-& line-length pos))) (when (not (or p1 p2)) (return nil)) (setq pos (min& (or p1 p2) (or p2 p1))) (when (<& (-& line-length pos) string-len) (return nil)) (when (string-equal chars string (1+& pos) 1 (+& string-len pos) string-len) (send point :move line pos) (send point :advance-pos-no-error string-len) (return-from search-forward t)) (setq pos (1+& pos)))))))) (defun string-lineify (string) (loop with len = (string-length string) for p1 first 0 then (1+& p2) while (<& p1 len) for p2 = (or (%string-posq #\return string p1 (-& len p1)) len) collect (substring string p1 p2))) ;This is a real hairy function. ;It is very similar to multi-line-search-back (below), (In fact this was ;used as a template.) The inversion is complicated also. (See comments ;before it.) Note line " for p1 first (bp-position point) then 0" is ;changed and the logical test "(>= (- ...) ...)" changes also. ;That test is used to handle the partial line that we start with. (defun multi-line-search-forward (s-list point) (loop with s1 = (car s-list) initially (setq s-list (cdr s-list)) with s1len = (string-length s1) for line first (bp-line point) then (line-next line) for p1 first (bp-position point) then 0 while line thereis (and (>=& (-& (line-char-count line) p1) s1len) (string-equal (line-chars line) s1 (-& (line-char-count line) s1len) 0 (line-char-count line) s1len) (loop for (s . tail) on s-list for l first (line-next line) then (line-next l) while l for llen = (line-char-count l) for slen = (string-length s) always (if (not (null tail)) ;Last string is a separate case. (and (=& llen slen) (string-equal (line-chars l) s 0 0 llen slen)) (and (>=& llen slen) (string-equal (line-chars l) s 0 0 slen slen))) finally (cond ((null l) (when (null (line-next line)) (return nil)) (send point :move (line-next line) 0)) (t (send point :move l slen))))))) ;This seems to assume that the point is the last possible place to ;match the first character in the string. I guess this is to avoid having ;to back up by the length of the string before matching. (defun search-back (string point &aux (string-len (string-length string))) (cond ((0p string-len)) ((%string-posq #\return string 0 string-len) (multi-line-search-back (string-lineify string) point)) (t (block search-tag (loop with char1 = (char string 0) for line first (bp-line point) then (line-previous line) while line for pos first (min& (1+& (bp-position point)) (line-char-count line)) then (line-char-count line) for chars = (line-chars line) for line-length = (line-char-count line) do (do () ((-p pos) nil) (setq pos (string-reverse-search-char char1 chars pos)) (when (null pos) (return nil)) (when (and (>=& (-& line-length pos) string-len) (loop for i from 1 below string-len for p from (1+& pos) always (char-equal (char chars p) (char string i))) (send point :move line pos) (return-from search-tag t))))))))) ;This is super hairy. ;the s-list is a list of strings. There is an implicit CR between ;each string. (Often the first string sill be "".) ;The first clause in the AND in the THEREIS does NOT check to see ;if there are enough characters in the line. It checks to see if ;point is close enough to the end so that when the string S1 is ;matched to the suffix of the line, the beginning of the match is on or ;before point. ;The STRING-EQUAL checks that the suffixes match. ; ;There seems to be a bogus double bug. This function can't do single line ;matches, because the string-equal is placed so it assumes there ;is a CR after the first string in s-list, even though the definition above ;says that CRs are between strings. The bug is that there is an assumed CR ;after a s-list of length one, and the function above returns such s-lists. ;all is cool though as long as this function multi-line-search-forward ;don't get changed. (defun multi-line-search-back (s-list point) (loop with s1 = (car s-list) initially (setq s-list (cdr s-list)) with s1len = (string-length s1) for line first (bp-line point) then (line-previous line) while line for p1 first (bp-position point) then (line-char-count line) thereis (and (<=& (-& (line-char-count line) p1) s1len) (string-equal (line-chars line) s1 (-& (line-char-count line) s1len) 0 (line-char-count line) s1len) (loop for (s . tail) on s-list for l first (line-next line) then (line-next l) while l for llen = (line-char-count l) for slen = (string-length s) always (if (not (null tail)) ;Last string is a separate case. (and (=& llen slen) (string-equal (line-chars l) s 0 0 llen slen)) (and (>=& llen slen) (string-equal (line-chars l) s 0 0 slen slen))) finally (send point :move line (-& (line-char-count line) s1len))))))