[comp.lang.eiffel] Eiffel-mode bug?

tynor@prism.gatech.EDU (Steve Tynor) (03/20/90)

I tried sending this directly to Bob Weiner, but it's bounced back 4 times...

------------------------------------------------------------------------------

I'm having trouble with the GNU Emacs Eiffel-mode recently posted by Bob
Weiner: eiffel.el. Here's the version I'm using:

	;; LAST-MOD:      6-Feb-90 at 18:43:30 by Bob Weiner

The trouble is that features don't always indent properly.  The
following construct:

	>	x is
	>	   -- a comment
	>       deferred
	>	end;
	
will indent incorrectly. E.g.:
	
	>	x is
	>	     -- a comment
	>deferred
	>end;
	
This only seems to happen when there is a comment or trailing space on the
'is' line.
	
I've checked the correctness of 'e-ends-with-is' - it seems to work properly
(handles trailing spaces and comments).  The problem appears to be that it is
only called by the 'e-comment-indent' function - was it intended to be used by
other line styles (e.g. the "deferred", or "require", etc) too?

Other than that, I can't be of much more assistance. If someone can diagnose
the problem and send a patch, I'd greatly appreciate it!
	
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
C++: Just say no.
                     
    Steve Tynor
    Georgia Tech Research Institute
    Artificial Intelligence Branch
    tynor@prism.gatech.edu
	

hws@icsi.Berkeley.EDU (Heinz Schmidt) (03/21/90)

	>From tynor@prism.gatech.EDU (Steve Tynor)

	>I'm having trouble with the GNU Emacs Eiffel-mode recently posted by Bob
	>Weiner: eiffel.el. Here's the version I'm using:

	>	;; LAST-MOD:      6-Feb-90 at 18:43:30 by Bob Weiner

	> The trouble is that features don't always indent properly.  The
	> following construct
	
	> will indent incorrectly. E.g.:
	
	>	>	x is
	>	>	     -- a comment
	>	>deferred
	>	>end;

Below you find an extension to Bob Weiner's eiffel-mode. 
It includes patches to various indentation problems we found including the one
you mentioned. Simply load the file after the other mode files.
(I include the whole file since I've no time to check what depends on what.
Although the file includes some X11 mouse support it does not depend 
on the X11 setup of Emacs).

-- Heinz

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Cut Here ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; FILE:         eif-ext.el
;; SUMMARY:      Eiffel mode extensions: 
;;               ec current buffer, region comment/uncomment
;;
;; AUTHOR:       Heinz W. Schmidt, Internat. Computer Science Inst.
;; E-MAIL:       USENET:  hws@icsi.berkeley.edu 
;;
;; CREATED:      Dec-89
;; HISTORY:      16-Feb-90 (hws): split off to go with Bob Weiners eiffel-mode.
;;               19-Feb-90 (hws): explicit cd to remove fragile ec <filename> call
;;                                which ec accepts only as first and sole arg.
;;               20-Feb-90 -
;;               22-Feb-90 (hws): various indentation problems fixed.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun str2argv (STR)
  (if (string-match "[^ ]" STR)
      (let ((arg1 (read-from-string STR)))
	(cons (prin1-to-string (car arg1))
	      (str2argv (substring STR (cdr arg1)))))))

(defvar run-ec-args "" "<arguments for ec>")
 
(defun class-compile (ARG)
  "Calls ec (Eiffel compile).  By default, ec is called on the file associated to 
the current buffer.
With numeric argument 0 prompts for explicit command line arguments.
Other numeric arguments allow you to insert options or further class names to go
with ec of the current file."
  (interactive "P")
  (let* ((ec-output (get-buffer-create "*ec-output*"))
	 (ec-process (get-buffer-process ec-output))
	 (curr-buffer (current-buffer))
	 (curr-name (buffer-file-name curr-buffer))
         (curr-dir (file-name-directory curr-name))
         (curr-classname (file-name-nondirectory curr-name)))
    (if ec-process
	(if (y-or-n-p "Kill current ec process? ")
	    (delete-process ec-process)
	  (error "Can't ec concurrently.")))
    (if (and (buffer-modified-p curr-buffer)
	     (y-or-n-p (format "Save file %s? " curr-name)))
	(progn (save-buffer) (message "")))
    ;; maybe prompt for args and dispatch according to numeric ARG
    (setq run-ec-args (if ARG (read-input "ec args: " run-ec-args) ""))
    ;; switch to shell buffer and run ec
    (set-buffer ec-output) (erase-buffer)
    ;; focus to directory and trim classname so ec works in situations
    ;; like       ec -t class1 <curr-classname>
    (cd curr-dir)
    (setq curr-classname (substring curr-classname 0 (- (length curr-classname) 2)))
    (insert "ec" (if ARG (format " %s" run-ec-args) "")
	    (format " %s" (if (not (and ARG (zerop ARG))) curr-classname ""))
	    "\n")
    (set-buffer curr-buffer)
    (display-buffer ec-output)
    (eval   
     (append '(start-process "ec" ec-output "ec")
	     (str2argv run-ec-args)
	     (if (not (and ARG (zerop ARG))) (list curr-classname)))))) 

(defvar *silent-comment-start* "--|")	;set this in eiffel-mode-hook if 
					;comment/uncomment also used in other modes
;;Commented to not interfere with personal eiffel-mode-hook
;;(setq eiffel-mode-hook '(lambda nil
;;  (setq *silent-comment-start* "--|") ; may be reset by other modes
;;  ;(setq comment-column 40) (setq fill-column 70) (setq fill-prefix " * ")
;;  ))

(defun comment-region-lines (arg)
  "Comments the lines of region by placing comment chars at begin-of-line."
  (interactive "P")
  (let ((to (region-end)))
    (goto-char (region-beginning)) 
    (while (< (point) to)
      (beginning-of-line)
      (insert *silent-comment-start*) 
      (next-line 1))))

(defun uncomment-region-lines (arg)
  "Uncomments the lines of region by removing comment chars from begin-of-line."
  (interactive "P")
  (let ((to (region-end)))
    (goto-char (region-beginning)) 
    (while (< (point) to)
      (beginning-of-line) 
      (if (looking-at *silent-comment-start*) 
         (let ((beg (point))) 
	   (forward-char 3) (delete-region beg (point))))
      (next-line 1))))

(define-key eiffel-mode-map "\C-c\C-e" 'class-compile)

;; 2/22 (hws) Allow the X users to compose code by pointing to pieces successively.
;;            The pointed thing is yanked to point. 
;; 2/23 (hws) fixed to also work across windows.
;; 3.1 (hws) Allow the X users to compose code by pointing to pieces successively.
;;            The pointed thing is yanked to point. 

(defvar mouse-mark-thing-p t
 "*Non-nil means that the the marked region is highlighted (only under epoch).")

(defun x-mouse-mark-pointed-thing (arg &rest ignore)
  "Determines the item pointed to and marks it. Redefine mark-thing-at-point
to make it mode dependent. By default an s-expression is chosen as the thing. 
This means that parentheses are respected to group things."
  (x-mouse-set-point arg)
  (mark-thing-at-point mouse-mark-thing-p))

(defun x-mouse-copy-pointed-thing (arg &rest ignore)
  (x-mouse-mark-pointed-thing arg)
  (copy-region-as-kill (mark) (point)))

(defun copy-thing-at-point ()
  (mark-thing-at-point nil)
  (copy-region-as-kill (mark) (point)))

(defun x-mouse-yank-pointed-thing (arg &rest ignore)
  "Determines the item pointed to and yanks it to point. 
Redefine copy-thing-at-point to make it mode dependent.
By default an s-expression is chosen as the thing. This means that
parentheses are respected to group things."
  (save-excursion
    ;; make sure this works also across different buffer windows
    (let ((point-w (selected-window)))
      (x-mouse-set-point arg)
      (copy-thing-at-point)
      (select-window point-w)))
  (yank))

(setq close-re "[\\\)\\\]\\\}]") ;; doesn't work yet

(defun mark-thing-at-point (&optional mark-it)
  (let ((opoint (point))
	(begin))
    ;; find beginning
    (if (looking-at close-re) ;; close parens
	(backward-sexp)
      (progn
	(forward-sexp)			; make sure pointing works on first char,
	(forward-sexp -1)		; calibrate,
	))
    (setq begin (min opoint (point)))	; no surprises what you point to should be in
	  				; what you get, and
    (forward-sexp)			; now to the end
    ;; make sure we really were on something,
    (if (and (<= begin opoint) (<= opoint (point)))
	(progn
	  (set-mark begin)
	  (if (and mark-it (boundp 'epoch::version) epoch::version)
	      (progn 
		(if drag-button
		    (epoch::delete-button drag-button))
		(unset-motion-handling-until-all-buttons-up) 
		(setq drag-button (epoch::add-button begin (point) 1 nil))
		(epoch::redisplay-screen)
		))
	  ;;make sure successive marks and yanks get the proper spacing
	  (if (looking-at "[ \t]") (forward-char)) ;"[ \t\n]" \n is strange
	  ))
    ))

(defun unset-motion-handling-until-all-buttons-up ()
  ;; no satisfying way found until now.
  ;; (epoch::set-motion-hints nil)
  nil
  )

;; make sure other-window-systems users are not surprised
(if (boundp 'x-button-c-m-middle)
    (progn
      (define-key mouse-map x-button-c-m-middle 'x-mouse-yank-pointed-thing)
      (define-key mouse-map x-button-c-m-right 'x-mouse-copy-pointed-thing)))

;; 2/20 (hws): Improve the logic for 'is' in manifest defs.
;;             so that indentation also works if there are blanks etc.
;;             following the 'is'.

(defun e-maybe-in-manifest-constant-p ()
  (and (looking-at "is[ \t]*[^-\n]")
       (not (save-excursion
	      (end-of-line) (backward-word 1)
	      (or (looking-at "end")	;short body
		  (looking-at "is")))))) ;back where we were

(defun e-goto-block-head ()
  "Move point to the block head that would be paired with an end at point.
Return nil if none."
  (let ((depth 1))
    (while (and (> depth 0)
		;; Search for start of keyword
		(re-search-backward
		  "\\(^\\|[ \t]\\)\\(indexing\\|class\\|expanded\\|\
deferred[ \t]+class\\|if\\|from\\|check\\|inspect\\|\is\\|debug\\|\
end\\)[ \t;\n]" nil t))
      (goto-char (match-beginning 2))
      (cond ((or (e-in-comment-p)
		 (e-in-quoted-string-p)
		 (e-maybe-in-manifest-constant-p))
             nil)                       ;ignore it
            ((looking-at "end")         ;end of block
             (setq depth (1+ depth)))
	    (t                          ;head of block
             (setq depth (1- depth)))))
    (if (> depth 0)                     ;check whether we hit top of file
        nil
      t)))

;; 2/20 (hws): There is another less important point in connnection with 'is',
;;             when you split a line behind 'is', like 'is do',
;;             a single blank is inserted by the indenter, because eiffel-indent-line 
;;             does not leave the cursor where it was (relative).
;;             In some re-searches such may have unexpected results.

(defun eiffel-indent-line ()
  "Indent the current line as Eiffel code."
  (interactive)
  (save-excursion
    (beginning-of-line)
    (delete-horizontal-space)
    (indent-to (e-calc-indent)))
    ;; skip only at the beginning of line
    (if (looking-at "^") (skip-chars-forward " \t"))
  )

;; 2/20 (hws): make eiffel-beginning-of-feature symmetrical to eiffel-end-of-feature.
;;             previously end;end;beginning wouldn't put you were you got after
;;             the first end but almost definitely at the buffer begin.
;; There remains an error: search does not fail on buffer beginning.

(defun eiffel-beginning-of-feature (&optional arg)
  "Move backward to next feature beginning.
With argument, do this that many times.
Returns t unless search stops due to beginning of buffer."
  (interactive "p")                    
  (and arg (< arg 0) (forward-char 1)) 
  (if (re-search-backward "^[ \t]*[\n][ \t]*[a-zA-Z0-9]\\|\\`" 
			  nil 'move (or arg 1))
      (progn (forward-line 1) t) 
    ))

;; 2/20 (hws): make sure the following is ok, even if it may not be nice.
;;             'do Result:=subnets.item(0) end;'  gets at the same place
;;             as if 'end' was on the subsequent line.

(defun e-ends-with-end-p ()
  "t if line ends with 'end' or 'end;' and a comment."
  (save-excursion
    (beginning-of-line)
    (and (looking-at "^\\(.*[ \t]+\\)?end;?[ \t]*\\($\\|--\\)")
         (not (looking-at "[ \t]*\\(do\\|from\\|loop\\|if\\|then\\|else\\|when\\|until\\|\
inspect\\|debug\\)")))))

;; 2/21 (hws): change doc of Eiffel mode to mention class-compile
;;             this patch is always loaded at the end, until the fixes
;;             are integrated. 
;; 2/27 (hws): also make the doc style settable by user.

(defvar eiffel-moto-hdr-p t
  "*If t, use our Motorola developed Eiffel construct template headers.")

(defun eiffel-mode ()
  "A major editing mode for the language Eiffel.
Comments are begun with --.
Paragraphs are separated by blank lines
Delete converts tabs to spaces as it moves back.
Tab anywhere on a line indents it according to Eiffel conventions.
M-; inserts and indents a comment on the line, or indents an existing
comment if there is one.
Return indents to the expected indentation for the new line.
Under X11, C-M-mouse-middle copies pointed-to thing to point.

Skeletons of the major Eiffel constructs are inserted with:

 C-c c  class           C-c i  if          C-c s  set-procedure
 C-c f  function        C-c p  procedure   C-c a  attribute
 C-c l  loop            M-;    comment     C-c \  delimit multi-line string

Incremental Compilation:

 C-c C-e class-compile (ec current buffer)

Abbreviations:
 itg   for  INTEGER           boo  for  BOOLEAN
 cha   for  CHARACTER         stg  for  STRING
 rea   for  REAL              dou  for  DOUBLE
 res   for  Result            cre  for  Create
 cur   for  Current           fgt  for  Forget

Variables controlling style:
   eiffel-indent          Indentation of Eiffel statements.
   eiffel-comment-col     Goal column for inline comments
   eiffel-moto-hdr-p      If t use a refined doc style (feature granularity)
                          suitable for extracting Unix(TM) man like reference, later.

Feature operations:
   C-M-a                 Move to current or previous feature beginning.
   C-M-e                 Move to current or previous feature end.
   C-M-h                 Mark current feature.

Turning on Eiffel mode calls the value of the variable eiffel-mode-hook with
no args, if that value is non-nil."
  (interactive)
  (kill-all-local-variables)
  (use-local-map eiffel-mode-map)
  (set-syntax-table eiffel-mode-syntax-table)
  (setq major-mode 'eiffel-mode)
  (setq mode-name "Eiffel")
  (eiffel-mode-variables nil)
  (run-hooks 'eiffel-mode-hook))


;;2/22 (hws): make sure indentation following multi line formal argument paren group is 
;;            not relative to last item but rather to the head to which
;;            the group belongs.

(defun e-get-block-indent ()
  "Return the outer indentation of the current block. Returns 0 or less if it can't
find one."
  (let ((indent 0) (succeed))
    (save-excursion
      (setq succeed (e-goto-block-head))
      (cond ((not succeed) nil)
	    ;; heads ending in 'is' have extra indent
            ((looking-at "is")
	     ;; but maybe we are behind a multi line paren group
             (eiffel-indent-line);; not a nice sideeffect, see later how to do without
	     (if (equal e-last-indent-type "STRING OR PAREN GROUPING")
		 (eiffel-beginning-of-feature))
             (setq indent (+ (current-indentation) eiffel-indent))
	     )
            (t
	     (setq indent (current-indentation)))))
    (if (and (e-ends-with-end-p)
	     (save-excursion
	       (beginning-of-line)
	       ;; An "end" on a line by itself
	       (looking-at "^[ \t]*end;?[ \t]*\\($\\|--\\)")))
	(setq indent (- indent eiffel-indent)))
    (if succeed
        indent
      -20)))                            ;will put at first col if lost


;;2/22 (hws): ditto for comments following in multi-line paren groups.

(defun e-comment-indent ()
  "Return indentation for a comment line."
    (save-excursion
      (let ((in (e-get-block-indent))
	    (prev-is-blank
	      (save-excursion (and (= (forward-line -1) 0) (e-empty-line-p)))))
      (if (or (and prev-is-blank (= in 0))
	      (not (e-move-to-prev-non-blank))) ;move to prev line if there is one
	  0                                     ;early comments start to the left
	(cond ((e-ends-with-is)             ;routine definition comment
	       (+ eiffel-indent in))
	      ((save-excursion              ;attribute definition comment
		     (and (= (e-get-block-indent) 0)
			  (progn (end-of-line) (= (preceding-char) ?\;))
			  (= (forward-line -1) 0)
			  (or (e-empty-line-p)
			      (looking-at "feature[ \t]*$"))))
	       (+ (* eiffel-indent 2) (e-current-indentation)))  ; indent twice
	      ((e-comment-line-p)         ;is a comment, same indentation
	       (e-current-indentation))
	      (t                          ;otherwise indent once
		(+ eiffel-indent (e-current-indentation))))))))

;;; EOF