[comp.emacs] csh mode posting for those who asked

arc1@eagle.ukc.ac.uk (A.R.Curtis) (06/29/88)

For those who asked about the csh-mode here it is. Should
have sharred it up I suppose but I guess it'll be ok.

--------------------------------- cut ------------------------------------
;; csh-mode for emacs: usual mode modifying behaviour for writing
;; shell scripts in C-shell (+ Tenex)
;;
;; PD software, free etc.
;;
;; $Header: csh-mode.el,v 1.2 88/06/27 09:51:46 arc1 Exp $

(defconst csh-SPACE 32
  "The space character")

(defconst csh-mode-default-exdent 2
  "Supplied indentation level as extra for keywords")

(defvar csh-match-and-tell t
  "*Non-nil means tell user about which keyword matches the current
one when closing constructs")

(defvar csh-shell-flags-default "-f"
  "*Flags passed to the shell in the file when inserting the shell
execute header")

;; exdenting
(defvar csh-match-case-regexp           "^[ \|\t]*case.*"
  "*Regexp used to match keyword: case")

(defvar csh-match-default-regexp        "^[ \|\t]*default:.*"
  "*Regexp used to match keyword: default")

(defvar csh-match-else-regexp           "^[ \|\t]*else.*"
  "*Regexp used to match keyword: else")

(defvar csh-match-ifthen-regexp         "^[ \|\t]*if.*then.*"
  "*Regexp used to match keyword sequence: if ... then")

(defvar csh-match-foreach-regexp        "^[ \|\t]*foreach.*"
  "*Regexp used to match keyword: foreach")

(defvar csh-match-while-regexp          "^[ \|\t]*while.*"
  "*Regexp used to match keyword: while")

;; structure starting
(defvar csh-match-switch-regexp         "^[ \|\t]*switch.*"
  "*Regexp used to match keyword: switch")

;; indenting
(defvar csh-match-breaksw-regexp        "^[ \|\t]*breaksw.*"
  "*Regexp used to match keyword: breaksw")

(defvar csh-match-break-regexp          "^[ \|\t]*break.*"
  "*Regexp used to match keyword: break")

;; structure ending
(defvar csh-match-end-regexp            "^[ \|\t]*end.*"
  "*Regexp used to match keyword: end")

(defvar csh-match-endsw-regexp          "^[ \|\t]*endsw.*"
  "*Regexp used to match keyword: endsw")

(defvar csh-match-endif-regexp          "^[ \|\t]*endif.*"
  "*Regexp used to match keyword: endif")

;; matching patterns for constructs

(defvar csh-match-case-or-default-regexp "^[ \\|\\t]*\\(case\\|default:\\).*"
  "*Match one of keywords: case, default")

(defvar csh-match-foreach-or-while-regexp "^[ \\|\\t]*\\(foreach\\|while\\).*"
  "*Match one of keywords: foreach, while")

(defvar csh-match-else-or-ifthen-regexp "^[ \\|\\t]*\\(else\\|if.*then\\).*"
  "*Match one of keywords: else, if...then")


(defvar csh-mode-exdent csh-mode-default-exdent
  "*Distance to exdent current line under previous")

(defvar csh-shell-flags csh-shell-flags-default
  "*Flags to supply to the shell invocation line")

(defvar csh-mode-map nil
  "Keymap for C-shell mode")

(defvar csh-mode-abbrev-table nil)
(defvar csh-mode-syntax-table nil)

(defun csh-mode-syntax-table ()
  "Csh syntax modifications"
  nil)

(if csh-mode-map
    nil
  (progn
    (setq csh-mode-map (make-sparse-keymap))
    (define-key csh-mode-map "\e#"     'csh-insert-shell-execute-line)
    (define-key csh-mode-map "\C-i"    'csh-indent-line-function)
    (define-key csh-mode-map "\177"    'backward-delete-char-untabify)
    (define-key csh-mode-map "\C-m"    'csh-match-structure-and-return)
    ) ;; progn
  ) ;; defun

(defun csh-mode-variables ()
  "Define local variables for csh mode"
  (make-local-variable 'csh-mode-exdent)
  (make-local-variable 'csh-shell-flags)
  (make-local-variable 'shell-file-name)   ;; in case another shell location
  (setq shell-file-name "/bin/csh")
  (make-local-variable 'indent-line-function)
  (setq indent-line-function 'csh-indent-line-function)
  ) ;; defun

(defun csh-mode ()
  "Run a mode suitable for writing C shell scripts

Currently running $Revision: 1.2 $

This mode allows matching of complex expressions to indicate the
nesting of constructs. 'end' may be matched with its corresponding
'foreach' or 'while' line (and a message is generated in the
minibuffer to say what the match was, or to give an error if no
match). The same applies to the other csh constructs.

TAB performs mode specific indentation according to current syntax.
RET maps to re-indentation of matching keywords in constructs.
Both keys indicate the corresponding match unless the variable
csh-match-and-tell has the value nil.
Key mapping is
\\{csh-mode-map}
"
  (interactive)
  (kill-all-local-variables)
  (use-local-map csh-mode-map)
  (setq major-mode 'csh-mode)
  (setq mode-name "Csh Script")
  (csh-mode-variables)
  (run-hooks 'csh-mode-hook)
  ) ;; defun

(defun indentation-on-this-line ()
  "Return current indentation level (no. of columns) that this line is
indented"
  (interactive)
  (save-excursion
    (beginning-of-line)
    (let
        (
         (pos-at-bol (point))
         (pos-at-ind (progn
                       (back-to-indentation)
                       (point)))
         )
         (compute-line-indent pos-at-bol pos-at-ind)
      ) ;; let
    ) ;; excursion
  ) ;; defun

(defun compute-line-indent (from to)
  "Given two points, add up the number of characters between from and
to. Do the proper things for tabs"
  (if (equal from to)
      0
    (+ (cond ((equal (char-to-string (char-after from)) "\t")
              tab-width)
             (t 1)
             ) ;; cond
       (compute-line-indent (+ 1 from) to))
    ) ;; if
  ) ;; defun

(defun is-looking-at-exdenter ()
  "Return true if current line contains an exdenting keyword"
  (or (looking-at csh-match-case-regexp)
      (looking-at csh-match-default-regexp)
      (looking-at csh-match-else-regexp)
      (looking-at csh-match-ifthen-regexp)
      (looking-at csh-match-foreach-regexp)
      (looking-at csh-match-while-regexp)
      ) ;; or
  ) ;; defun

(defun is-looking-at-indenter ()
  "Return true if current line contains an indenting keyword"
  (or (looking-at csh-match-breaksw-regexp)
      (looking-at csh-match-break-regexp)
      ) ;; or
  ) ;; defun

(defun need-more-less ()
  "Does the current level need refining? Return extra
indentation to indicate this"
  (save-excursion
    (forward-line -1)
    (cond ((is-looking-at-exdenter)
           csh-mode-exdent)
          ((is-looking-at-indenter)
           (- 0 csh-mode-exdent ))
          (t 0)
          ) ;; cond
    ) ;; excursion
  ) ;; defun

(defun csh-indent-line-function ()
  "Indent current line as far as it should go according
to the syntax"
  (interactive)
  (save-excursion
    (beginning-of-line)
    (if (bobp)
        nil
      (let*
          (
           (last-line-level (progn
                              (forward-line -1)     ;; previous line tells us
                              (indentation-on-this-line)))
           (this-line-level (progn
                              (forward-line 1)
                              (indentation-on-this-line)))
           (level-diff (- this-line-level last-line-level))
           )
        (cond ((> level-diff 0)
               (delete-char level-diff))
              ((< level-diff 0)
               (insert-char csh-SPACE (- 0 level-diff)))
	      (t nil)
              ) ;; cond
        (let
            (
             (extra (need-more-less))           ;; re-adjust indent level ?
             )
          (cond ((> extra 0)
                 (insert-char csh-SPACE extra))
                ((and (< extra 0)
		      (not (bolp)))
                 (delete-char extra))
                (t nil)
                ) ;; cond
          ) ;; let
        (if csh-match-and-tell
            (csh-match-structure-and-return t) ;; indicate match but no newline
          nil) ;; if
        ) ;; let*
      ) ;; if
    ) ;; excursion
  (let*
      (
       (this-line-level (indentation-on-this-line))
       (this-bol (save-excursion
                   (beginning-of-line)
                   (point)))
       (this-point (- (point) this-bol))
       )
    (cond ((> this-line-level this-point) ;; point in initial white space
           (back-to-indentation))
           (t nil)
           ) ;; cond
    ) ;; let*
  ) ;; defun


(defun csh-insert-shell-execute-line ()
  "Go to the top of the buffer and insert the #! /bin/csh
line if necessary. The flags passed to the shell are
the value of `csh-shell-flags'"
  (interactive)
  (let
      (
       (ins-nl-flag (and (bobp) (eobp)))
       (shell-rgexp (concat "^#![ \|\t]*" shell-file-name ".*"))
       )
    (if ins-nl-flag
        (next-line 1)
      nil) ;; if
    (save-excursion
      (beginning-of-buffer)
      (if (looking-at shell-rgexp)
          nil
        (progn
          (insert-string "#! " shell-file-name " " csh-shell-flags)
          (if (not ins-nl-flag)
              (insert-string "\n")
            nil) ;; if
          ) ;; progn
        ) ;; if
      ) ;; excursion
    ) ;; let
  ) ;; defun

;; got a problem matching nearest pattern only. Want to retain the
;; flexibility but sacrificing temporarily to get things
;; working

(defun csh-match-structure-and-return (&optional arg)
  "If the current line matches one of the indenting keywords
or one of the control structure ending keywords then indicate
where it matches if csh-match-and-tell is non-nil. Add newline
if argument present"
  (interactive)
  (if csh-match-and-tell
      (save-excursion
        (beginning-of-line)
        (cond ((looking-at csh-match-breaksw-regexp)
               (structure-match csh-match-case-or-default-regexp))
              ((looking-at csh-match-break-regexp)
               (structure-match csh-match-foreach-or-while-regexp))
              ((looking-at csh-match-else-regexp)
               (grep-and-indent-again csh-match-ifthen-regexp))
              ((looking-at csh-match-endsw-regexp)
               (structure-match csh-match-switch-regexp))
              ((looking-at csh-match-endif-regexp)
               (grep-and-indent-again csh-match-else-or-ifthen-regexp))
              ;; endif comes before end. Got to do something about this
              ((looking-at csh-match-end-regexp)
               (grep-and-indent-again csh-match-foreach-or-while-regexp))
              (t nil)
              ) ;; cond
        ) ;; excursion
    nil
    ) ;; if
  (if arg
      nil
    (newline 1)
    ) ;; if
  ) ;; defun

(defun grep-and-indent-again (pat)
  "Try to match PAT and then indent accordingly"
  (interactive)
  (let*
      (
       (last-level (save-excursion
                     (if (re-search-backward pat (point-min) t)
                         (indentation-on-this-line)
                       0) ;; if
                     ) ;; excursion
                   ) ;; bind
       (diff-level (- (indentation-on-this-line) last-level))
       ) ;; b-list
    (save-excursion
      (beginning-of-line)
      (cond ((> diff-level 0)
             (delete-char diff-level))
            ((< diff-level 0)
             (insert-char csh-SPACE (- 0 diff-level)))
            (t nil)
            ) ;; cond
      ) ;; excursion
    ) ;; let*
  (structure-match pat)
  ) ;; defun


(defun structure-match (pat)
  "Match against the pattern in PAT and print
out the matched text. If none then indicate no match and say so in
minibuffer"
  (interactive)
  (let
      (
       (result (found-a-match-in pat))
       )
    (message "%s" (cond ((null result)
                         "No matching open")
                        (t
                         result)
                        ) ;; cond
             ) ;; message
    ) ;; let
  ) ;; defun

(defun found-a-match-in (pat)
  "Go back and search for PAT. Text if successful, nil otherwise"
  (save-excursion
    (beginning-of-line)
    (if (re-search-backward pat (point-min) t)
        (format "Matched ... %s" (line-to-string))
      nil)
    ) ;; excursion
  ) ;; defun

(defun line-to-string ()
  "From point, construct a string from all characters on
current line"
  (setq answer "")
  (while (equal (char-after (point)) csh-SPACE)            ;; skip white space
    (forward-char 1)
    ) ;; while
  (while (not (eolp))
    (setq answer (concat answer (char-to-string (char-after (point)))))
    (forward-char 1)
    ) ;; while
  answer
  ) ;; defun
------------------------------- end cut ----------------------------------
-- 
Tony Curtis, Computing Lab.                | arc1@uk.ac.ukc
Univ. Kent at Canterbury                   | 
Canterbury, Kent CT2 7NF                   | tcu@uk.ac.ex.cs