;;;
;;; Smalltalk mode for Gnu Emacs
;;;

;;; TODO:
;;;
;;; need to fix ! to check to see if it's in a string or comment
;;; and not to take effect if it is
;;;
;;;parseMethodDefinition
;;;    " parse a method definition, from selector, to terminating bang "
;;;    | t |
;;;
;;;    t _ stream peek.
;;; this line indents too far


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Copyright (C) 1988, 1989, 1990, 1991, 1992, 1994, 1995 Free Software Foundation, Inc.
;;; Written by Steve Byrne.
;;;
;;; This file is part of GNU Smalltalk.
;;;
;;; GNU Smalltalk 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 version 1, or (at your option) any later 
;;; version.
;;;
;;; GNU Smalltalk 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 GNU Smalltalk; see the file COPYING.  If not, write to the Free
;;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; Incorporates Frank Caggiano's changes for Emacs 19.

(require 'browse)
(provide 'st)

(defvar smalltalk-prompt-pattern "^st> *"
  "Regexp to match prompts in smalltalk buffer.")

(defvar smalltalk-name-regexp "[A-Za-z][A-Za-z0-9]*"
  "A regular expression that matches a Smalltalk identifier")

(defvar smalltalk-keyword-regexp (concat smalltalk-name-regexp ":")
  "A regular expression that matches a Smalltalk keyword")

(defvar smalltalk-name-chars "a-zA-Z0-9"
  "The collection of character that can compose a Smalltalk identifier")

(defvar smalltalk-whitespace " \t\n\f")

(defvar smalltalk-mode-abbrev-table nil
  "Abbrev table in use in smalltalk-mode buffers.")
(define-abbrev-table 'smalltalk-mode-abbrev-table ())

(defvar smalltalk-c-style-tab t
  "Non-nil means that tab reindents, M-tab tabs to next tab stop.
Nil has the opposite effect.  Examined only when loading. ")

;; copied from advice.el
(defconst smalltalk-emacs19-p
  (not (or (and (boundp 'epoch::version) epoch::version)
	   (string-lessp emacs-version "19"))))

;;; this hack was to play around with adding Smalltalk-specific menu items
;;; to the Emacstool on the Sun.
(if (featurep 'sun-mouse)
    (let (new-menu i)
      (defmenu smalltalk-menu
	("Smalltalk")
	("Do it"))
      (setq new-menu (make-vector (1+ (length emacs-menu)) nil))
      (aset new-menu 0 (aref emacs-menu 0))
      (setq i 1)
      (while (< i (length emacs-menu))
	(aset new-menu (1+ i) (aref emacs-menu i))
	(setq i (1+ i)))
      (aset new-menu 1 '("Smalltalk" . smalltalk-menu))
      (setq emacs-menu new-menu)
      )
  )



(defvar smalltalk-ctl-t-map nil
  "Keymap of subcommands of C-c C-t, tracing related commands")

(if smalltalk-ctl-t-map
    ()
  (setq smalltalk-ctl-t-map (make-sparse-keymap))
  (define-key smalltalk-ctl-t-map "\C-d" 'smalltalk-toggle-decl-tracing)
  (define-key smalltalk-ctl-t-map "\C-e" 'smalltalk-toggle-exec-tracing)
  (define-key smalltalk-ctl-t-map "\C-v" 'smalltalk-toggle-verbose-exec-tracing)
  )


(defvar smalltalk-template-map nil
  "Keymap of template creation keys")

(if smalltalk-template-map
    ()
  (setq smalltalk-template-map (make-sparse-keymap))
  (define-key smalltalk-template-map "p"	'smalltalk-private-template)
  (define-key smalltalk-template-map "c"	'smalltalk-class-template)
  )

;;; !!! move these
(defun smalltalk-private-template ()
  (interactive)
  (let (insert-point)
    (insert "!")
    (setq insert-point (point))
    (insert " methodsFor: 'private'!

! !
")
    (goto-char insert-point)
    )
  )

(defun smalltalk-class-template ()
  (interactive)
  (let (insert-point)
    (insert "!")
    (setq insert-point (point))
    (insert " class methodsFor: 'instance creation'!

! !
")
    (goto-char insert-point)
    )
     
  )



(defvar smalltalk-ctl-b-map nil
  "Keymap of subcommands of C-c C-b, browsing related commands")

(if smalltalk-ctl-b-map
    ()
  (setq smalltalk-ctl-b-map (make-sparse-keymap))
  ;; (define-key smalltalk-ctl-b-map "\C-i" 'smalltalk-show-implementors)
  (define-key smalltalk-ctl-b-map "\C-c" 'smalltalk-show-class-methods)
  (define-key smalltalk-ctl-b-map "\C-i" 'smalltalk-show-instance-methods)
  (define-key smalltalk-ctl-b-map "\C-d" 'smalltalk-show-direct-instance-methods)
  (define-key smalltalk-ctl-b-map "\C-h" 'smalltalk-browse-hierarchy)
  (define-key smalltalk-ctl-b-map "\C-o" 'smalltalk-get-class-names)

					; who implements method
					; what methods does a class/instance have
					; something about the class hierarchy
					; like direct subclasses
					; all subclasses
					; all superclasses

)





(defvar smalltalk-mode-map nil "Keymap used in Smalltalk mode.")
(if smalltalk-mode-map
    ()
  (setq smalltalk-mode-map (make-sparse-keymap))
  (if smalltalk-c-style-tab
      (progn
	(define-key smalltalk-mode-map "\M-\t" 'smalltalk-tab)
	(define-key smalltalk-mode-map "\t"	'smalltalk-reindent)
	)
    (define-key smalltalk-mode-map "\t" 'smalltalk-tab)
    (define-key smalltalk-mode-map "\M-\t"	'smalltalk-reindent)
    )
  (define-key smalltalk-mode-map "\177" 	'backward-delete-char-untabify)
  (define-key smalltalk-mode-map "\n" 		'smalltalk-newline-and-indent)
  (define-key smalltalk-mode-map "\C-\M-a" 	'smalltalk-begin-of-defun)
  (define-key smalltalk-mode-map "\C-\M-f" 	'smalltalk-forward-sexp)
  (define-key smalltalk-mode-map "\C-\M-b" 	'smalltalk-backward-sexp)
  (define-key smalltalk-mode-map "!" 		'smalltalk-bang)
  (define-key smalltalk-mode-map ":"		'smalltalk-colon)
  (define-key smalltalk-mode-map "\C-c\C-d" 	'smalltalk-category-definition)
  (define-key smalltalk-mode-map "\C-cc" 	'smalltalk-compile)
  (define-key smalltalk-mode-map "\C-cd" 	'smalltalk-doit)
  (define-key smalltalk-mode-map "\C-ce" 	'smalltalk-eval-region)
  (define-key smalltalk-mode-map "\C-cf" 	'smalltalk-filein)
  (define-key smalltalk-mode-map "\C-cm" 	'gst)
  (define-key smalltalk-mode-map "\C-cp" 	'smalltalk-print)
  (define-key smalltalk-mode-map "\C-cq" 	'smalltalk-quit)
  (define-key smalltalk-mode-map "\C-cr" 	'smalltalk-reeval-region)
  (define-key smalltalk-mode-map "\C-cs" 	'smalltalk-snapshot)
  (define-key smalltalk-mode-map "\C-ct" 	smalltalk-template-map)
  (define-key smalltalk-mode-map "\C-c\C-s" 	'smalltalk-browse-selectors)
  (define-key smalltalk-mode-map "\C-c\C-t" 	smalltalk-ctl-t-map)
  (define-key smalltalk-mode-map "\C-c\C-b" 	smalltalk-ctl-b-map)
  ;; experimental
  (define-key smalltalk-mode-map "\C-xc"	'smalltalk-complete-class)
  )

(defvar smalltalk-mode-syntax-table nil
  "Syntax table in use in smalltalk-mode buffers.")

(if smalltalk-mode-syntax-table
    ()
  (setq smalltalk-mode-syntax-table (make-syntax-table))
  (modify-syntax-entry ?\' "\"" smalltalk-mode-syntax-table)
  ;; GNU Emacs is deficient: there seems to be no way to have a comment char
  ;; that is both the start and end character.  This is going to cause
  ;; me great pain.
  (modify-syntax-entry ?\" "\"" smalltalk-mode-syntax-table)
  (modify-syntax-entry ?+ "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?- "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?* "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?/ "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?= "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?% "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?< "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?> "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?& "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?$ "\\" smalltalk-mode-syntax-table)
  (modify-syntax-entry ?# "'" smalltalk-mode-syntax-table)
  (modify-syntax-entry ?| "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?_ "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?\\ "." smalltalk-mode-syntax-table)
  (modify-syntax-entry ?! "." smalltalk-mode-syntax-table)
  )

(defconst smalltalk-indent-amount 4
  "*'Tab size'; used for simple indentation alignment.")

(defvar smalltalk-eval-data nil)


(autoload 'smalltalk-install-change-log-functions "st-changelog.el")

(defun stm ()
  (smalltalk-mode))

(defun smalltalk-mode ()
  "Major mode for editing Smalltalk code.
Comments are delimited with \" ... \".
Paragraphs are separated by blank lines only.
Delete converts tabs to spaces as it moves back.

Of special interest are the commands for interacting with a live Smalltalk
session:
\\[gst]
    Invoke the Smalltalk interactor, which basically keeps the current buffer
    in one window, and creates another window with a running Smalltalk in it.
    The other window behaves essentially like a shell-mode window when the
    cursor is in it, but it will receive the operations requested when the
    interactor related commands are used.

\\[smalltalk-doit]
    interactively evaluate the expression that the cursor is in in a Smalltalk
    mode window, or with an argument execute the region as smalltalk code

\\[smalltalk-compile]
    compile the method definition that the cursor is currently in.

\\[smalltalk-snapshot]
    produce a snapshot binary image of the current working Smalltalk system.
    Useful to do periodically as you define new methods to save the state of
    your work.

\\{smalltalk-mode-map}

Turning on Smalltalk mode calls the value of the variable
smalltalk-mode-hook with no args, if that value is non-nil."
  (interactive)
  (kill-all-local-variables)
  (setq major-mode 'smalltalk-mode)
  (setq mode-name "Smalltalk")
  (use-local-map smalltalk-mode-map)
  (setq local-abbrev-table smalltalk-mode-abbrev-table)
  (set-syntax-table smalltalk-mode-syntax-table)
  (make-local-variable 'paragraph-start)
  (setq paragraph-start (concat "^$\\|" page-delimiter))
  (make-local-variable 'paragraph-separate)
  (setq paragraph-separate paragraph-start)
  (make-local-variable 'paragraph-ignore-fill-prefix)
  (setq paragraph-ignore-fill-prefix t)
  (make-local-variable 'indent-line-function)
  (setq indent-line-function 'smalltalk-indent-line)
  (make-local-variable 'require-final-newline)
  (setq require-final-newline t)
  (make-local-variable 'comment-start)
  (setq comment-start "\"")
  (make-local-variable 'comment-end)
  (setq comment-end "\"")
  (make-local-variable 'comment-column)
  (setq comment-column 32)
  (make-local-variable 'comment-start-skip)
  (setq comment-start-skip "\" *")
  (make-local-variable 'comment-indent-hook)
  (setq comment-indent-hook 'smalltalk-comment-indent)
  (make-local-variable 'parse-sexp-ignore-comments)
  (setq parse-sexp-ignore-comments nil)	;for interactive f-b sexp
  (smalltalk-install-change-log-functions)
  (run-hooks 'smalltalk-mode-hook))

;; This is used by indent-for-comment
;; to decide how much to indent a comment in Smalltalk code
;; based on its context.
(defun smalltalk-comment-indent ()
  (if (looking-at "^\"")
      0				;Existing comment at bol stays there.
    (save-excursion
      (skip-chars-backward " \t")
      (max (1+ (current-column))	;Else indent at comment column
	   comment-column))))	; except leave at least one space.

(defun smalltalk-indent-line ()
  (let (indent-amount is-keyword)
    (save-excursion
      (beginning-of-line)
      (smalltalk-forward-whitespace)
      (if (looking-at "[a-zA-Z][a-zA-Z0-9]*:") ;indent for colon
	  (let ((parse-sexp-ignore-comments t))
	    (beginning-of-line)
	    (smalltalk-backward-whitespace)
	    (if (not (memq (preceding-char) '(?\;)))
		(setq is-keyword t)
	      )
	    )
	)
      )
    (if is-keyword
	(smalltalk-indent-for-colon)
      (setq indent-amount (calculate-smalltalk-indent))
      (smalltalk-indent-to-column indent-amount)
      )
    )
  )

(defun calculate-smalltalk-indent ()
  (let (needs-indent indent-amount done c state start-of-line
		     (parse-sexp-ignore-comments t))
    (save-excursion
      (save-restriction
	(widen)
	(narrow-to-region (point-min) (point)) ;only care about what's before
	(setq state (parse-partial-sexp (point-min) (point)))
	(cond ((equal (nth 3 state) ?\") ;in a comment
	       (save-excursion
		 (smalltalk-backward-comment)
		 (setq indent-amount (1+ (current-column)))
		 ))
	      ((equal (nth 3 state) ?')	;in a string
	       (setq indent-amount 0))
	      (t
	       (save-excursion
		 (smalltalk-backward-whitespace)
		 (if (or (bobp)
			 (= (preceding-char) ?!))
		     (setq indent-amount 0))
		 )
	       ))
	(if (null indent-amount)
	    (progn
	      (smalltalk-narrow-to-method)
	      (beginning-of-line)
	      (setq state (parse-partial-sexp (point-min) (point)))
	      (narrow-to-paren state)
	      (smalltalk-backward-whitespace)
	      (cond ((bobp)		;must be first statment in block or exp
		     (if (nth 1 state)	;we're in a paren exp
			 (setq indent-amount (smalltalk-current-column))
		       ;; we're top level
		       (setq indent-amount smalltalk-indent-amount)))
		    ((= (preceding-char) ?.) ;at end of statement
		     (smalltalk-find-statement-begin)
		     (setq indent-amount (smalltalk-current-column)))
		    ((= (preceding-char) ?:)
		     (beginning-of-line)
		     (smalltalk-forward-whitespace)
		     (setq indent-amount (+ (smalltalk-current-column)
					    smalltalk-indent-amount))
		     )
		    ((= (preceding-char) ?>) ;maybe <primitive: xxx>
		     (setq orig (point))
		     (backward-char 1)
		     (smalltalk-backward-whitespace)
		     (skip-chars-backward "0-9")
		     (smalltalk-backward-whitespace)
		     (if (= (preceding-char) ?:)
			 (progn
			   (backward-char 1)
			   (skip-chars-backward "a-zA-Z")
			   (if (looking-at "primitive:")
			       (progn
				 (smalltalk-backward-whitespace)
				 (if (= (preceding-char) ?<)
				     (setq indent-amount (1- (smalltalk-current-column))))
				 )
			     )
			   )
		       )
		     (if (null indent-amount)
			 (progn
			   (goto-char orig)
			   (smalltalk-find-statement-begin)
			   (setq indent-amount (+ (smalltalk-current-column)
						  smalltalk-indent-amount))
			   )
		       )
		     )
		    (t			;must be a statement continuation
		     (save-excursion
		       (beginning-of-line)
		       (setq start-of-line (point)))
		     (smalltalk-find-statement-begin)
		     (setq indent-amount (+ (smalltalk-current-column)
					    smalltalk-indent-amount))
		     )
		    )
	      )
	  )
	indent-amount)
      )
    )
  )


(defun smalltalk-previous-nonblank-line ()
  (forward-line -1)
  (while (and (not (bobp))
	      (looking-at "^[ \t]*$"))
    (forward-line -1))
  )

(defun smalltalk-tab ()
  (interactive)
  (let (col)
    ;; round up, with overflow
    (setq col (* (/ (+ (current-column) smalltalk-indent-amount)
		    smalltalk-indent-amount)
		 smalltalk-indent-amount))
    (indent-to-column col)
  ))

(defun smalltalk-begin-of-defun ()
  "Skips to the beginning of the current method.  If already at
the beginning of a method, skips to the beginning of the previous
one."
  (interactive)
  (let ((parse-sexp-ignore-comments t) here delim start)
    (setq here (point))
    (while (and (search-backward "!" nil 'to-end)
		(setq delim (smalltalk-in-string)))
      (search-backward delim))
    (setq start (point))
    (if (looking-at "!")
	(forward-char 1))
    (smalltalk-forward-whitespace)
    ;; check to see if we were already at the start of a method
    ;; in which case, the semantics are to go to the one preceeding
    ;; this one
    (if (and (= here (point))
	     (/= start (point-min)))
	(progn
	  (goto-char start)
	  (smalltalk-backward-whitespace) ;may be at ! "foo" !
	  (if (= (preceding-char) ?!)
	      (backward-char 1))
	  (smalltalk-begin-of-defun)	;and go to the next one
	  )
      )
    )
  )

(defun smalltalk-in-string ()
  "Returns non-nil delimiter as a string if the current location is
actually inside a string or string like context."
  (let (state)
    (setq state (parse-partial-sexp (point-min) (point)))
    (and (nth 3 state)
	 (char-to-string (nth 3 state)))
    )
  )



(defun smalltalk-forward-whitespace ()
  "Skip white space and comments forward, stopping at end of buffer
or non-white space, non-comment character"
  (while (looking-at (concat "[" smalltalk-whitespace "\"]"))
    (skip-chars-forward smalltalk-whitespace)
    (if (= (following-char) ?\")
	(forward-sexp 1)))
  )

(defun smalltalk-backward-whitespace ()
  "Like forward whitespace only going towards the start of the buffer"
  (while (progn (skip-chars-backward smalltalk-whitespace)
		(= (preceding-char) ?\"))
    (backward-sexp 1))
  )

(defun smalltalk-forward-sexp (n)
  (interactive "p")
  (let (i)
    (cond ((< n 0)
	   (smalltalk-backward-sexp (- n)))
	  ((null parse-sexp-ignore-comments)
	   (forward-sexp n))
	  (t
	   (while (> n 0)
	     (smalltalk-forward-whitespace)
	     (forward-sexp 1)
	     (setq n (1- n))
	     )
	   )
	  )
    )
  )

(defun smalltalk-backward-sexp (n)
  (interactive "p")
  (let (i)
    (cond ((< n 0)
	   (smalltalk-forward-sexp (- n)))
	  ((null parse-sexp-ignore-comments)
	   (backward-sexp n))
	  (t
	   (while (> n 0)
	     (smalltalk-backward-whitespace)
	     (backward-sexp 1)
	     (setq n (1- n))
	     )
	  )))
  )

(defun smalltalk-reindent ()
  (interactive)
  ;; +++ Still loses if at first charcter on line
  (smalltalk-indent-line)
;  (let ((pos (- (point-max) (point))))
;    (beginning-of-line)
;    (delete-horizontal-space)
;    (delete-char -1)
;    (smalltalk-newline-and-indent 1)
;    (goto-char (- (point-max) pos))
;    (if (looking-at "[\t ]*$")
;	(end-of-line))
;    )
  )

(defun smalltalk-newline-and-indent (levels)
  "Called basically to do newline and indent.  Sees if the current line is a
new statement, in which case the indentation is the same as the previous
statement (if there is one), or is determined by context; or, if the current
line is not the start of a new statement, in which case the start of the
previous line is used, except if that is the start of a new line in which case
it indents by smalltalk-indent-amount."
  (interactive "p")
  (newline)
  (smalltalk-indent-line)
  )

;;;(defun smalltalk-newline-and-indent (levels)
;;;  "Called basically to do newline and indent.  Sees if the current line is a
;;;new statement, in which case the indentation is the same as the previous
;;;statement (if there is one), or is determined by context; or, if the current
;;;line is not the start of a new statement, in which case the start of the
;;;previous line is used, except if that is the start of a new line in which case
;;;it indents by smalltalk-indent-amount."
;;;  (interactive "p")
;;;  (let (needs-indent indent-amount done c state start-of-line
;;;		     (parse-sexp-ignore-comments t))
;;;    (save-excursion
;;;      (save-restriction
;;;	(save-excursion
;;;	  (smalltalk-backward-whitespace)
;;;	  (if (or (bobp)
;;;		  (= (preceding-char) ?!))
;;;	      (setq indent-amount 0))
;;;	  )
;;;	(if (null indent-amount)
;;;	    (progn
;;;	      (smalltalk-narrow-to-method)
;;;	      (setq state (parse-partial-sexp (point-min) (point)))
;;;	      (if (nth 3 state)		;in a string or comment
;;;		  (cond ((= (nth 3 state) ?\") ;in a comment
;;;			 (save-excursion
;;;			   (smalltalk-backward-comment)
;;;			   (setq indent-amount (1+ (current-column)))
;;;			   ))
;;;			((= (nth 3 state) ?')	;in a string
;;;			 (setq indent-amount 0))
;;;			)
;;;		(narrow-to-paren state)
;;;		(smalltalk-backward-whitespace)
;;;		(cond ((bobp)			;must be first statment in block or exp
;;;		       (if (nth 1 state)	;we're in a paren exp
;;;			   (setq indent-amount (smalltalk-current-column))
;;;			 ;; we're top level
;;;			 (setq indent-amount smalltalk-indent-amount)))
;;;		      ((= (preceding-char) ?.) ;at end of statement
;;;		       (smalltalk-find-statement-begin)
;;;		       (setq indent-amount (smalltalk-current-column)))
;;;		      ((= (preceding-char) ?:)
;;;		       (beginning-of-line)
;;;		       (smalltalk-forward-whitespace)
;;;		       (setq indent-amount (+ (smalltalk-current-column)
;;;					      smalltalk-indent-amount))
;;;		       )
;;;		      ((= (preceding-char) ?>) ;maybe <primitive: xxx>
;;;		       (setq orig (point))
;;;		       (backward-char 1)
;;;		       (smalltalk-backward-whitespace)
;;;		       (skip-chars-backward "0-9")
;;;		       (smalltalk-backward-whitespace)
;;;		       (if (= (preceding-char) ?:)
;;;			   (progn
;;;			     (backward-char 1)
;;;			     (skip-chars-backward "a-zA-Z")
;;;			     (if (looking-at "primitive:")
;;;				 (progn
;;;				   (smalltalk-backward-whitespace)
;;;				   (if (= (preceding-char) ?<)
;;;				       (setq indent-amount (1- (smalltalk-current-column))))
;;;				   )
;;;			       )
;;;			     )
;;;			 )
;;;		       (if (null indent-amount)
;;;			   (progn
;;;			     (goto-char orig)
;;;			     (smalltalk-find-statement-begin)
;;;			     (setq indent-amount (+ (smalltalk-current-column)
;;;						      smalltalk-indent-amount))
;;;			     )
;;;			 )
;;;		       )
;;;		      (t			;must be a statement continuation
;;;		       (save-excursion
;;;			 (beginning-of-line)
;;;			 (setq start-of-line (point)))
;;;		       (smalltalk-find-statement-begin)
;;;		       (setq indent-amount (+ (smalltalk-current-column)
;;;					      smalltalk-indent-amount))
;;;		       )
;;;		      )
;;;		)
;;;	      ))
;;;	)
;;;      )
;;;    (newline)
;;;    (delete-horizontal-space)		;remove any carried-along whites
;;;    (indent-to indent-amount)
;;;    (if (looking-at "[a-zA-Z][a-zA-Z0-9]*:") ;indent for colon
;;;	(save-excursion
;;;	  (goto-char (1- (match-end 0)))
;;;	  (smalltalk-indent-for-colon))
;;;	)
;;;    ))

(defun smalltalk-current-column ()
  "Returns the current column of the given line, regardless of narrowed buffer."
  (save-restriction
    (widen)
    (current-column)			;this changed in 18.56
    )
  )

(defun smalltalk-find-statement-begin ()
  "Leaves the point at the first non-blank, non-comment character of a new
statement.  If begininning of buffer is reached, then the point is left there.
This routine only will return with the point pointing at the first non-blank
on a line; it won't be fooled by multiple statements on a line into stopping
prematurely.  Also, goes to start of method if we started in the method
selector."
  (let (start ch)
    (if (= (preceding-char) ?.)		;if we start at eos
	(backward-char 1))		;we find the begin of THAT stmt
    (while (and (null start) (not (bobp)))
      (smalltalk-backward-whitespace)
      (cond ((= (setq ch (preceding-char)) ?.)
	     (let (saved-point)
	       (setq saved-point (point))
	       (smalltalk-forward-whitespace)
	       (if (smalltalk-white-to-bolp)
		   (setq start (point))
		 (goto-char saved-point)
		 (smalltalk-backward-sexp 1))
	       ))
	    ((= ch ?^)			;HACK -- presuming that when we back
					;up into a return that we're at the
					;start of a statement
	     (backward-char 1)
	     (setq start (point))
	     )
	    ((= ch ?!)
	     (smalltalk-forward-whitespace)
	     (setq start (point))
	     )
	    (t
	     (smalltalk-backward-sexp 1)
	     )
	    )
      )
    (if (null start)
      (progn
	(goto-char (point-min))
	(smalltalk-forward-whitespace)
	(setq start (point))))
  start))


;;; hold on to this code for a little bit, but then flush it
;;;
;;;	  ;; not in a comment, so skip backwards for some indication
;;;	  (smalltalk-backward-whitespace)
;;;	  (if (bobp)
;;;	      (setq indent-amount smalltalk-indent-amount)
;;;	    (setq c (preceding-char))
;;;	    (cond ((eq c ?.)		;this is a new statement
;;;		   (smalltalk-backward-statement)
;;;		   (setq indent-amount (current-column)))
;;;		  ((memq c '(?|
;;;
;;;			     (smalltalk-narrow-to-method)
;;;
;;;			     (smalltalk-backward-whitespace)
;;;			     (setq c (preceding-char))
;;;			     (cond
;;;			      ((memq c '(?. ?| ?\[ ?\( )) (setq done t))
;;;			      ((eq c ?:)
;;;			       (backward-char 1)
;;;			       (skip-chars-backward "a-zA-Z0-9")
;;;			       (setq indent-amount (current-column)))
;;;			      (t
;;;			       (smalltalk-backward-sexp 1)))
;;;			     )
;;;
;;;			 )
;;;		   )
;;;		  (if indent-amount
;;;		      (save-excursion
;;;			(beginning-of-line)
;;;			(delete-horizontal-space)
;;;			(indent-to indent-amount))
;;;		    )
;;;		  (insert last-command-char)
;;;		  ))

(defun narrow-to-paren (state)
  "Narrows the region to between point and the closest previous open paren.
Actually, skips over any block parameters, and skips over the whitespace
following on the same line."
  (let ((paren-addr (nth 1 state))
	start c done)
    (if (not paren-addr) nil
      (save-excursion
	(goto-char paren-addr)
	(setq c (following-char))
	(cond ((eq c ?\()
	       (setq start (1+ (point))))
	      ((eq c ?\[)
	       (setq done nil)
	       (forward-char 1)
	       (while (not done)
		 (skip-chars-forward " \t")
		 (setq c (following-char))
		 (cond ((eq c ?:)
			(smalltalk-forward-sexp 1))
		       ((eq c ?|)
			(forward-char 1) ;skip vbar
			(skip-chars-forward " \t")
			(setq done t))	;and leave
		       (t
			(setq done t))
		       )
		 )
	       (setq start (point))
	       )
	      )
	)
      (narrow-to-region start (point))
      )
    )
  )


(defun smalltalk-at-method-begin ()
  "Returns T if at the beginning of a method definition, otherwise nil"
  (let ((parse-sexp-ignore-comments t))
    (if (bolp)
	(save-excursion
	  (smalltalk-backward-whitespace)
	  (= (preceding-char) ?!)
	  )
      )
    )
  )
	
  


(defun smalltalk-colon ()
  "Possibly reindents a line when a colon is typed.
If the colon appears on a keyword that's at the start of the line (ignoring
whitespace, of course), then the previous line is examined to see if there
is a colon on that line, in which case this colon should be aligned with the
left most character of that keyword.  This function is not fooled by nested
expressions."
  (interactive)
  (let (needs-indent (parse-sexp-ignore-comments t))
    (setq state (parse-partial-sexp (point-min) (point)))

    (if (null (nth 3 state))		;we're not in string or comment
	(progn
	  (save-excursion
      
	    (skip-chars-backward "A-Za-z0-9")
	    (if (and (looking-at smalltalk-name-regexp)
		     (not (smalltalk-at-method-begin)))
		(setq needs-indent (smalltalk-white-to-bolp))
	      )
	    )
	  (and needs-indent
	       (smalltalk-indent-for-colon))
	  )
      )
    ;; out temporarily
    ;;    (expand-abbrev)			;I don't think this is the "correct"
    ;;					;way to do this...I suspect that
    ;;					;some flavor of "call interactively"
    ;;					;is better.
    (self-insert-command 1)
    )
  )


(defun smalltalk-indent-for-colon ()
  (let (indent-amount c start-line state done default-amount
		     (parse-sexp-ignore-comments t))
    ;; we're called only for lines which look like "<whitespace>foo:"
    (save-excursion
      (save-restriction
	(widen)
	(smalltalk-narrow-to-method)
	(beginning-of-line)
	(setq state (parse-partial-sexp (point-min) (point)))
	(narrow-to-paren state)
	(narrow-to-region (point-min) (point))
	(setq start-line (point))
	(smalltalk-backward-whitespace)
	(cond
	 ((bobp)
	  (setq indent-amount (smalltalk-current-column)))
	 ((eq (setq c (preceding-char)) ?\;)	; cascade before, treat as stmt continuation
	  (smalltalk-find-statement-begin)
	  (setq indent-amount (+ (smalltalk-current-column)
				 smalltalk-indent-amount)))
	 ((eq c ?.)	; stmt end, indent like it (syntax error here?)
	  (smalltalk-find-statement-begin)
	  (setq indent-amount (smalltalk-current-column)))
	 (t				;could be a winner
	    (smalltalk-find-statement-begin)
	    ;; we know that since we weren't at bobp above after backing
	    ;; up over white space, and we didn't run into a ., we aren't
	    ;; at the beginning of a statement, so the default indentation
	    ;; is one level from statement begin
	    (setq default-amount
		  (+ (smalltalk-current-column) ;just in case
		     smalltalk-indent-amount))
	    ;; might be at the beginning of a method (the selector), decide
	    ;; this here
	    (if (not (looking-at smalltalk-keyword-regexp ))
		;; not a method selector
		(while (and (not done) (not (eobp)))
		  (smalltalk-forward-sexp 1) ;skip over receiver
		  (smalltalk-forward-whitespace)
		  (cond ((eq (following-char) ?\;)
			 (setq done t)
			 (setq indent-amount default-amount))
			((and (null indent-amount) ;pick up only first one
			      (looking-at smalltalk-keyword-regexp))
			 (setq indent-amount (smalltalk-current-column))
			 )
			) 
		  )
	      )
	    (and (null indent-amount)
		 (setq indent-amount default-amount))
	    )
	 )
	)
      )
    (if indent-amount
	(smalltalk-indent-to-column indent-amount))
    )
  )

(defun smalltalk-indent-to-column (col)
  (save-excursion
    (beginning-of-line)
    (delete-horizontal-space)
    (indent-to col)
    )
  (if (bolp)
      ;;delete horiz space may have moved us to bol instead of staying where
      ;; we were.  this fixes it up.
      (move-to-column col))
  )

(defun smalltalk-narrow-to-method ()
  "Narrows the buffer to the contents of the method, exclusive of the
method selector and temporaries."
  (let ((end (point))
	(parse-sexp-ignore-comments t)
	done handled)
    (save-excursion
      (smalltalk-begin-of-defun)
      (if (looking-at "[a-zA-z]")	;either unary or keyword msg
	  ;; or maybe an immediate expression...
	  (progn
	    (forward-sexp)
	    (if (= (following-char) ?:) ;keyword selector
		(progn			;parse full keyword selector
		  (backward-sexp 1)	;setup for common code
		  (smalltalk-forward-keyword-selector)
		  )
	      ;; else maybe just a unary selector or maybe not
	      ;; see if there's stuff following this guy on the same line
	      (let (here eol-point)
		(setq here (point))
		(end-of-line)
		(setq eol-point (point))
		(goto-char here)
		(smalltalk-forward-whitespace)
		(if (< (point) eol-point) ;if there is, we're not a method
					; (a heuristic guess)
		    (beginning-of-line)
		  (goto-char here)	;else we're a unary method (guess)
		  )
		)
	      )
	    )

	;; this must be a binary selector, or a temporary
	(if (= (following-char) ?|)
	    (progn			;could be temporary
	      (end-of-line)
	      (smalltalk-backward-whitespace)
	      (if (= (preceding-char) ?|)
		  (progn
		    (setq handled t))
		)
	      (beginning-of-line)
	      )
	  )
	(if (not handled)
	    (progn
	      (skip-chars-forward (concat "^" smalltalk-whitespace))
	      (smalltalk-forward-whitespace)
	      (skip-chars-forward smalltalk-name-chars)) ;skip over operand
	  )
	)
      (smalltalk-forward-whitespace)
      ;;sbb  6-Sep-93 14:58:54 attempted fix(skip-chars-forward smalltalk-whitespace)
      (if (= (following-char) ?|)	;scan for temporaries
	  (progn
	    (forward-char)		;skip over |
	    (smalltalk-forward-whitespace)
	    (while (and (not (eobp))
			(looking-at "[a-zA-Z]"))
	      (skip-chars-forward smalltalk-name-chars)
	      (smalltalk-forward-whitespace)
	      )
	    (if (and (= (following-char) ?|) ;only if a matching | as a temp
		     (< (point) end))	;and we're after the temps
		(narrow-to-region (1+ (point)) end) ;do we limit the buffer
	      )
	    )
	;; added "and <..." Dec 29 1991 as a test
	(and (< (point) end)
	     (narrow-to-region (point) end))
	)
      )
    )
  )

(defun smalltalk-forward-keyword-selector ()
  "Starting on a keyword, this function skips forward over a keyword selector.
It is typically used to skip over the actual selector for a method."
  (let (done)
    (while (not done)
      (if (not (looking-at "[a-zA-Z]"))
	  (setq done t)
	(skip-chars-forward smalltalk-name-chars)
	(if (= (following-char) ?:)
	    (progn
	      (forward-char)
	      (smalltalk-forward-sexp 1)
	      (smalltalk-forward-whitespace))
	  (setq done t)
	  (backward-sexp 1))
	)
      )
    )
  )


(defun smalltalk-white-to-bolp ()
  "Returns T if from the current position to beginning of line is whitespace.
Whitespace is defined as spaces, tabs, and comments."
  (let (done is-white line-start-pos)
    (save-excursion
      (save-excursion
	(beginning-of-line)
	(setq line-start-pos (point)))
      (while (not done)
	(and (not (bolp))
	     (skip-chars-backward " \t"))
	(cond ((bolp)
	       (setq done t)
	       (setq is-white t))
	      ((= (char-after (1- (point))) ?\")
	       (backward-sexp)
	       (if (< (point) line-start-pos) ;comment is multi line
		   (setq done t)
		 )
	       )
	      (t
	       (setq done t))
	      )
	)
      is-white)
    ))


(defun smalltalk-bang ()
  (interactive)
  (insert "!")
  (save-excursion
    (beginning-of-line)
    (if (looking-at "^[ \t]+!")
	(delete-horizontal-space))
    )
  )


(defun smalltalk-backward-comment ()
  (search-backward "\"")		;find its start
  (while (= (preceding-char) ?\")	;skip over doubled ones
    (backward-char 1)
    (search-backward "\""))
  )


;;;(defun smalltalk-collect-selector ()
;;;  "Point is stationed inside or at the beginning of the selector in question.
;;;This function computes the Smalltalk selector (unary, binary, or keyword) and
;;;returns it as a string.  Point is not changed."
;;;  (save-excursion
;;;    (let (start selector done
;;;		(parse-sexp-ignore-comments t))
;;;      (skip-chars-backward (concat "^" "\"" smalltalk-whitespace))
;;;      (setq start (point))
;;;      (if (looking-at smalltalk-name-regexp)
;;;	  (progn			;maybe unary, maybe keyword
;;;	    (skip-chars-forward smalltalk-name-chars)
;;;	    (if (= (following-char) ?:)	;keyword?
;;;		(progn
;;;		  (forward-char 1)
;;;		  (setq selector (buffer-substring start (point)))
;;;		  (smalltalk-forward-sexp 1)
;;;		  (smalltalk-forward-whitespace)
;;;		  (while (not done)
;;;		    (if (not (looking-at smalltalk-name-regexp))
;;;			(setq done t)
;;;		      (setq start (point))
;;;		      (skip-chars-forward smalltalk-name-chars)
;;;		      (if (= (following-char) ?:)
;;;			  (progn
;;;			    (forward-char)
;;;			    (setq selector (concat selector
;;;						   (buffer-substring
;;;						    start (point))))
;;;			    (smalltalk-forward-sexp 1)
;;;			    (smalltalk-forward-whitespace))
;;;			(setq done t))
;;;		      )
;;;		    )
;;;		  )
;;;	      (setq selector (buffer-substring start (point)))
;;;	      )
;;;	    )
;;;	(skip-chars-forward (concat "^" ?\" smalltalk-whitespace))
;;;	(setq selector (buffer-substring start (point)))
;;;	)
;;;      selector
;;;      )
;;;    )
;;;  )

(defun smalltalk-collect-selector ()
  "Point is stationed inside or at the beginning of the selector in question.
This function computes the Smalltalk selector (unary, binary, or keyword) and
returns it as a string.  Point is not changed."
  (save-excursion
    (let (start selector done ch
		(parse-sexp-ignore-comments t))
      (skip-chars-backward (concat "^" "\"" smalltalk-whitespace))
      (setq start (point))
      (if (looking-at smalltalk-name-regexp)
	  (progn			;maybe unary, maybe keyword
	    (skip-chars-forward smalltalk-name-chars)
	    (if (= (following-char) ?:)	;keyword?
		(progn
		  (forward-char 1)
		  (setq selector (buffer-substring start (point)))
		  (setq start (point))
		  (while (not done)
		    (smalltalk-forward-whitespace)
		    (setq ch (following-char))
		    (cond ((memq ch '(?\; ?. ?\] ?\) ?! ))
			   (setq done t))
			  ((= ch ?:)
			   (forward-char 1)
			   (setq selector
				 (concat selector
					 (buffer-substring start (point))))
			   )
			  (t
			   (setq start (point))
			   (smalltalk-forward-sexp 1))
			  )
		    )
		  )
	      (setq selector (buffer-substring start (point)))
	      )
	    )
	(skip-chars-forward (concat "^" ?\" smalltalk-whitespace))
	(setq selector (buffer-substring start (point)))
	)
      selector
      )
    )
  )


(defun smalltalk-category-definition (class-name category-name)
  (interactive
   (list (read-string "Class: " (smalltalk-backward-find-class-name))
	 (read-string "Category: ")))
  (insert (format "!%s methodsFor: '%s'!\n\n" class-name category-name))
  (save-excursion
    (insert "\n! !\n"))
  )
	 
(defun smalltalk-backward-find-class-name ()
  (let (first-hit-point first-hit second-hit-point second-hit)
    (save-excursion
      (if (setq first-hit-point
		(search-backward-regexp "^![ \t]*\\(\\w+\\)[ \t]+" nil t))
	  (setq first-hit (buffer-substring (match-beginning 1) (match-end 1)))
	)
      )
    (save-excursion
      (if (setq second-hit-point
		(search-backward-regexp
		 "^\\w+[ \t]+\\(variable\\|variableWord\\|variableByte\\)?subclass:[ \t]+#\\(\\w+\\)" nil t))
	  (setq second-hit (buffer-substring
			    (match-beginning 2)
			    (match-end 2)))
	)
      )
    (if first-hit-point
	(if (and second-hit-point (> second-hit-point first-hit-point))
	    second-hit
	  first-hit)
      (or second-hit ""))
    )
  )

(defun st-test ()			;just an experimental testing harness
  (interactive)
  (let (l end)
    (setq end (point))
    (beginning-of-defun)
    (setq l (parse-partial-sexp (point) end nil nil nil))
    (message "%s" (prin1-to-string l)) (read-char)
    (message "depth %s" (nth 1 l)) (goto-char (nth 1 l)) (read-char)
    (message "last sexp %s" (nth 2 l)) (goto-char (nth 2 l)) (read-char)
    (message "lstsx %s stp %s com %s quo %s pdep %s"
	     (nth 3 l)
	     (nth 4 l)
	     (nth 5 l)
	     (nth 6 l)
	     (nth 7 l))
    ))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; GNU Emacs Smalltalk interactor mode
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defvar *smalltalk-process* nil)
(defvar gst-args '("-Vp"))

(defvar smalltalk-classes
  nil "The set of class names...used for completion")

(defvar smalltalk-command-string nil
  "Non nil means that we're accumulating output from Smalltalk")



(defvar smalltalk-interactor-mode-map nil "Keymap used in Smalltalk interactor mode.")



(if smalltalk-interactor-mode-map
    ()
  (setq smalltalk-interactor-mode-map (copy-keymap smalltalk-mode-map))
  (if smalltalk-emacs19-p
      (progn
	(define-key smalltalk-interactor-mode-map "\C-m" 'comint-send-input)
	(define-key smalltalk-interactor-mode-map "\C-c\C-d" 'comint-delchar-or-maybe-eof)
	(define-key smalltalk-interactor-mode-map "\C-c\C-u" 'comint-kill-input)

  

	(define-key smalltalk-interactor-mode-map "\C-c\C-c" 'comint-interrupt-subjob)
	(define-key smalltalk-interactor-mode-map "\C-c\C-z" 'comint-stop-subjob)
	(define-key smalltalk-interactor-mode-map "\C-c\C-\\" 'comint-quit--subjob)
	(define-key smalltalk-interactor-mode-map "\C-c\C-o" 'comint-kill-output)
	(define-key smalltalk-interactor-mode-map "\C-c\C-r" 'comint-show-output)
	)
    (define-key smalltalk-interactor-mode-map "\C-c\C-d" 'shell-send-eof)
    (define-key smalltalk-interactor-mode-map "\C-m" 'shell-send-input)
    (define-key smalltalk-interactor-mode-map "\C-c\C-u" 'kill-shell-input)
    (define-key smalltalk-interactor-mode-map "\C-c\C-c" 'interrupt-shell-subjob)
    (define-key smalltalk-interactor-mode-map "\C-c\C-z" 'stop-shell-subjob)
    (define-key smalltalk-interactor-mode-map "\C-c\C-\\" 'quit-shell-subjob)
    (define-key smalltalk-interactor-mode-map "\C-c\C-o" 'kill-output-from-shell)
    (define-key smalltalk-interactor-mode-map "\C-c\C-r" 'show-output-from-shell)
    (define-key smalltalk-interactor-mode-map "\C-c\C-y" 'copy-last-shell-input)
    )
  )





(defun gst (args)
  (interactive (list (if (null current-prefix-arg)
			 gst-args
		       (read-smalltalk-args))))
  (setq gst-args args)
  (if (not (eq major-mode 'mst-mode))
      (switch-to-buffer-other-window
       (apply 'make-gst "gst" gst-args))
    ;; invoked from a Smalltalk interactor window, so stay there
    (apply 'make-gst "gst" gst-args)
    )
  (setq *smalltalk-process* (get-buffer-process (current-buffer)))
  )

(defun read-smalltalk-args ()
  "Reads the arguments to pass to Smalltalk as a string, returns a list."
  (let (str args args-str result-args start end)
    (setq args gst-args)
    (setq args-str "")
    (while args
      (setq args-str (concat args-str " " (car args)))
      (setq args (cdr args))
      )
    (setq str (read-string "Invoke Smalltalk: " args-str))
    
    (while (setq start (string-match "[^ ]" str))
      (setq end (or (string-match " " str start) (length str)))
      (setq result-args (cons (substring str start end) result-args))
      (setq str (substring str end))
      )
    (reverse result-args)
    )
  )


(defun make-gst (name &rest switches)
  (let ((buffer (get-buffer-create (concat "*" name "*")))
	proc status size)
    (setq proc (get-buffer-process buffer))
    (if proc (setq status (process-status proc)))
    (save-excursion
      (set-buffer buffer)
      ;;    (setq size (buffer-size))
      (if (memq status '(run stop))
	  nil
	(if proc (delete-process proc))
	(setq proc (apply  'start-process
			   name buffer
			   "env"
			   ;;(concat exec-directory "env")
			   ;; I'm choosing to leave these here
			   ;;"-"
			   (format "TERMCAP=emacs:co#%d:tc=unknown:"
				   (screen-width))
			   "TERM=emacs"
			   "EMACS=t"
			   "gst"
			   switches))
	(setq name (process-name proc)))
      (goto-char (point-max))
      (set-marker (process-mark proc) (point))
      (set-process-filter proc 'gst-filter)
      (gst-mode))
    buffer))

(defun gst-filter (process string)
  "Make sure that the window continues to show the most recently output
text."
  (let (where ch command-str)
    (setq where 0)			;fake to get through the gate
    (while (and string where)
      (if smalltalk-command-string
	  (setq string (smalltalk-accum-command string)))
      (if (and string
	       (setq where (string-match "\C-a\\|\C-b" string)))
	  (progn
	    (setq ch (aref string where))
	    (cond ((= ch ?\C-a)		;strip these out
		   (setq string (concat (substring string 0 where)
					(substring string (1+ where)))))
		  ((= ch ?\C-b)		;start of command
		   (setq smalltalk-command-string "") ;start this off
		   (setq string (substring string (1+ where))))
		  )
	    )
	)
      )
    (save-excursion
      (set-buffer (process-buffer process))
      (goto-char (point-max))
      (and string
	   (setq mode-status "idle")
	   (insert string))
      (if (process-mark process)
	  (set-marker (process-mark process) (point-max)))
      )
    )
  ;;  (if (eq (process-buffer process)
  ;;	  (current-buffer))
  ;;      (goto-char (point-max)))
					;  (save-excursion
					;      (set-buffer (process-buffer process))
					;      (goto-char (point-max))
  ;;      (set-window-dot (get-buffer-window (current-buffer)) (point-max))
					;      (sit-for 0))
  (let ((buf (current-buffer)))
    (set-buffer (process-buffer process))
    (goto-char (point-max)) (sit-for 0)
    (set-window-dot (get-buffer-window (current-buffer)) (point-max))
    (set-buffer buf))
  )

(defun smalltalk-accum-command (string)
  (let (where)
    (setq where (string-match "\C-a" string))
    (setq smalltalk-command-string
	  (concat smalltalk-command-string (substring string 0 where)))
    (if where
	(progn
	  (unwind-protect		;found the delimiter...do it
	      (smalltalk-handle-command smalltalk-command-string)
	    (setq smalltalk-command-string nil))
	  ;; return the remainder
	  (substring string where))
      ;; we ate it all and didn't do anything with it
      nil)
    )
  )


(defun smalltalk-handle-command (str)
  (eval (read str))
  )


(defun gst-mode ()
  "Major mode for interacting Smalltalk subprocesses.

The following commands imitate the usual Unix interrupt and
editing control characters:
\\{smalltalk-mode-map}

Entry to this mode calls the value of gst-mode-hook with no arguments,
if that value is non-nil.  Likewise with the value of shell-mode-hook.
gst-mode-hook is called after shell-mode-hook."
  (interactive)
  (kill-all-local-variables)
  (setq major-mode 'gst-mode)
  (setq mode-name "Smalltalk")
  (setq mode-line-format
	'("" mode-line-modified mode-line-buffer-identification "   "
	  global-mode-string "   %[(" mode-name ": " mode-status
	  "%n" mode-line-process ")%]----" (-3 . "%p") "-%-"))
  (if smalltalk-emacs19-p
      (progn
	(require 'comint)
	(comint-mode)
	(setq comint-prompt-regexp smalltalk-prompt-pattern)
	(use-local-map comint-mode-map)
	)
    (require 'shell)
    (use-local-map smalltalk-interactor-mode-map)
    ;;  (setq mode-line-process '(": %s"))
    (make-local-variable 'last-input-start)
    (setq last-input-start (make-marker))
    (make-local-variable 'last-input-end)
    (setq last-input-end (make-marker)))
  (make-local-variable 'mode-status)
  (make-local-variable 'smalltalk-command-string)
  (setq smalltalk-command-string nil)
  (setq mode-status "starting-up")
  (run-hooks (if smalltalk-emacs19-p 'comint-mode-hook 'shell-mode-hook)
	     'gst-mode-hook))



(defun smalltalk-eval-region (start end &optional label)
  "Evaluate START to END as a Smalltalk expression in Smalltalk window.
If the expression does not end with an exclamation point, one will be
added (at no charge)."
  (interactive "r")
  (let (str filename line pos)
    (setq str (buffer-substring start end))
    (save-excursion
      (save-restriction 
	(goto-char (max start end))
	(smalltalk-backward-whitespace)
	(if (/= (preceding-char) ?!)	;canonicalize
	    (setq str (concat str "!")))
	;; unrelated, but reusing save-excursion
	(goto-char (min start end))
	(setq pos (point))
	(setq filename (buffer-file-name))
	(widen)
	(setq line (1+ (count-lines 1 (point))))
	)
      )
    (send-to-smalltalk str (or label "eval")
		       (list line filename pos))
    )
  )

(defun smalltalk-reeval-region (remember)
  (interactive "P")
  (and remember
       (let (rgn start end)
	 (setq rgn (smalltalk-bound-expr))
	 (setq start (car rgn)
	       end (cdr rgn))
	 (setq smalltalk-eval-data
	     (smalltalk-get-eval-region-data start end "re-doIt"))))
  (apply 'send-to-smalltalk smalltalk-eval-data)
  )


(defun smalltalk-get-eval-region-data (start end &optional label)
  (interactive "r")
  (let (str filename line pos)
    (setq str (buffer-substring start end))
    (save-excursion
      (save-restriction 
	(goto-char (max start end))
	(smalltalk-backward-whitespace)
	(if (/= (preceding-char) ?!)	;canonicalize
	    (setq str (concat str "!")))
	;; unrelated, but reusing save-excursion
	(goto-char (min start end))
	(setq pos (point))
	(setq filename (buffer-file-name))
	(widen)
	(setq line (1+ (count-lines 1 (point))))
	)
      )
    ;; certainly not perfect, should probably use markers to bound the region
    (list str (or label "eval")
	  (list line filename pos))
    )
  )


(defun smalltalk-eval-region-with-memory (start end &optional label)
  "Evaluate START to END as a Smalltalk expression in Smalltalk window.
If the expression does not end with an exclamation point, one will be
added (at no charge)."
  (interactive "r")

;  (let (str filename line pos)
;    (setq str (buffer-substring start end))
;    (save-excursion
;      (save-restriction 
;	(goto-char (max start end))
;	(smalltalk-backward-whitespace)
;	(if (/= (preceding-char) ?!)	;canonicalize
;	    (setq str (concat str "!")))
;	;; unrelated, but reusing save-excursion
;	(goto-char (min start end))
;	(setq pos (point))
;	(setq filename (buffer-file-name))
;	(widen)
;	(setq line (1+ (count-lines 1 (point))))
;	)
;      )
;    ;; certainly not perfect, should probably use markers to bound the region
;    (setq smalltalk-eval-data
;	  (list str (or label "eval")
;		(list line filename pos)))
  (setq smalltalk-eval-data (smalltalk-get-eval-region-data start end label))
  (smalltalk-reeval-region)
;  )
  )





(defun smalltalk-doit (use-region)
  (interactive "P")
  (let (start end rgn)
    (if use-region
	(progn
	  (setq start (min (mark) (point)))
	  (setq end (max (mark) (point)))
	  )
      (setq rgn (smalltalk-bound-expr))
      (setq start (car rgn)
	    end (cdr rgn))
      )
    (smalltalk-eval-region start end "doIt")
    )
  )

(defun smalltalk-bound-expr ()
  "Returns a cons of the region of the buffer that contains a smalltalk expression.
It's pretty dumb right now...looks for a line that starts with ! at the end and
a non-white-space line at the beginning, but this should handle the typical
cases nicely."
  (let (start end here)
    (save-excursion
      (setq here (point))
      (re-search-forward "^!")
      (setq end (point))
      (beginning-of-line)
      (if (looking-at "^[^ \t\"]")
	  (progn
	    (goto-char here)
	    (re-search-backward "^[^ \t\"]")
	    (while (looking-at "^$") ;this is a hack to get around a bug
	      (re-search-backward "^[^ \t\"]") ;with GNU Emacs's regexp system
	      )
	    )
	)
      (setq start (point))
      (cons start end)
      )
    )
  )

(defun smalltalk-compile (use-region)
  (interactive "P")
  (let (str start end rgn filename line pos header classname category)
    (if use-region
	(progn
	  (setq start (min (point) (mark)))
	  (setq end (max (point) (mark)))
	  (setq str (buffer-substring start end))
	  (save-excursion
	    (goto-char end)
	    (smalltalk-backward-whitespace)
	    (if (/= (preceding-char) ?!) ;canonicalize
		(setq str (concat str "!")))
	    )
	  (send-to-smalltalk str "compile"))
      (setq rgn (smalltalk-bound-method))
      (setq str (buffer-substring (car rgn) (cdr rgn)))
      (setq filename (buffer-file-name))
      (setq pos (car rgn))
      (save-excursion
	(save-restriction
	  (widen)
	  (setq line (1+ (count-lines 1 (car rgn))))
	  )
	)
      (if (buffer-file-name)
	  (progn 
	    (save-excursion
	      (re-search-backward "^![ \t]*[A-Za-z]")
	      (setq start (point))
	      (forward-char 1)
	      (search-forward "!")
	      (setq end (point))
	      (setq line (- line (1- (count-lines start end))))
	      ;; extra -1 here to compensate for emacs positions being 1 based,
	      ;; and smalltalk's (really ftell & friends) being 0 based.
	      (setq pos (- pos (- end start) 1)))
	    (setq str (concat (buffer-substring start end) "\n\n" str "!"))
	    (send-to-smalltalk str "compile"
		       ;-2 accounts for num lines and num chars extra
			       (list (- line 2) filename (- pos 2)))
	    )
	(save-excursion
	  (re-search-backward "^!\\(.*\\) methodsFor: \\(.*\\)!")
	  (setq classname (buffer-substring
			   (match-beginning 1) (match-end 1)))
	  (setq category (buffer-substring
			  (match-beginning 2) (match-end 2)))
	  (goto-char (match-end 0))
	  (setq str (smalltalk-quote-strings str))
	  (setq str (format "%s compile: '%s' classified: %s!\n"
			    classname (substring str 0 -1) category))
	  (save-excursion (set-buffer (get-buffer-create "junk"))
			  (erase-buffer)
			  (insert str))
	  (send-to-smalltalk str "compile"
			     (list line nil 0))
	  )
		 
      )
    )
    )
  )


(defun smalltalk-bound-method ()
  (let (start end)
    (save-excursion
      (re-search-forward "^!")
      (setq end (point)))
    (save-excursion
      (re-search-backward "^[^ \t\"]")
      (while (looking-at "^$")		;this is a hack to get around a bug
	(re-search-backward "^[^ \t\"]");with GNU Emacs's regexp system
	)
      (setq start (point)))
    (cons start end))
  )


(defun smalltalk-quote-strings (str)
  (let (new-str)
    (save-excursion
      (set-buffer (get-buffer-create " st-dummy "))
      (erase-buffer)
      (insert str)
      (goto-char 1)
      (while (and (not (eobp))
		  (search-forward "'" nil 'to-end))
	(insert "'"))
      (buffer-string)
      )
    )
  )

(defun smalltalk-snapshot (&optional snapshot-name)
  (interactive (if current-prefix-arg
		   (list (setq snapshot-name (expand-file-name (read-file-name "Snapshot to: "))))))
  (if snapshot-name
      (send-to-smalltalk (format "Smalltalk snapshot: '%s'!" "Snapshot"))
  (send-to-smalltalk "Smalltalk snapshot!" "Snapshot"))
  )

(defun smalltalk-print (start end)
  "Evaluate the expression delimited by START and END and print the result.
Interactively, the region is used.  Printing is done in the standard Smalltalk
output window."
  (interactive "r")
  (let (str)
    (setq str (buffer-substring start end))
    (save-excursion
      (goto-char (max start end))
      (smalltalk-backward-whitespace)
      (if (= (preceding-char) ?!)	;canonicalize
	  (setq str (buffer-substring (min start end)  (point)))
	)
      (setq str (format "(%s) printNl!" str))
      (send-to-smalltalk str "print")
      )
    )
  )


(defun smalltalk-quit ()
  "Terminate the Smalltalk session and associated process.  Emacs remains
running."
  (interactive)
  (send-to-smalltalk "Smalltalk quitPrimitive!" "Quitting"))

(defun smalltalk-filein (filename)
  "Do a FileStream>>fileIn: on FILENAME."
  (interactive "fSmalltalk file to load: ")
  (send-to-smalltalk (format "FileStream fileIn: '%s'!"
			     (expand-file-name filename))
		     "fileIn")
  )

;(defun smalltalk-show-implementors ()
;  (interactive)
;  (let (method-name)
;    (save-excursion
;      )
;    (send-to-smalltalk (format "Browser whoImplements: #%s"
;			       method-name)
;		       "implementors")


(defun smalltalk-complete-class (name)
  (interactive (list (completing-read "Class: " smalltalk-class-names nil
				      t nil)))
  (message name) (sit-for 3)
  name
  )

(defun smalltalk-get-class-names ()
  (interactive)
  (send-to-smalltalk "Browser loadClassNames!" "ClassNames")
  )

(defun smalltalk-set-class-names (class-names)
  (let (sym-str)
    (setq smalltalk-class-names nil)
    (while class-names
      (setq sym-str (symbol-name (car class-names)))
      (setq smalltalk-class-names (cons (cons sym-str sym-str)
					smalltalk-class-names))
      (setq class-names (cdr class-names))
      )
    )
  )

(defun smalltalk-set-all-methods (method-names)
  (let (sym-str)
    (setq smalltalk-method-names nil)
    (while method-names
      (and (not (assoc (car method-names) smalltalk-method-names))
	   (setq smalltalk-method-names (cons (list (car method-names))
					      smalltalk-method-names)))
      (setq method-names (cdr method-names))
      )
    )
  )

(defun smalltalk-show-instance-methods (class-name)
  (interactive (smalltalk-complete-class-name))
   ;;(require 'browse)
  (send-to-smalltalk (format "Browser showMethods: %s for: 'instance'!"
			     class-name)
		     "ShowInstMethods")
  )

(defun smalltalk-generic-show-methods (class-name kind is-class)
  "IS-CLASS is either the empty string or the word 'Class'.  
Kind is one of 'Direct', 'Indirect', or 'All'"
  (require 'browse)
  (let ((class-selector (downcase is-class))
	(inst-or-class (if (= (length is-class) 0) "Inst" "Class"))
	(class-space (if (= (length is-class) 0) "" "Class "))
	)
    (send-to-smalltalk
     (format "Browser show%sMethods: %s %s inBuffer: '*%s %sMethods*'!"
	     kind class-name class-selector kind
	     class-space)
     (format "Show%sMethods" inst-or-class)
     )
    )
  )
  

(defun smalltalk-show-direct-class-methods (class-name)
  (interactive (smalltalk-complete-class-name))
  (smalltalk-generic-show-methods class-name "Direct" "Class")
  )

(defun smalltalk-show-indirect-class-methods (class-name)
  (interactive (smalltalk-complete-class-name))
  (smalltalk-generic-show-methods class-name "Indirect" "Class")
  )

(defun smalltalk-show-all-class-methods (class-name)
  (interactive (smalltalk-complete-class-name))
  (smalltalk-generic-show-methods class-name "All" "Class")
  )

(defun smalltalk-show-direct-instance-methods (class-name)
  (interactive (smalltalk-complete-class-name))
  (smalltalk-generic-show-methods class-name "Direct" "")
  )

(defun smalltalk-show-all-instance-methods (class-name)
  (interactive (smalltalk-complete-class-name))
  (smalltalk-generic-show-methods class-name "All" "")
  )

(defun smalltalk-show-indirect-instance-methods (class-name)
  (interactive (smalltalk-complete-class-name))
  (smalltalk-generic-show-methods class-name "Indirect" "")
  )

(defun smalltalk-show-class-methods (class-name)
  (interactive (smalltalk-complete-class-name))
  (require 'browse)
  (send-to-smalltalk (format "Browser showMethods: %s class
                                      for: 'class' !"
			     class-name)
		     "ShowClassMethods")
  )

(defun smalltalk-browse-hierarchy ()
  (interactive)
  (require 'browse)
  (send-to-smalltalk "Browser browseHierarchy!" "GetHierarchy")
  )


(defun smalltalk-browse-selectors ()
  "Set up to browse all methods whose selectors match the selector under
point."
  (interactive)
  (let ((selector (smalltalk-collect-selector)))
    (send-to-smalltalk
     (format "Browser getAllSelectors: #%s inBuffer: '#%s classes'!"
	     selector selector)
		       "ShowSelectors")
    )
  )

(defun smalltalk-complete-class-name (&optional prompt)
  (or prompt
      (setq prompt "Class name: "))
  ;; add getting of class names here when required.
  (list (completing-read prompt smalltalk-class-names nil t))
  )

(defun smalltalk-toggle-decl-tracing ()
  (interactive)
  (send-to-smalltalk
"Smalltalk declarationTrace:
     Smalltalk declarationTrace not!")
  )

(defun smalltalk-toggle-exec-tracing ()
  (interactive)
  (send-to-smalltalk "Smalltalk executionTrace: Smalltalk executionTrace not!")
  )


(defun smalltalk-toggle-verbose-exec-tracing ()
  (interactive)
  (send-to-smalltalk "Smalltalk verboseTrace: Smalltalk verboseTrace not!")
  )

(defun test-func (arg &optional cmd-arg)
  (let ((buf (current-buffer)))
    (unwind-protect
	(progn
	  (if (not (consp (cdr arg)))
	      (progn
		(find-file-other-window (car arg))
		(goto-char (1+ (cdr arg)))
		(recenter '(0))		;hack to recenter the window without
					;redisplaying everything
		)
	    (switch-to-buffer-other-window (get-buffer-create (car arg)))
	    (smalltalk-mode)
	    (erase-buffer)
	    (insert (format "!%s methodsFor: '%s'!

%s! !" (nth 0 arg) (nth 1 arg) (nth 2 arg)))
	    (beginning-of-buffer)
	    (forward-line 2)		;skip to start of method
	  )
	  )
      (pop-to-buffer buf)
      )
    )
  )

(defun hier-func (arg)
  ;; browse the direct methods for the given class in the other window.
  ;; just the local methods
  ;; idea: use the => marker, split the top pane in two, showing
;;;Object                            | Method for class desc 1
;;;    Autoload			     | method for class desc 2
;;;    Behavior			     | method for class desc 3 
;;;=>      ClassDescription	     |
;;;            Class		     |
;;;            Metaclass	     |
;;;    BlockContext		     |
  ;; don't even have to use the marker, just installing any random junk
  ;; into the buffer should be sufficient.  The bottom window shows the
  ;; source code.  May even use the marker for the method window?
  ;; --> be sure to set truncate lines to true (buffer local )

  (message arg)
  )




(defun send-to-smalltalk (str &optional mode fileinfo)
  (let (temp-file buf switch-back old-buf)
    (setq temp-file (concat "/tmp/" (make-temp-name "gst")))
    (save-excursion
      (setq buf (get-buffer-create " zap-buffer "))
      (set-buffer buf)
      (erase-buffer)
      (princ str buf)
      (write-region (point-min) (point-max) temp-file nil 'no-message)
      )
    (kill-buffer buf)
    ;; this should probably be conditional
    (save-window-excursion (gst gst-args))
;;; why is this like this?
;;    (if mode
;;	(progn
;;	  (save-excursion
;;	    (set-buffer (process-buffer *smalltalk-process*))
;;	    (setq mode-status mode))
;;	  ))
    (setq old-buf (current-buffer))
    (setq buf (process-buffer *smalltalk-process*))
    (pop-to-buffer buf)
    (if mode
	(setq mode-status mode))
    (goto-char (point-max))
    (newline)
    (pop-to-buffer old-buf)
;    (if (not (eq buf (current-buffer)))
;	(progn
;	  (switch-to-buffer-other-window buf)
;	  (setq switch-back t))
;      )
;    (if mode
;	(setq mode-status mode))
;    (goto-char (point-max))
;    (newline)
;    (and switch-back (other-window 1))
;      ;;(sit-for 0)
    (if fileinfo
	(process-send-string
	 *smalltalk-process*
	 (format
	  "FileStream fileIn: '%s' line: %d from: '%s' at: %d!\n"
	  temp-file (nth 0 fileinfo) (nth 1 fileinfo) (nth 2 fileinfo)
	  ))	
      (process-send-string *smalltalk-process*
			   (concat "FileStream fileIn: '" temp-file "'!\n"))
      )
    )
  )



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; GNU Emacs hooks for invoking Emacs on Smalltalk methods
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;



(setq command-switch-alist
      (append '(("-smalltalk" . smalltalk-edit))
	      command-switch-alist))


(defun smalltalk-edit (rest)
  (let (file pos done)
    (setq file (car command-line-args-left))
    (setq command-line-args-left
	  (cdr command-line-args-left))
    (setq pos (string-to-int (car command-line-args-left)))
    (setq command-line-args-left
	  (cdr command-line-args-left))
    (find-file (expand-file-name file))
    (goto-char pos)
    )
  )
