;;; isearch mode for skk with emacs 19.

;; $Id: skk-isearch.el,v 1.3 1997/09/16 08:56:20 mrt Exp $

;; Copyright (C) 1994, 1995, 1996, 1997
;; Enami Tsugutomo <enami@ba2.so-net.or.jp>

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either versions 2, or (at your option)
;; any later version.

;; This program is distributed in the hope that it will be useful
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with SKK, see the file COPYING.  If not, write to the Free
;; Software Foundation Inc., 59 Temple Place - Suite 330, Boston,
;; MA 02111-1307, USA.

;; skk-isearch-initial-mode examine the variable of skk before calling
;; skk-mode.
(require 'skk-foreword)
(require 'skk-vars)

;;
;; variables
;;
(defvar skk-isearch-incomplete-message "" "incomplete isearch message")
(defvar skk-isearch-working-buffer " *skk-isearch*"
  "work buffer for skk isearch.")

;; (setq skk-isearch-mode-map nil)
;; (lookup-key skk-isearch-mode-map " ")
(defvar skk-isearch-mode-map nil
  "Keymap for skk isearch mode.  This map should be derived from
isearch-mode-map.")

;;;###skk-autoload
(defvar skk-isearch-whitespace-regexp "\\(\\s \\|[ \t\n\r\f]\\)*")

(defvar skk-isearch-overriding-local-map
  (cond (skk-xemacs
	 'overriding-terminal-local-map)
	((and (boundp 'emacs-major-version)
	      (or (> emacs-major-version 19)
		  (and (= emacs-major-version 19) 
		       (>= emacs-minor-version 29))))
	 ;; GNU Emacs version 19.29, 19.30 and 19.31 uses this in isearch.el.
	 'overriding-terminal-local-map)
	;; GNU Emacs version 19.22 .. 19.28 uses this in isearch.el.
	((string-match "^19\\.2[2-8]" emacs-version) 'overriding-local-map))
  "Variable holding overrinding local map used in isearch-mode.")

(defvar skk-isearch-breakup-string-function
  (cond ((fboundp 'string-to-char-list)
	 ;; Mule 2.3 or its ancestor defines `string-to-char-list'.
	 'string-to-char-list)
	((fboundp 'string-to-list)
	 ;; delta version of Mule merged Emacs currently defines
	 ;; `string-to-list'.
	 'string-to-list)
	(t (error "No appropriate function as: %s"
		  'skk-isearch-breakup-string-function)))
  "Function to breakup STRING into list of characters.")

(defvar skk-isearch-breakable-character-p-function
  (cond ((fboundp 'char-category-set)
	 (function (lambda (char)
		     ;; see emacs/lisp/fill.el how the category `|' is
		     ;; treated.
		     (aref (char-category-set char) ?|))))
	((boundp 'word-across-newline)
	 (function (lambda (char)
		     ;; (let ((lc (char-leading-char char)))
		     ;;   (or (= lc lc-jp) (= lc lc-cn)))
		     (string-match (char-to-string char)
				   word-across-newline))))
	(t (error "No appropriate function as: %s"
		  'skk-isearch-breakable-character-p-function)))
  "Function to test if we can insert a newline around the CHAR when filling.")

(defvar skk-isearch-use-previous-mode nil
  "*If non-nil, search mode will be same as the last search mode for the
previous search in the buffer.")

;; suggested by Yoshiyuki Kondo, 1991.6.19.
;; modified by Mikio Nakajima, 1995.5.30
(defvar skk-isearch-start-mode nil
  "*Specifies the search mode when isearch is called.
This variable is valid only when `skk-isearch-use-previous-mode' is nil.
If nil, it means that if skk-mode has been called in this buffer, same as
the mode of the buffer, otherwise perform ascii search.  If `ascii', perfrom
ascii search.  If `kana' or `hirakana' -> hira kana search.  If `eiji',
perform zenkaku eiji (i.e. JIS X0208 alphabet) search.")

(defvar skk-isearch-mode nil
  "Current search mode.  0 -> hira kana search.  1 -> kata kana search.
2 -> zenkaku eiji (i.e. JIS X0208 alphabet) search.  3 -> ascii search.")


(defsubst skk-isearch-set-working-buffer ()
  "Set current buffer to the working buffer for skk isearch."
  (set-buffer (get-buffer-create skk-isearch-working-buffer)))

;;
;; interface to skk.el
;;
(defsubst skk-isearch-turn-off-skk-mode ()
  "Turn off skk mode."
  (skk-mode 0) )

(defsubst skk-isearch-turn-on-skk-mode ()
  "Turn on skk mode."
  (skk-mode 1) )

(defsubst skk-isearch-conversion-active-p ()
  "Non-nil if skk conversion is active."
  skk-henkan-on )

(defsubst skk-isearch-conversion-start ()
  "Point where conversion is start.  Includes skk marker."
  (- skk-henkan-start-point skk-kanji-len))

(defsubst skk-isearch-skk-kakutei ()
  "Perform kakutei."
  (skk-kakutei) )

(defsubst skk-isearch-skk-hirakana-mode-p ()
  "Non-nil if skk is hirakana input mode."
  (and (not skk-katakana) skk-j-mode) )
 
(defsubst skk-isearch-skk-turn-on-hirakana-mode ()
  "Set current skk mode to hirakana input mode."
  (skk-j-mode-on) )

(defsubst skk-isearch-skk-katakana-mode-p ()
  "Non-nil if skk is katakana input mode."
  (and skk-j-mode skk-katakana) )

(defsubst skk-isearch-skk-turn-on-katakana-mode ()
  "Set current skk mode to katakana input mode."
  (skk-j-mode-on 'katakana) )

(defsubst skk-isearch-skk-jix0208-latin-mode-p ()
  "Non-nil if skk is zenkaku (jisx0208 latin) input mode."
  skk-zenkaku-mode )

(defsubst skk-isearch-skk-turn-on-jix0208-latin-mode ()
  "Set current skk mode to zenkaku (jisx0208 latin) input mode."
  (skk-zenkaku-mode-on) )

(defsubst skk-isearch-skk-turn-on-latin-mode ()
  "Set current skk mode to normal latin input mode."
  (skk-ascii-mode-on) )

;; Override the function in skk-isearch.el.  I hope the
;; skk-kana-input would be rewritten without using while
;; read-char loop...
(defun skk-isearch-message ()
  "Show isearch message."
  ;; `prefix' is dynmaic variable in skk-kana-input.
  (skk-isearch-incomplete-message
   (if (boundp 'prefix)
       prefix
     (char-to-string last-command-char))))

(defun skk-isearch-current-mode ()
  "Return the symbolic current mode of skk for skk-isearch."
  (cond ((not skk-mode) nil)
	((skk-isearch-skk-katakana-mode-p) 'katakana)
	((skk-isearch-skk-hirakana-mode-p) 'hiragana)
	((skk-isearch-skk-jix0208-latin-mode-p) 'jisx0208-latin)
	(t 'latin)))

(defun skk-isearch-set-initial-mode (mode)
  "Set up the initial condition according to given symbolic MODE.
The MODE should be canonical."
  ;; following code is highly depends on internal of skk.
  ;; (skk-isearch-turn-on-skk-mode)
  ;; (skk-isearch-skk-kakutei)
  (cond ((eq mode 'hiragana) (skk-isearch-skk-turn-on-hirakana-mode))
	((eq mode 'katakana) (skk-isearch-skk-turn-on-katakana-mode))
	((eq mode 'jisx0208-latin)
	 (skk-isearch-skk-turn-on-jix0208-latin-mode))
	((eq mode 'latin) (skk-isearch-skk-turn-on-latin-mode))
	((not mode) (skk-isearch-turn-off-skk-mode))
	;; shouldn't happen.
	(t (error "unknown skk-isearch-mode %s" mode))))

;;
;; functions for hooks.
;;
;; 1. always invoke skk isearch.
;; (add-hook 'isearch-mode-hook 'skk-isearch-mode-setup)
;; (add-hook 'isearch-mode-end-hook 'skk-isearch-mode-cleanup)
;; 2. invoke only if skk-mode is on.
;; (add-hook 'isearch-mode-hook
;;           (function (lambda ()
;;			 (and (boundp 'skk-mode) skk-mode
;;			      (skk-isearch-mode-setup)))))
;; (add-hook 'isearch-mode-end-hook
;;	     (function (lambda ()
;;			 (and (boundp 'skk-mode) skk-mode
;;			      (skk-isearch-mode-cleanup)))))
;; 3. invoke if current buffer has japanese characters.
;; ...

(defvar skk-isearch-mode-canonical-alist
  '((hiragana . 0) (katakana . 1) (jisx0208-latin . 2) (latin . 3))
  "List of dot pair, (SYMBOL . NUMBER).
The SYMBOL is canonical skk mode, and NUMBER is its numerical representation.")

(defvar skk-isearch-mode-alias-alist
  '((hirakana . hiragana) (kana . hiragana)
    (eiji . jisx0208-latin)
    (ascii . latin))
  "List of dot pair, (ALIAS . CANONICAL).  The both ALIAS and CANONICAL should
be symbol.  ALIAS can be used as an alias of CANONICAL.  CANONICAL should be
found in `skk-isearch-mode-canonical-alist'.")

;; (makunbound 'skk-isearch-mode-string-alist)
(defvar skk-isearch-mode-string-alist
  '((hiragana . "[か] ") (katakana . "[カ] ")
    (jisx0208-latin . "[英] ") (latin . "[aa] ") (nil . "[--] "))
  "Alist of (MODE-SYMBOL . PROMPT-STRING).  MODE-SYMBOL is a symbol
indicates canonical mode of skk for skk-isearch.  PROMPT-STRING is a string
used in prompt to indicates current mode of skk for skk-isearch.")

(defun skk-isearch-symbolic-mode (mode)
  "Return symbolic skk isearch mode for given numerical MODE."
  (car (rassq mode skk-isearch-mode-canonical-alist)))

(defun skk-isearch-numerical-mode (mode)
  "Return numerical skk isearch mode for given symbolic MODE."
  (cdr (assq mode skk-isearch-mode-canonical-alist)))

(defun skk-isearch-mode-string ()
  "Return the current skk mode string for prompting."
  (with-current-buffer (get-buffer-create skk-isearch-working-buffer)
    (cdr (assq (skk-isearch-current-mode) skk-isearch-mode-string-alist))))

(defun skk-isearch-current-numerical-mode ()
  "Return the symbolic skk isearch mode according to the current skk
internal condition."
  (skk-isearch-numerical-mode (or (skk-isearch-current-mode) 'latin)))

(defun skk-isearch-canonical-start-mode (mode)
  "Canonicalize the symbolic skk isearch MODE."
  ;; alias, canonical, or error.
  (cond ((cdr (assq mode skk-isearch-mode-alias-alist)))
	((skk-isearch-numerical-mode mode) mode)
	(t (error "Unknown skk-isearch-start-mode: %s" mode))))

(defvar skk-isearch-initial-mode-when-skk-mode-disabled 'latin
  "*Symbol indicates the mode to use as initial mode for skk-isearch when
skk is turned off in the current buffer.")

(defun skk-isearch-initial-mode ()
  "Return the symbolic mode name of skk-isearch used to initialize working
buffer."
  (cond ((and skk-isearch-use-previous-mode skk-isearch-mode)
	 ;; use the mode when last isearch is done.  note that the
	 ;; `skk-isearch-mode' is numerical, so convert it to symbolic
	 ;; mode.
	 (skk-isearch-symbolic-mode skk-isearch-mode))
	(skk-isearch-start-mode
	 ;; always start with the specified mode.
	 ;; `skk-isearch-start-mode' is symbolic.
	 (skk-isearch-canonical-start-mode skk-isearch-start-mode))
	;; guess the current buffer.  note that if skk-mode is off,
	;; skk-isearch-current-mode returns symbol `nil' and control
	;; falls through to next cond clause.
	((skk-isearch-current-mode))
	;; skk-mode is off in this buffer.
	(t skk-isearch-initial-mode-when-skk-mode-disabled)))

(defun skk-isearch-initialize-working-buffer ()
  "Initialize the current buffer as working buffer for skk isearch.
More precicely, turn on skk-mode, put into kana mode, make sure
kakutei'ed and erase the buffer contents."
  (skk-isearch-turn-on-skk-mode)
  (skk-isearch-skk-kakutei)
  (erase-buffer))

;;;###autoload
(defun skk-isearch-mode-setup ()
  "hook function called when skk isearch begin."
  ;; setup working buffer.  initial skk mode for isearch should be
  ;; determined in the original buffer and set in working buffer.
  (let ((initial (skk-isearch-initial-mode)))
    (with-current-buffer (get-buffer-create skk-isearch-working-buffer)
      (skk-isearch-initialize-working-buffer)
      (skk-isearch-set-initial-mode initial)))
  ;; setup variables and keymap
  (set skk-isearch-overriding-local-map skk-isearch-mode-map)
  (setq skk-isearch-incomplete-message ""
	;; set skk-isearch-message non-nil to call skk-isearch-message.
	skk-isearch-message "")
  (skk-isearch-mode-message)
  (skk-isearch-incomplete-message))

;;;###autoload
(defun skk-isearch-mode-cleanup ()
  "Hook function called when skk isearch is done."
  ;; remember the current skk mode for next use.
  (and skk-isearch-use-previous-mode
       (setq skk-isearch-mode
	     (with-current-buffer (get-buffer-create skk-isearch-working-buffer)
	       (skk-isearch-current-numerical-mode))))
  ;; reset the overrinding-local-map.
  (set skk-isearch-overriding-local-map nil)
  (setq skk-isearch-message nil
	skk-isearch-last-mode-string ""
	skk-isearch-last-mode-regexp ""))

;;; for backward compatibility
(defalias 'skk-isearch-forward 'isearch-forward)
(defalias 'skk-isearch-forward-regexp 'isearch-forward-regexp)
(defalias 'skk-isearch-backward 'isearch-backward)
(defalias 'skk-isearch-backward-regexp 'isearch-backward-regexp)

(defun skk-isearch-incomplete-message (&optional prefix)
  "Show message when when kana kanji convertion is progress.
Optional argument PREFIX is apppended if given."
  (let ((isearch-message (concat isearch-message
				 skk-isearch-incomplete-message prefix)))
    (isearch-message)))

;;
;; define keymap
;;

;; XXX should be more generic
(defun skk-isearch-setup-keymap (map)
  ;; printable chars.
  (let ((c ?\040))
    (while (< c ?\177)
      (define-key map (char-to-string c) 'skk-isearch-wrapper)
      (setq c (1+ c))))

  ;; control chars for skk.
  (define-key map "\C-g" 'skk-isearch-keyboard-quit)
  (define-key map "\C-j" 'skk-isearch-newline)
  (define-key map "\C-m" 'skk-isearch-exit)
  (define-key map "\177" 'skk-isearch-delete-char)

  ;; C-x map for skk.
  (define-key map "\C-x" (make-sparse-keymap))
  (define-key map [?\C-x t] 'isearch-other-control-char)
  (define-key map "\C-x\C-j" 'skk-isearch-skk-mode)
  map)

(or skk-isearch-mode-map
    (if skk-xemacs
        (progn
          (setq skk-isearch-mode-map (skk-isearch-setup-keymap (make-keymap)))
          (set-keymap-parents skk-isearch-mode-map isearch-mode-map) )
      (setq skk-isearch-mode-map
            (skk-isearch-setup-keymap (cons 'keymap isearch-mode-map)) )))


;;
;; wrapper functions
;;

(defun skk-isearch-redo-function ()
  "Execute the command of given key sequence in skk environment."
  ;; with saving value of old binding.
  (let ((local-map (symbol-value skk-isearch-overriding-local-map)))
    (unwind-protect
	(progn
	  ;; temporarily disable the overriding-local-map.  this
	  ;; should be done in ther buffer isearch is performed, i.e.,
	  ;; before calling skk-isearch-set-working-buffer.
	  (set skk-isearch-overriding-local-map nil)
	  ;; don't change the current buffer during save/restore the
	  ;; overriding-local-map, because it is buffer local in some
	  ;; version of emacs.
	  (with-current-buffer (get-buffer-create skk-isearch-working-buffer)
	    ;; listify this-command-keys.  this works only if it is
	    ;; string.
	    (setq unread-command-events
		  (append (if (= (length (this-command-keys)) 0)
			      (list last-command-event)
			    (this-command-keys) )
			  nil ))
	    (condition-case error
		;; setup last-command-event and this-command because
		;; some command refers them.
		(let* ((keys (read-key-sequence nil))
		       (this-command (key-binding keys)))
		  (setq last-command-event (aref keys (1- (length keys))))
		  (command-execute this-command))
	      (quit (signal (car error) (cdr error)))
	      (error (signal (car error) (cdr error)))))
	  (skk-isearch-mode-message))
      (set skk-isearch-overriding-local-map local-map))))

(defun skk-isearch-search-string ()
  "Return the string to be searched.
If the conversion is progress and no string is fixed, just return nil."
    (with-current-buffer (get-buffer-create skk-isearch-working-buffer)
      (prog1
	  (cond ((skk-isearch-conversion-active-p)
		 (let ((start (skk-isearch-conversion-start)))
		   ;; is there fixed string?
		   (if (/= start 1)
		       (prog1
			   (buffer-substring 1 start)
			 (delete-region 1 start)))))
		;; whole string in the buffer is fixed.
		((not (zerop (buffer-size)))
		 (prog1
		     (buffer-string)
		   (erase-buffer))))
	;; update incomplete-message with contents of working buffer.
	(setq skk-isearch-incomplete-message (buffer-string))
	;; update echo area.
	(skk-isearch-incomplete-message))))


;;
;; regexp search supports.
;;
(defun skk-isearch-last-char (string)
  (and (string-match ".\\'" string)
       (string-to-char (substring string (match-beginning 0)))))

(defun skk-isearch-breakable-p (char)
  (and char
       (funcall skk-isearch-breakable-character-p-function char)))

(defun skk-isearch-search-string-regexp (string)
  (if isearch-regexp
      (let ((chars (funcall skk-isearch-breakup-string-function string))
	    (prev (skk-isearch-last-char isearch-string))
	    (result ""))
	(while chars
	  (if (and (skk-isearch-breakable-p prev)
		   (skk-isearch-breakable-p (car chars)))
	      (setq result (concat result skk-isearch-whitespace-regexp)))
	  (setq result (concat result (char-to-string (car chars)))
		prev (car chars)
		chars (cdr chars)))
	result)
    string))

(defvar skk-isearch-last-mode-string "")
(defvar skk-isearch-last-mode-regexp "")

(defun skk-isearch-mode-message ()
  "Prepend the skk isearch mode string to `isearch-message'.  If the current
mode is different from previous, remove it first."
  (let ((mode-string (skk-isearch-mode-string)))
    (if (string= mode-string skk-isearch-last-mode-string)
	nil
      (if (string-match skk-isearch-last-mode-regexp isearch-message)
	  (setq isearch-message (substring isearch-message (match-end 0))))
      (setq skk-isearch-last-mode-string mode-string
	    skk-isearch-last-mode-regexp (concat "^" 
						 (regexp-quote mode-string)))
      (setq isearch-message (concat mode-string isearch-message)))))

(defun skk-isearch-process-search-string (string)
  (isearch-process-search-string (skk-isearch-search-string-regexp string) 
				 string))


;;
;; interactive functions.
;;
(defun skk-isearch-delete-char (&rest args)
  (interactive "P")
  (or (with-current-buffer (get-buffer-create skk-isearch-working-buffer)
	;; following code is highly depends on internal of skk.
	(if (skk-isearch-conversion-active-p)
	    (prog1
		t
	      ;; now, we can't pass the universal argument within the
	      ;; isearch-mode.  so hard code the value `1'.
	      (delete-backward-char 1)
	      (setq skk-isearch-incomplete-message (buffer-string))
	      (skk-isearch-incomplete-message))))
      (isearch-delete-char)))

(defun skk-isearch-kakutei (isearch-function)
  "Special wrapper for skk-kakutei or newline."
  (if (with-current-buffer (get-buffer-create skk-isearch-working-buffer)
	;; following code is highly depends on internal of skk.
	(if (skk-isearch-conversion-active-p)
	    (prog1
		t
	      (skk-isearch-skk-kakutei))))
      (skk-isearch-process-search-string (skk-isearch-search-string))
    (funcall isearch-function)))

(defun skk-isearch-exit (&rest args)
  (interactive "P")
  (skk-isearch-kakutei (function isearch-exit)))

(defun skk-isearch-newline (&rest args)
  (interactive "P")
  ;; following code is highly depends on internal of skk.
  (if (with-current-buffer (get-buffer-create skk-isearch-working-buffer)
	(if (memq (skk-isearch-current-mode) '(latin jisx0208-latin))
	    (prog1
		t
	      ;; if the working buffer is latin or jisx0208-latin
	      ;; mode, default behaviour of C-j is set current mode
	      ;; to kana mode.
	      (skk-mode 1)
	      (skk-isearch-mode-message))))
      (isearch-message)
    (skk-isearch-kakutei (function isearch-printing-char))))

(defun skk-isearch-skk-mode (&rest args)
  (interactive "P")
  (skk-isearch-redo-function)
  (isearch-message))

(defun skk-isearch-keyboard-quit (&rest args)
  (interactive "P")
  (condition-case ()
      (progn
	(skk-isearch-redo-function)
	;; update echo area message.
	(skk-isearch-search-string))
    (quit (isearch-abort))))

(defun skk-isearch-wrapper (&rest args)
  (interactive "P")
  (skk-isearch-redo-function)
  (let ((string (skk-isearch-search-string)))
    (if (null string)
	;; on the way to converting to kanji.
	nil
      ;; with saving value of old binding...
      (let ((local-map (symbol-value skk-isearch-overriding-local-map))
	    (current-buffer (current-buffer)))
	;; because the overrinding local map may be buffer local, keep the
	;; current buffer, but we can't use save-excursion. ...
	(unwind-protect
	    (progn
	      (set skk-isearch-overriding-local-map isearch-mode-map)
	      (let ((command (key-binding string)))
		(if (commandp command)
		    ;; process a special character, such as *, |, ...
		    (command-execute command)
		  ;; just search literally.
		  (skk-isearch-process-search-string string) )))
	  ;; restore the overriding local map.
	  (set-buffer current-buffer)
	  (set skk-isearch-overriding-local-map local-map))))))

(put 'skk-isearch-wrapper 'isearch-command t)
(put 'skk-isearch-keyboard-quit 'isearch-command t)
(put 'skk-isearch-newline 'isearch-command t)
(put 'skk-isearch-exit 'isearch-command t)
(put 'skk-isearch-delete-char 'isearch-command t)
(put 'isearch-other-control-char 'isearch-command t)
(put 'skk-isearch-skk-mode 'isearch-command t)

(provide 'skk-isearch)

;;; skk-isearch.el ends here
