936 lines
29 KiB
EmacsLisp
936 lines
29 KiB
EmacsLisp
;; csh-mode.el --- csh (and tcsh) script editing mode for Emacs.
|
|
;;
|
|
;; Version: 1.2
|
|
;; Date: April 2, 1999
|
|
;; Maintainer: Dan Harkless <dan@wave.eng.uci.edu>
|
|
;;
|
|
;; Description:
|
|
;; csh and tcsh script editing mode for Emacs.
|
|
;;
|
|
;; Installation:
|
|
;; Put csh-mode.el in some directory in your load-path and load it.
|
|
;;
|
|
;; Usage:
|
|
;; This major mode assists shell script writers with indentation
|
|
;; control and control structure construct matching in much the same
|
|
;; fashion as other programming language modes. Invoke describe-mode
|
|
;; for more information.
|
|
;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;
|
|
;; Author key:
|
|
;; DH - Dan Harkless <dan@wave.eng.uci.edu>
|
|
;; CM - Carlo Migliorini <migliorini@sodalia.it>
|
|
;; JR - Jack Repenning <jackr@sgi.com>
|
|
;; GE - Gary Ellison <Gary.F.Ellison@att.com>
|
|
;;
|
|
;; *** REVISION HISTORY ***
|
|
;;
|
|
;; DATE MOD. BY REASON FOR MODIFICATION
|
|
;; --------- -- --------------------------------------------------------------
|
|
;; 2 Apr 99 DH 1.2: Noticed an out-of-date comment referencing .bashrc etc.
|
|
;; 11 Dec 96 DH 1.1: ksh-mode just indented continuation lines by 1 space.
|
|
;; csh-mode looks at the first line and indents properly to line
|
|
;; up under the open-paren, quote, or command.
|
|
;; 11 Dec 96 DH Added fontification for history substitutions.
|
|
;; 10 Dec 96 DH Added indentation and fontification for labels. Added
|
|
;; fontification for variables and backquoted strings.
|
|
;; 9 Dec 96 DH 1.0: Brought csh-mode up to the level of functionality of
|
|
;; the original ksh-mode.
|
|
;; 7 Oct 96 CM 0.1: Hacked ksh-mode.el into minimally functional csh-mode.el
|
|
;; by doing search-and-replace and some keyword changes.
|
|
;; 8 Aug 96 JR (Last modification to ksh-mode 2.6.)
|
|
;; [...]
|
|
;; 19 Jun 92 GE (Conception of ksh-mode.)
|
|
;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
(defconst csh-mode-version "1.2"
|
|
"*Version number of this version of csh-mode")
|
|
|
|
(defvar csh-mode-hook
|
|
'(lambda ()
|
|
(auto-fill-mode 1))
|
|
"Hook to run each time csh-mode is entered.")
|
|
|
|
|
|
;;
|
|
;; -------------------------------------------> Variables controlling completion
|
|
;;
|
|
(defvar csh-completion-list '())
|
|
(make-variable-buffer-local 'csh-completion-list)
|
|
(set-default 'csh-completion-list '())
|
|
;;
|
|
;; -type- : type number, 0:misc, 1:variable, 2:function
|
|
;; -regexp-: regexp used to parse the script
|
|
;; -match- : used by match-beginning/end to pickup target
|
|
;;
|
|
(defvar csh-completion-type-misc 0)
|
|
(defvar csh-completion-regexp-var "\\([A-Za-z_0-9]+\\)=")
|
|
(defvar csh-completion-type-var 1)
|
|
(defvar csh-completion-match-var 1)
|
|
(defvar csh-completion-regexp-var2 "\\$\\({\\|{#\\)?\\([A-Za-z_0-9]+\\)[#%:}]?")
|
|
(defvar csh-completion-match-var2 2)
|
|
(defvar csh-completion-regexp-function
|
|
"\\(function\\)?[ \t]*\\([A-Za-z_0-9]+\\)[ \t]*([ \t]*)")
|
|
(defvar csh-completion-type-function 2)
|
|
(defvar csh-completion-match-function 2)
|
|
|
|
|
|
;;
|
|
;; ------------------------------------> Variables controlling indentation style
|
|
;;
|
|
(defvar csh-indent 4
|
|
"*Indentation of csh statements with respect to containing block. A value
|
|
of nil indicates compound list keyword \(\"do\" and \"then\"\) alignment.")
|
|
|
|
(defvar csh-case-item-offset csh-indent
|
|
"*Additional indentation for case items within a case statement.")
|
|
(defvar csh-case-indent nil
|
|
"*Additional indentation for statements under case items.")
|
|
(defvar csh-comment-regexp "^\\s *#"
|
|
"*Regular expression used to recognize comments. Customize to support
|
|
csh-like languages.")
|
|
(defvar csh-match-and-tell t
|
|
"*If non-nil echo in the minibuffer the matching compound command
|
|
for the \"breaksw\", \"end\", or \"endif\".")
|
|
(defvar csh-tab-always-indent t
|
|
"*Controls the operation of the TAB key. If t (the default), always
|
|
reindent the current line. If nil, indent the current line only if
|
|
point is at the left margin or in the line's indentation; otherwise
|
|
insert a tab.")
|
|
|
|
|
|
;;
|
|
;; ----------------------------------------> Constants containing syntax regexps
|
|
;;
|
|
(defconst csh-case-default-re
|
|
"^\\s *\\(case\\|default\\)\\b"
|
|
"Regexp used to locate grouping keywords case and default" )
|
|
|
|
(defconst csh-case-item-re "^\\s *\\(case .*\\|default\\):"
|
|
"Regexp used to match case-items")
|
|
|
|
(defconst csh-end-re "^\\s *end\\b"
|
|
"Regexp used to match keyword: end")
|
|
|
|
(defconst csh-endif-re "^\\s *endif\\b"
|
|
"Regexp used to match keyword: endif")
|
|
|
|
(defconst csh-endsw-re "^\\s *endsw\\b"
|
|
"Regexp used to match keyword: endsw")
|
|
|
|
(defconst csh-else-re "^\\s *\\belse\\(\\b\\|$\\)"
|
|
"Regexp used to match keyword: else")
|
|
|
|
(defconst csh-else-if-re "^\\s *\\belse if\\(\\b\\|$\\)"
|
|
"Regexp used to match keyword pair: else if")
|
|
|
|
(defconst csh-if-re "^\\s *if\\b.+\\(\\\\\\|\\bthen\\b\\)"
|
|
"Regexp used to match non-one-line if statements")
|
|
|
|
(defconst csh-iteration-keywords-re "^[^#\n]*\\s\"*\\b\\(while\\|foreach\\)\\b"
|
|
"Match one of the keywords: while, foreach")
|
|
|
|
(defconst csh-keywords-re
|
|
"^\\s *\\(else\\b\\|foreach\\b\\|if\\b.+\\(\\\\\\|\\bthen\\b\\)\\|switch\\b\\|while\\b\\)"
|
|
"Regexp used to detect compound command keywords: else, if, foreach, while")
|
|
|
|
(defconst csh-label-re "^\\s *[^!#$\n ]+:"
|
|
"Regexp used to match flow-control labels")
|
|
|
|
(defconst csh-multiline-re "^.*\\\\$"
|
|
"Regexp used to match a line with a statement using more lines.")
|
|
|
|
(defconst csh-switch-re "^\\s *switch\\b"
|
|
"Regexp used to match keyword: switch")
|
|
|
|
|
|
;;
|
|
;; ----------------------------------------> Variables controlling fontification
|
|
;;
|
|
(defvar csh-keywords '("@" "alias" "bg" "break" "breaksw" "case" "cd" "chdir"
|
|
"continue" "default" "dirs" "echo" "else" "end" "endif"
|
|
"endsw" "eval" "exec" "exit" "fg" "foreach" "glob" "goto"
|
|
"hashstat" "history" "if" "jobs" "kill" "limit" "login"
|
|
"logout" "limit" "notify" "onintr" "popd" "printenv"
|
|
"pushd" "rehash" "repeat" "set" "setenv" "shift" "source"
|
|
"stop" "suspend" "switch" "then" "time" "umask" "unalias"
|
|
"unhash" "unlimit" "unset" "unsetenv" "wait" "while"
|
|
;; tcsh-keywords
|
|
"alloc" "bindkey" "builtins" "complete" "echotc"
|
|
"filetest" "hup" "log" "ls-F" "nice" "nohup" "sched"
|
|
"settc" "setty" "telltc" "uncomplete" "where" "which"))
|
|
|
|
(require 'font-lock) ; need to do this before referring to font-lock-* below
|
|
|
|
(defconst csh-font-lock-keywords
|
|
;; NOTE: The order of some of the items in this list is significant. Do not
|
|
;; alphabetize or otherwise blindly rearrange.
|
|
(list
|
|
;; Comments on line 1, which are missed by syntactic fontification.
|
|
'("^#.*" 0 font-lock-comment-face)
|
|
|
|
;; Label definitions (1 means first parenthesized exp in regexp).
|
|
'("^\\s *\\([^!#$\n ]+\\):" 1 font-lock-function-name-face)
|
|
|
|
;; Label references.
|
|
'("\\b\\(goto\\|onintr\\)\\b\\s +\\([^!#$ \n\t]+\\)"
|
|
2 font-lock-function-name-face)
|
|
|
|
;; Variable settings.
|
|
'("\\(@\\|set\\|setenv\\)\\s +\\([0-9A-Za-z_]+\\b\\)"
|
|
2 font-lock-variable-name-face)
|
|
|
|
;; Variable references not inside of strings.
|
|
'("\\$[][0-9A-Za-z_#:?]+" 0 font-lock-variable-name-face)
|
|
|
|
;; Backquoted strings. 'keep' means to just fontify non-fontified text.
|
|
'("`\\(.*\\)`" 1 font-lock-reference-face keep)
|
|
|
|
;; NOTE: The following variables need to be anchored to the beginning of
|
|
;; line to prevent re-fontifying text in comments. Due to this, we
|
|
;; can only catch a finite number of occurrences. More can be added.
|
|
;; The 't' means to override previous fontification.
|
|
;;
|
|
;; Variable references inside of " strings.
|
|
'("^[^#\n]*\".*\\(\\$[][0-9A-Za-z_#:?]+\\).*\""
|
|
1 font-lock-variable-name-face t) ; 1
|
|
'("^[^#\n]*\".*\\(\\$[][0-9A-Za-z_#:?]+\\).*\\$[][0-9A-Za-z_#:?]+.*\""
|
|
1 font-lock-variable-name-face t) ; 2
|
|
(cons (concat "^[^#\n]*\".*\\(\\$[][0-9A-Za-z_#:?]+\\).*"
|
|
"\\$[][0-9A-Za-z_#:?]+.*\\$[][0-9A-Za-z_#:?]+.*\"")
|
|
(list 1 font-lock-variable-name-face t)) ; 3
|
|
;;
|
|
;; History substitutions.
|
|
'("^![^~= \n\t]+" 0 font-lock-reference-face t) ; BOL
|
|
'("^[^#\n]*[^#\\\n]\\(![^~= \n\t]+\\)" 1 font-lock-reference-face t) ; 1
|
|
'("^[^#\n]*[^#\\\n]\\(![^~= \n\t]+\\).*![^~= \n\t]+"
|
|
1 font-lock-reference-face t) ; 2
|
|
|
|
;; Keywords.
|
|
(cons (concat
|
|
"\\(\\<"
|
|
(mapconcat 'identity csh-keywords "\\>\\|\\<")
|
|
"\\>\\)")
|
|
1)
|
|
))
|
|
|
|
(put 'csh-mode 'font-lock-keywords 'csh-font-lock-keywords)
|
|
|
|
|
|
;;
|
|
;; -------------------------------------------------------> Mode-specific tables
|
|
;;
|
|
(defvar csh-mode-abbrev-table nil
|
|
"Abbrev table used while in csh mode.")
|
|
(define-abbrev-table 'csh-mode-abbrev-table ())
|
|
|
|
(defvar csh-mode-map nil
|
|
"Keymap used in csh mode")
|
|
(if csh-mode-map
|
|
()
|
|
(setq csh-mode-map (make-sparse-keymap))
|
|
;;(define-key csh-mode-map "\177" 'backward-delete-char-untabify)
|
|
(define-key csh-mode-map "\C-c\t" 'csh-completion-init-and-pickup)
|
|
(define-key csh-mode-map "\C-j" 'reindent-then-newline-and-indent)
|
|
(define-key csh-mode-map "\e\t" 'csh-complete-symbol)
|
|
(define-key csh-mode-map "\n" 'reindent-then-newline-and-indent)
|
|
(define-key csh-mode-map '[return] 'reindent-then-newline-and-indent)
|
|
(define-key csh-mode-map "\t" 'csh-indent-command)
|
|
;;(define-key csh-mode-map "\t" 'csh-indent-line)
|
|
)
|
|
|
|
(defvar csh-mode-syntax-table nil
|
|
"Syntax table used while in csh mode.")
|
|
(if csh-mode-syntax-table
|
|
;; If it's already set up, don't change it.
|
|
()
|
|
;; Else, create it from the standard table and modify entries that need to be.
|
|
(setq csh-mode-syntax-table (make-syntax-table))
|
|
(modify-syntax-entry ?& "." csh-mode-syntax-table) ; & -punctuation
|
|
(modify-syntax-entry ?* "." csh-mode-syntax-table) ; * -punctuation
|
|
(modify-syntax-entry ?- "." csh-mode-syntax-table) ; - -punctuation
|
|
(modify-syntax-entry ?= "." csh-mode-syntax-table) ; = -punctuation
|
|
(modify-syntax-entry ?+ "." csh-mode-syntax-table) ; + -punctuation
|
|
(modify-syntax-entry ?| "." csh-mode-syntax-table) ; | -punctuation
|
|
(modify-syntax-entry ?< "." csh-mode-syntax-table) ; < -punctuation
|
|
(modify-syntax-entry ?> "." csh-mode-syntax-table) ; > -punctuation
|
|
(modify-syntax-entry ?/ "." csh-mode-syntax-table) ; / -punctuation
|
|
(modify-syntax-entry ?\' "\"" csh-mode-syntax-table) ; ' -string quote
|
|
(modify-syntax-entry ?. "w" csh-mode-syntax-table) ; . -word constituent
|
|
(modify-syntax-entry ?? "w" csh-mode-syntax-table) ; ? -word constituent
|
|
|
|
;; \n - comment ender, first character of 2-char comment sequence
|
|
(modify-syntax-entry ?\n "> 1" csh-mode-syntax-table) ; # -word constituent
|
|
|
|
;; - whitespace, first character of 2-char comment sequence
|
|
(modify-syntax-entry ? " 1" csh-mode-syntax-table) ;
|
|
|
|
;; \t - whitespace, first character of 2-char comment sequence
|
|
(modify-syntax-entry ?\t " 1" csh-mode-syntax-table) ; # -word constituent
|
|
|
|
;; # - word constituent, second character of 2-char comment sequence
|
|
(modify-syntax-entry ?# "w 2" csh-mode-syntax-table) ; # -word constituent
|
|
)
|
|
|
|
|
|
;;
|
|
;; ------------------------------------------------------------------> Functions
|
|
;;
|
|
(defun csh-current-line ()
|
|
"Return the vertical position of point in the buffer.
|
|
Top line is 1."
|
|
(+ (count-lines (point-min) (point))
|
|
(if (= (current-column) 0) 1 0))
|
|
)
|
|
|
|
(defun csh-get-compound-level
|
|
(begin-re end-re anchor-point &optional balance-list)
|
|
"Determine how much to indent this structure. Return a list (level line)
|
|
of the matching compound command or nil if no match found."
|
|
(let*
|
|
(;; Locate the next compound begin keyword bounded by point-min
|
|
(match-point (if (re-search-backward begin-re (point-min) t)
|
|
(match-beginning 0) 0))
|
|
(nest-column (if (zerop match-point)
|
|
1
|
|
(progn
|
|
(goto-char match-point)
|
|
(current-indentation))))
|
|
(nest-list (cons 0 0)) ;; sentinel cons since cdr is >= 1
|
|
)
|
|
(if (zerop match-point)
|
|
nil ;; graceful exit from recursion
|
|
(progn
|
|
(if (nlistp balance-list)
|
|
(setq balance-list (list)))
|
|
;; Now search forward from matching start keyword for end keyword
|
|
(while (and (consp nest-list) (zerop (cdr nest-list))
|
|
(re-search-forward end-re anchor-point t))
|
|
(if (not (memq (point) balance-list))
|
|
(progn
|
|
(setq balance-list (cons (point) balance-list))
|
|
(goto-char match-point) ;; beginning of compound cmd
|
|
(setq nest-list
|
|
(csh-get-compound-level begin-re end-re
|
|
anchor-point balance-list))
|
|
)))
|
|
|
|
(cond ((consp nest-list)
|
|
(if (zerop (cdr nest-list))
|
|
(progn
|
|
(goto-char match-point)
|
|
(cons nest-column (csh-current-line)))
|
|
nest-list))
|
|
(t nil)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
(defun csh-get-nest-level ()
|
|
"Return a 2 element list (nest-level nest-line) describing where the
|
|
current line should nest."
|
|
(let ((case-fold-search)
|
|
(level))
|
|
(save-excursion
|
|
(forward-line -1)
|
|
(while (and (not (bobp))
|
|
(null level))
|
|
(if (and (not (looking-at "^\\s *$"))
|
|
(not (save-excursion
|
|
(forward-line -1)
|
|
(beginning-of-line)
|
|
(looking-at csh-multiline-re)))
|
|
(not (looking-at csh-comment-regexp)))
|
|
(setq level (cons (current-indentation)
|
|
(csh-current-line)))
|
|
(forward-line -1)
|
|
);; if
|
|
);; while
|
|
(if (null level)
|
|
(cons (current-indentation) (csh-current-line))
|
|
level)
|
|
)
|
|
)
|
|
)
|
|
|
|
(defun csh-get-nester-column (nest-line)
|
|
"Return the column to indent to with respect to nest-line taking
|
|
into consideration keywords and other nesting constructs."
|
|
(save-excursion
|
|
(let ((fence-post)
|
|
(case-fold-search)
|
|
(start-line (csh-current-line)))
|
|
;;
|
|
;; Handle case item indentation constructs for this line
|
|
(cond ((looking-at csh-case-item-re)
|
|
;; This line is a case item...
|
|
(save-excursion
|
|
(goto-line nest-line)
|
|
(let ((fence-post (save-excursion (end-of-line) (point))))
|
|
(cond ((re-search-forward csh-switch-re fence-post t)
|
|
;; If this is the first case under the switch, indent.
|
|
(goto-char (match-beginning 0))
|
|
(+ (current-indentation) csh-case-item-offset))
|
|
|
|
((re-search-forward csh-case-item-re fence-post t)
|
|
;; If this is another case right under a previous case
|
|
;; without intervening code, stay at the same
|
|
;; indentation.
|
|
(goto-char (match-beginning 0))
|
|
(current-indentation))
|
|
|
|
(t
|
|
;; Else, this is a new case. Outdent.
|
|
(- (current-indentation) csh-case-item-offset))
|
|
)
|
|
)))
|
|
(t;; Not a case-item. What to do relative to the nest-line?
|
|
(save-excursion
|
|
(goto-line nest-line)
|
|
(setq fence-post (save-excursion (end-of-line) (point)))
|
|
(save-excursion
|
|
(cond
|
|
;;
|
|
;; Check if we are in a continued statement
|
|
((and (looking-at csh-multiline-re)
|
|
(save-excursion
|
|
(goto-line (1- start-line))
|
|
(looking-at csh-multiline-re)))
|
|
(if (looking-at ".*[\'\"]\\\\")
|
|
;; If this is a continued string, indent under
|
|
;; opening quote.
|
|
(progn
|
|
(re-search-forward "[\'\"]")
|
|
(forward-char -1))
|
|
(if (looking-at ".*([^\)\n]*\\\\")
|
|
;; Else if this is a continued parenthesized
|
|
;; list, indent after paren.
|
|
(re-search-forward "(" fence-post t)
|
|
;; Else, indent after whitespace after first word.
|
|
(re-search-forward "[^ \t]+[ \t]+" fence-post t)))
|
|
(current-column))
|
|
|
|
;; In order to locate the column of the keyword,
|
|
;; which might be embedded within a case-item,
|
|
;; it is necessary to use re-search-forward.
|
|
;; Search by literal case, since shell is
|
|
;; case-sensitive.
|
|
((re-search-forward csh-keywords-re fence-post t)
|
|
(goto-char (match-beginning 1))
|
|
(if (looking-at csh-switch-re)
|
|
(+ (current-indentation) csh-case-item-offset)
|
|
(+ (current-indentation)
|
|
(if (null csh-indent)
|
|
2 csh-indent)
|
|
)))
|
|
|
|
((re-search-forward csh-case-default-re fence-post t)
|
|
(if (null csh-indent)
|
|
(progn
|
|
(goto-char (match-end 1))
|
|
(+ (current-indentation) 1))
|
|
(progn
|
|
(goto-char (match-beginning 1))
|
|
(+ (current-indentation) csh-indent))
|
|
))
|
|
|
|
;;
|
|
;; Now detect first statement under a case item
|
|
((looking-at csh-case-item-re)
|
|
(if (null csh-case-indent)
|
|
(progn
|
|
(re-search-forward csh-case-item-re fence-post t)
|
|
(goto-char (match-end 1))
|
|
(+ (current-column) 1))
|
|
(+ (current-indentation) csh-case-indent)))
|
|
|
|
;;
|
|
;; If this is the first statement under a control-flow
|
|
;; label, indent one level.
|
|
((csh-looking-at-label)
|
|
(+ (current-indentation) csh-indent))
|
|
|
|
;; This is hosed when using current-column
|
|
;; and there is a multi-command expression as the
|
|
;; nester.
|
|
(t (current-indentation)))
|
|
)
|
|
));; excursion over
|
|
);; Not a case-item
|
|
);;let
|
|
);; excursion
|
|
);; defun
|
|
|
|
(defun csh-indent-command ()
|
|
"Indent current line relative to containing block and allow for
|
|
csh-tab-always-indent customization"
|
|
(interactive)
|
|
(let (case-fold-search)
|
|
(cond ((save-excursion
|
|
(skip-chars-backward " \t")
|
|
(bolp))
|
|
(csh-indent-line))
|
|
(csh-tab-always-indent
|
|
(save-excursion
|
|
(csh-indent-line)))
|
|
(t (insert-tab))
|
|
))
|
|
)
|
|
|
|
(defun csh-indent-line ()
|
|
"Indent current line as far as it should go according
|
|
to the syntax/context"
|
|
(interactive)
|
|
(let (case-fold-search)
|
|
(save-excursion
|
|
(beginning-of-line)
|
|
(if (bobp)
|
|
nil
|
|
;;
|
|
;; Align this line to current nesting level
|
|
(let*
|
|
(
|
|
(level-list (csh-get-nest-level)) ; Where to nest against
|
|
;; (last-line-level (car level-list))
|
|
(this-line-level (current-indentation))
|
|
(nester-column (csh-get-nester-column (cdr level-list)))
|
|
(struct-match (csh-match-structure-and-reindent))
|
|
)
|
|
(if struct-match
|
|
(setq nester-column struct-match))
|
|
(if (eq nester-column this-line-level)
|
|
nil
|
|
(beginning-of-line)
|
|
(let ((beg (point)))
|
|
(back-to-indentation)
|
|
(delete-region beg (point)))
|
|
(indent-to nester-column))
|
|
);; let*
|
|
);; if
|
|
);; excursion
|
|
;;
|
|
;; Position point on this line
|
|
(let*
|
|
(
|
|
(this-line-level (current-indentation))
|
|
(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*
|
|
);; let
|
|
);; defun
|
|
|
|
(defun csh-indent-region (start end)
|
|
"From start to end, indent each line."
|
|
;; The algorithm is just moving through the region line by line with
|
|
;; the match noise turned off. Only modifies nonempty lines.
|
|
(save-excursion
|
|
(let (csh-match-and-tell
|
|
(endmark (copy-marker end)))
|
|
|
|
(goto-char start)
|
|
(beginning-of-line)
|
|
(setq start (point))
|
|
(while (> (marker-position endmark) start)
|
|
(if (not (and (bolp) (eolp)))
|
|
(csh-indent-line))
|
|
(forward-line 1)
|
|
(setq start (point)))
|
|
|
|
(set-marker endmark nil)
|
|
)
|
|
)
|
|
)
|
|
|
|
(defun csh-line-to-string ()
|
|
"From point, construct a string from all characters on
|
|
current line"
|
|
(skip-chars-forward " \t") ;; skip tabs as well as spaces
|
|
(buffer-substring (point)
|
|
(progn
|
|
(end-of-line 1)
|
|
(point))))
|
|
|
|
(defun csh-looking-at-label ()
|
|
"Return true if current line is a label (not the default: case label)."
|
|
(and
|
|
(looking-at csh-label-re)
|
|
(not (looking-at "^\\s *default:"))))
|
|
|
|
(defun csh-match-indent-level (begin-re end-re)
|
|
"Match the compound command and indent. Return nil on no match,
|
|
indentation to use for this line otherwise."
|
|
(interactive)
|
|
(let* ((case-fold-search)
|
|
(nest-list
|
|
(save-excursion
|
|
(csh-get-compound-level begin-re end-re (point))
|
|
))
|
|
) ;; bindings
|
|
(if (null nest-list)
|
|
(progn
|
|
(if csh-match-and-tell
|
|
(message "No matching compound command"))
|
|
nil) ;; Propagate a miss.
|
|
(let* (
|
|
(nest-level (car nest-list))
|
|
(match-line (cdr nest-list))
|
|
) ;; bindings
|
|
(if csh-match-and-tell
|
|
(save-excursion
|
|
(goto-line match-line)
|
|
(message "Matched ... %s" (csh-line-to-string))
|
|
) ;; excursion
|
|
) ;; if csh-match-and-tell
|
|
nest-level ;;Propagate a hit.
|
|
) ;; let*
|
|
) ;; if
|
|
) ;; let*
|
|
) ;; defun csh-match-indent-level
|
|
|
|
(defun csh-match-structure-and-reindent ()
|
|
"If the current line matches one of the indenting keywords
|
|
or one of the control structure ending keywords then reindent. Also
|
|
if csh-match-and-tell is non-nil the matching structure will echo in
|
|
the minibuffer"
|
|
(interactive)
|
|
(let (case-fold-search)
|
|
(save-excursion
|
|
(beginning-of-line)
|
|
(cond ((looking-at csh-else-re)
|
|
(csh-match-indent-level csh-if-re csh-endif-re))
|
|
((looking-at csh-else-if-re)
|
|
(csh-match-indent-level csh-if-re csh-endif-re))
|
|
((looking-at csh-endif-re)
|
|
(csh-match-indent-level csh-if-re csh-endif-re))
|
|
((looking-at csh-end-re)
|
|
(csh-match-indent-level csh-iteration-keywords-re csh-end-re))
|
|
((looking-at csh-endsw-re)
|
|
(csh-match-indent-level csh-switch-re csh-endsw-re))
|
|
((csh-looking-at-label)
|
|
;; Flush control-flow labels left since they don't nest.
|
|
0)
|
|
;;
|
|
(t nil)
|
|
);; cond
|
|
)
|
|
))
|
|
|
|
;;;###autoload
|
|
(defun csh-mode ()
|
|
"csh-mode 2.0 - Major mode for editing csh and tcsh scripts.
|
|
Special key bindings and commands:
|
|
\\{csh-mode-map}
|
|
Variables controlling indentation style:
|
|
csh-indent
|
|
Indentation of csh statements with respect to containing block.
|
|
Default value is 4.
|
|
csh-case-indent
|
|
Additional indentation for statements under case items.
|
|
Default value is nil which will align the statements one position
|
|
past the \")\" of the pattern.
|
|
csh-case-item-offset
|
|
Additional indentation for case items within a case statement.
|
|
Default value is 2.
|
|
csh-tab-always-indent
|
|
Controls the operation of the TAB key. If t (the default), always
|
|
reindent the current line. If nil, indent the current line only if
|
|
point is at the left margin or in the line's indentation; otherwise
|
|
insert a tab.
|
|
csh-match-and-tell
|
|
If non-nil echo in the minibuffer the matching compound command
|
|
for the \"done\", \"}\", \"fi\", or \"endsw\". Default value is t.
|
|
|
|
csh-comment-regexp
|
|
Regular expression used to recognize comments. Customize to support
|
|
csh-like languages. Default value is \"\^\\\\s *#\".
|
|
|
|
Style Guide.
|
|
By setting
|
|
(setq csh-indent default-tab-width)
|
|
|
|
The following style is obtained:
|
|
|
|
if [ -z $foo ]
|
|
then
|
|
bar # <-- csh-group-offset is additive to csh-indent
|
|
foo
|
|
fi
|
|
|
|
By setting
|
|
(setq csh-indent default-tab-width)
|
|
(setq csh-group-offset (- 0 csh-indent))
|
|
|
|
The following style is obtained:
|
|
|
|
if [ -z $foo ]
|
|
then
|
|
bar
|
|
foo
|
|
fi
|
|
|
|
By setting
|
|
(setq csh-case-item-offset 1)
|
|
(setq csh-case-indent nil)
|
|
|
|
The following style is obtained:
|
|
|
|
case x in *
|
|
foo) bar # <-- csh-case-item-offset
|
|
baz;; # <-- csh-case-indent aligns with \")\"
|
|
foobar) foo
|
|
bar;;
|
|
endsw
|
|
|
|
By setting
|
|
(setq csh-case-item-offset 1)
|
|
(setq csh-case-indent 6)
|
|
|
|
The following style is obtained:
|
|
|
|
case x in *
|
|
foo) bar # <-- csh-case-item-offset
|
|
baz;; # <-- csh-case-indent
|
|
foobar) foo
|
|
bar;;
|
|
endsw
|
|
|
|
|
|
Installation:
|
|
Put csh-mode.el in some directory in your load-path.
|
|
Put the following forms in your .emacs file.
|
|
|
|
(setq auto-mode-alist
|
|
(append auto-mode-alist
|
|
(list
|
|
'(\"\\\\.csh$\" . csh-mode)
|
|
'(\"\\\\.login\" . csh-mode))))
|
|
|
|
(setq csh-mode-hook
|
|
(function (lambda ()
|
|
(font-lock-mode 1) ;; font-lock the buffer
|
|
(setq csh-indent 8)
|
|
(setq csh-tab-always-indent t)
|
|
(setq csh-match-and-tell t)
|
|
(setq csh-align-to-keyword t) ;; Turn on keyword alignment
|
|
)))"
|
|
(interactive)
|
|
(kill-all-local-variables)
|
|
(use-local-map csh-mode-map)
|
|
(setq major-mode 'csh-mode)
|
|
(setq mode-name "Csh")
|
|
(setq local-abbrev-table csh-mode-abbrev-table)
|
|
(set-syntax-table csh-mode-syntax-table)
|
|
(make-local-variable 'indent-line-function)
|
|
(setq indent-line-function 'csh-indent-line)
|
|
(make-local-variable 'indent-region-function)
|
|
(setq indent-region-function 'csh-indent-region)
|
|
(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 "#+ *")
|
|
;;
|
|
;; config font-lock mode
|
|
(make-local-variable 'font-lock-keywords)
|
|
(setq font-lock-keywords csh-font-lock-keywords)
|
|
;;
|
|
;; Let the user customize
|
|
(run-hooks 'csh-mode-hook)
|
|
) ;; defun
|
|
|
|
;;
|
|
;; Completion code supplied by Haavard Rue <hrue@imf.unit.no>.
|
|
;;
|
|
;;
|
|
;; add a completion with a given type to the list
|
|
;;
|
|
(defun csh-addto-alist (completion type)
|
|
(setq csh-completion-list
|
|
(append csh-completion-list
|
|
(list (cons completion type)))))
|
|
|
|
(defun csh-bol-point ()
|
|
(save-excursion
|
|
(beginning-of-line)
|
|
(point)))
|
|
|
|
(defun csh-complete-symbol ()
|
|
"Perform completion."
|
|
(interactive)
|
|
(let* ((case-fold-search)
|
|
(end (point))
|
|
(beg (unwind-protect
|
|
(save-excursion
|
|
(backward-sexp 1)
|
|
(while (= (char-syntax (following-char)) ?\')
|
|
(forward-char 1))
|
|
(point))))
|
|
(pattern (buffer-substring beg end))
|
|
(predicate
|
|
;;
|
|
;; ` or $( mark a function
|
|
;;
|
|
(save-excursion
|
|
(goto-char beg)
|
|
(if (or
|
|
(save-excursion
|
|
(backward-char 1)
|
|
(looking-at "`"))
|
|
(save-excursion
|
|
(backward-char 2)
|
|
(looking-at "\\$(")))
|
|
(function (lambda (sym)
|
|
(equal (cdr sym) csh-completion-type-function)))
|
|
;;
|
|
;; a $, ${ or ${# mark a variable
|
|
;;
|
|
(if (or
|
|
(save-excursion
|
|
(backward-char 1)
|
|
(looking-at "\\$"))
|
|
(save-excursion
|
|
(backward-char 2)
|
|
(looking-at "\\${"))
|
|
(save-excursion
|
|
(backward-char 3)
|
|
(looking-at "\\${#")))
|
|
(function (lambda (sym)
|
|
(equal (cdr sym)
|
|
csh-completion-type-var)))
|
|
;;
|
|
;; don't know. use 'em all
|
|
;;
|
|
(function (lambda (sym) t))))))
|
|
;;
|
|
(completion (try-completion pattern csh-completion-list predicate)))
|
|
;;
|
|
(cond ((eq completion t))
|
|
;;
|
|
;; oops, what is this ?
|
|
;;
|
|
((null completion)
|
|
(message "Can't find completion for \"%s\"" pattern))
|
|
;;
|
|
;; insert
|
|
;;
|
|
((not (string= pattern completion))
|
|
(delete-region beg end)
|
|
(insert completion))
|
|
;;
|
|
;; write possible completion in the minibuffer,
|
|
;; use this instead of a seperate buffer (usual)
|
|
;;
|
|
(t
|
|
(let ((list (all-completions pattern csh-completion-list predicate))
|
|
(string ""))
|
|
(while list
|
|
(progn
|
|
(setq string (concat string (format "%s " (car list))))
|
|
(setq list (cdr list))))
|
|
(message string))))))
|
|
|
|
;;
|
|
;; init the list and pickup all
|
|
;;
|
|
(defun csh-completion-init-and-pickup ()
|
|
(interactive)
|
|
(let (case-fold-search)
|
|
(csh-completion-list-init)
|
|
(csh-pickup-all)))
|
|
|
|
;;
|
|
;; init the list
|
|
;;
|
|
(defun csh-completion-list-init ()
|
|
(interactive)
|
|
(setq csh-completion-list
|
|
(list
|
|
(cons "break" csh-completion-type-misc)
|
|
(cons "breaksw" csh-completion-type-misc)
|
|
(cons "case" csh-completion-type-misc)
|
|
(cons "continue" csh-completion-type-misc)
|
|
(cons "endif" csh-completion-type-misc)
|
|
(cons "exit" csh-completion-type-misc)
|
|
(cons "foreach" csh-completion-type-misc)
|
|
(cons "if" csh-completion-type-misc)
|
|
(cons "while" csh-completion-type-misc))))
|
|
|
|
(defun csh-eol-point ()
|
|
(save-excursion
|
|
(end-of-line)
|
|
(point)))
|
|
|
|
(defun csh-pickup-all ()
|
|
"Pickup all completions in buffer."
|
|
(interactive)
|
|
(csh-pickup-completion-driver (point-min) (point-max) t))
|
|
|
|
(defun csh-pickup-completion (regexp type match pmin pmax)
|
|
"Pickup completion in region and addit to the list, if not already
|
|
there."
|
|
(let ((i 0) kw obj)
|
|
(save-excursion
|
|
(goto-char pmin)
|
|
(while (and
|
|
(re-search-forward regexp pmax t)
|
|
(match-beginning match)
|
|
(setq kw (buffer-substring
|
|
(match-beginning match)
|
|
(match-end match))))
|
|
(progn
|
|
(setq obj (assoc kw csh-completion-list))
|
|
(if (or (equal nil obj)
|
|
(and (not (equal nil obj))
|
|
(not (= type (cdr obj)))))
|
|
(progn
|
|
(setq i (1+ i))
|
|
(csh-addto-alist kw type))))))
|
|
i))
|
|
|
|
(defun csh-pickup-completion-driver (pmin pmax message)
|
|
"Driver routine for csh-pickup-completion."
|
|
(if message
|
|
(message "pickup completion..."))
|
|
(let* (
|
|
(i1
|
|
(csh-pickup-completion csh-completion-regexp-var
|
|
csh-completion-type-var
|
|
csh-completion-match-var
|
|
pmin pmax))
|
|
(i2
|
|
(csh-pickup-completion csh-completion-regexp-var2
|
|
csh-completion-type-var
|
|
csh-completion-match-var2
|
|
pmin pmax))
|
|
(i3
|
|
(csh-pickup-completion csh-completion-regexp-function
|
|
csh-completion-type-function
|
|
csh-completion-match-function
|
|
pmin pmax)))
|
|
(if message
|
|
(message "pickup %d variables and %d functions." (+ i1 i2) i3))))
|
|
|
|
(defun csh-pickup-this-line ()
|
|
"Pickup all completions in current line."
|
|
(interactive)
|
|
(csh-pickup-completion-driver (csh-bol-point) (csh-eol-point) nil))
|
|
|
|
|
|
(provide 'csh-mode)
|
|
;;; csh-mode.el ends here
|