2199 lines
66 KiB
EmacsLisp
2199 lines
66 KiB
EmacsLisp
;;; plsql.el --- Programming support for PL/SQL code
|
|
|
|
;; Copyright (C) 2001, 2002 by Free Software Foundation, Inc.
|
|
|
|
;; Author: Kahlil (Kal) HODGSON <dorge@tpg.com.au>
|
|
;; X-URL: http://www.emacswiki.org/elisp/plsql.el
|
|
;; Keywords: languages
|
|
;; Time-stamp: "2002-11-07 01:26:20 kahlil"
|
|
;; Version: 0.8.0
|
|
|
|
;; This file is NOT part of GNU Emacs.
|
|
|
|
;; This file 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 2, or (at your option)
|
|
;; any later version.
|
|
|
|
;; This file 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 Emacs; see the file COPYING. If not, write to
|
|
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
;; Boston, MA 02111-1307, USA.
|
|
|
|
;;;_ * Commentary
|
|
|
|
;; Provides a major mode (PL/SQL) that supports writing PL/SQL code.
|
|
;;
|
|
;; This `plsql-mode' is an extension of `sql-mode' and uses that mode
|
|
;; to provide keyword fontification, symbol-tables, and interaction
|
|
;; with the database. In addition to foxing some bugs this mode
|
|
;; provides the following:
|
|
;;
|
|
;; 1. Fast and sophisticated indentation function `plsql-indent'
|
|
;; (normally bound to TAB) which can help to present (and maintain)
|
|
;; complex code in a readable form.
|
|
;;
|
|
;; 2. Imenu support which allows you to jump to specific modules using
|
|
;; either the "Contents" menu or speedbar.
|
|
;;
|
|
;; 3. Align support which allows you to automatically "beautify" code
|
|
;; as you write by aligning certain constructs e.g. `align-current'
|
|
;; will align all =, :=, and => tokens in the current paragraph.
|
|
;; XEmacs users may have to get a copy of the align package to use
|
|
;; this feature.
|
|
|
|
;; This mode was written for FSF Emacs but has been tested in XEmacs.
|
|
|
|
;;;_ * Installation:
|
|
|
|
;; Place this file somewhere in your load path and byte-compile (!) it.
|
|
;; Also check the customizable variables for this package
|
|
;; `plsql-indent' and `plsql-uses-font-lock'. You can then set the
|
|
;; plsql major mode interactively via the command `plsql-mode' or
|
|
;; automatically via filename extensions by adding the following to
|
|
;; your dot emacs file:
|
|
;;
|
|
;; (setq auto-mode-alist
|
|
;; (append
|
|
;; '(("\\.\\(p\\(?:k[bg]\\|ls\\)\\|sql\\)\\'" . plsql-mode))
|
|
;; auto-mode-alist))
|
|
;;
|
|
;; If you use the speedbar you may want to set the following
|
|
;;
|
|
;; (speedbar-add-supported-extension "pls")
|
|
;; (speedbar-add-supported-extension "pkg")
|
|
;; (speedbar-add-supported-extension "pkb")
|
|
;; (speedbar-add-supported-extension "sql")
|
|
;;
|
|
;; as per your favorite filename extensions.
|
|
|
|
;; Enjoy!
|
|
|
|
;;;_ * Known Bugs
|
|
|
|
;; This has only been tested on a handful of programs I had to write
|
|
;; for a specific job. That code was reasonably complex but certainly
|
|
;; did not cover every aspect of PL/SQL so there are bound to be some
|
|
;; bugs. I am not currently working with this language so further
|
|
;; development has been suspended; however, if you find a bug please
|
|
;; email me with the offending code and I'll have a go at fixing it:-)
|
|
|
|
;; (1) A comment flush against a keyword/statement end produces bad
|
|
;; indentation. Probably too hard to fix. Does anyone actually like
|
|
;; to do this?
|
|
|
|
;; (2) Don't know if TAB gets bound properly in XEmacs. If anyone can
|
|
;; confirm this please email me (saves me installing XEmacs:-)
|
|
|
|
;; (3) The some inconsistencies will screw up the indention that follows e.g.
|
|
|
|
;; if CONDITION then
|
|
;; STATEMENT
|
|
;; else STATEMENT;
|
|
;; STATEMENT;
|
|
;; end if;
|
|
|
|
;; is screwed up but
|
|
|
|
;; if CONDITION then
|
|
;; STATEMENT
|
|
;; else
|
|
;; STATEMENT;
|
|
;; STATEMENT;
|
|
;; end if;
|
|
|
|
;; is O.K. Let me know if you really want to do the former and I'll
|
|
;; fix it :-)
|
|
|
|
;;;_ * Credits
|
|
|
|
;; Inspired by an pls-mode.el by Dmitry Nizhegorodov (Oracle) and
|
|
;; plsql-mode.el by Karel Sprenger.
|
|
|
|
;; Thanks to Arch Murphy for noting a problem with XEmacs, to Brett
|
|
;; for noting a problem with push, and Andreas Zielke for noting a
|
|
;; indentation problem with "blocks within blocks". Thanks to Jérome
|
|
;; Haguet for spotting another indentation bug and for suggesting
|
|
;; `plsql-indent-region'. Thanks to Alex Schroeder for compilation
|
|
;; suggestions and suggestions regarding SQL level indentation. Thanks
|
|
;; to Mark Buckle for convincing me to support block-less PL/SQL and
|
|
;; inspiring a huge code cleanup.
|
|
|
|
;;;_ * To do
|
|
|
|
;; (*) Check logic on calling `plsql-sql-statement-indent' do we still
|
|
;; need to do this.
|
|
|
|
;; (1) BNF for PL/SQL to add to semantic bovinator (Eeek!)
|
|
;; (2) Imenu for local modules, global variables? (really need
|
|
;; bovinator for that)
|
|
;; (3) Object oriented extensions? What are they? Haven't used this
|
|
;; stuff yet.
|
|
;; (4) Movement functions. Does anyone actually use these? Speedbar is
|
|
;; faster?
|
|
;; (5) Filling comments? Defer to rebox.
|
|
;; (6) Set the current indentation context as a text-property. This
|
|
;; could vastly simplify the code. Perhaps a new package entirely.
|
|
;; (7) Develop a test file containing all the PL/SQL contortions I can
|
|
;; think of.
|
|
;; (8) Pass on comment-indent and string-indent to user definable
|
|
;; functions.
|
|
;; (9) Can we have anonymous blocks inside exception sections?
|
|
;; (10) Make "align" line up anything that is highlighted?
|
|
|
|
;;;_ * Code
|
|
|
|
;; Note to hackers: explanatory notes scatter throughout.
|
|
;; Suggested starting point: search for ";;;;; Top Level"
|
|
|
|
(defgroup plsql nil "")
|
|
|
|
;;;; Indentation:
|
|
;;;;; User Variables:
|
|
|
|
(defcustom plsql-indent 3
|
|
"*Number of columns of indentation for each level."
|
|
:group 'plsql
|
|
:type 'number)
|
|
|
|
(defcustom plsql-uses-font-lock t
|
|
"*Indicate whether font-lock properties can be use to speed up indentation.
|
|
This is up to 3 times faster, and hence, highly recommended."
|
|
:group 'plsql
|
|
:type 'boolean)
|
|
|
|
(defcustom plsql-mode-hook '()
|
|
"*Hook for customizing `plsql-mode'."
|
|
:type 'hook
|
|
:group 'plsql)
|
|
|
|
|
|
;;;_ + Local Variables
|
|
|
|
(defvar plsql-debug nil
|
|
"If t then we rebuild everything on reload. Useful for debugging.")
|
|
|
|
(eval-when-compile (setq plsql-debug t))
|
|
|
|
(defun plsql-toggle-debug () "Toggle value of plsql-debug."
|
|
(interactive)
|
|
(message "plsql-debug is %s" (setq plsql-debug (not plsql-debug))))
|
|
|
|
(defun plsql-reset (variable)
|
|
"t iff VARIABLE should be reset"
|
|
(or plsql-debug
|
|
(null variable)
|
|
(string-equal variable "")))
|
|
|
|
(defvar plsql-indent-function-stack nil
|
|
"Record the indent functions use to indent the current line.")
|
|
|
|
(defvar plsql-white-space-re "[ \t\n\r()]"
|
|
"Regular expression matching whitespace or anything that delimits
|
|
identifiers.")
|
|
|
|
;; this includes () since they can delimit
|
|
(when (plsql-reset plsql-white-space-re)
|
|
(setq plsql-white-space-re "[ \t\n\r()]" ))
|
|
|
|
;;;_ + Predicates
|
|
|
|
(defun plsql-in-comment-p (&optional limit)
|
|
"Return non-nil iff point is in a comment.
|
|
LIMIT is a point before the current point used to limit the partial parse."
|
|
(or (looking-at "/\\*")
|
|
(nth 4 (parse-partial-sexp limit (point)))))
|
|
|
|
(defun plsql-in-string-p (&optional limit)
|
|
"Return non-nil iff point is in a string.
|
|
LIMIT is a point before the current point used to limit the partial parse."
|
|
(nth 3 (parse-partial-sexp limit (point))))
|
|
|
|
;; alternatively using font-lock faces is even faster
|
|
|
|
(defun plsql-comment-face-p (&optional limit)
|
|
"Return non-nil iff text at point is displayed in a `font-lock-comment-face'."
|
|
(eq (get-text-property (point) 'face) 'font-lock-comment-face))
|
|
|
|
(defun plsql-string-face-p (&optional limit)
|
|
"Return non-nil iff text at point is displayed in a `font-lock-string-face'."
|
|
(eq (get-text-property (point) 'face) 'font-lock-string-face))
|
|
|
|
(defvar plsql-in-comment-predicate 'plsql-in-comment-p
|
|
"Function used to determine if point is inside a comment.")
|
|
|
|
(defvar plsql-in-string-predicate 'plsql-in-string-p
|
|
"Function used to determine if point is inside a string.")
|
|
|
|
;;;_ + RegExp Search
|
|
|
|
;; defining the following functions in-line give a small increase in speed
|
|
|
|
(defsubst plsql-re-search-backward
|
|
(regexp &optional bound no-error count limit)
|
|
|
|
"Search backwards for REGEXP ignoring comments and strings.
|
|
Optional parameters BOUND, NO-ERROR and COUNT behave as for `re-search-backward'.
|
|
LIMIT is used to bound partial parse and is assumed to be before the current point."
|
|
|
|
;; cant think of any better logic than this
|
|
|
|
(let ((not-found t) ;; invert the sense to minimise calls to `not'
|
|
(limit (or limit 1)))
|
|
(while (and not-found
|
|
(re-search-backward regexp bound no-error count))
|
|
(forward-char 1) ;; point is on first char of match
|
|
(unless (or (funcall plsql-in-comment-predicate limit)
|
|
(funcall plsql-in-string-predicate limit))
|
|
(setq not-found nil))
|
|
(forward-char -1) ;; point is before first char of match
|
|
)
|
|
|
|
(not not-found)))
|
|
|
|
(defsubst plsql-re-search-forward
|
|
(regexp &optional bound no-error count limit)
|
|
|
|
"Search forwards for REGEXP ignoring comments and strings.
|
|
Optional parameters BOUND, NO-ERROR and COUNT behave as for `re-search-forward'.
|
|
LIMIT is used to bound partial parse and is assumed to be before the current point."
|
|
|
|
(let ((not-found t) ;; invert the sense to minimise calls to `not'
|
|
(limit (or limit 1)))
|
|
(while (and not-found
|
|
(re-search-forward regexp bound no-error count))
|
|
|
|
(forward-char -1) ;; point is on last char of match
|
|
(unless (or (funcall plsql-in-comment-predicate limit)
|
|
(funcall plsql-in-string-predicate limit))
|
|
(setq not-found nil))
|
|
(forward-char 1) ;; point is after last char of match
|
|
)
|
|
(not not-found)))
|
|
|
|
;;;_ + Statement Level
|
|
|
|
;; There are a few special sub-statement forms that will require slightly
|
|
;; different indentation.
|
|
;;
|
|
;; a) expressions in parentheses e.g. function parameters
|
|
;; b) SQL (block) statements e.g. SELECT statements in a cursor
|
|
;; c) statements that span more then one line.
|
|
|
|
(defvar plsql-leading-identifier-re ""
|
|
"Regexp that will match a PL/SQL identifier." )
|
|
|
|
(when (plsql-reset plsql-leading-identifier-re)
|
|
(setq plsql-leading-identifier-re "^[ \t\n\r]*\\([_#:$a-z,A-Z]+\\)"))
|
|
|
|
;; SYMBOL TABLE?
|
|
|
|
(defvar plsql-sql-statement-re ""
|
|
"Regexp matching key terms indicating that we are within an SQL statement.")
|
|
|
|
;; Borrowed this from sql-indent.el
|
|
;; plus some from Oracle 9i SQL reference
|
|
;; removed some that conflict with some PL/SQL keywords
|
|
;; do we need all these?
|
|
|
|
;; Exclude words longer that 8 characters.
|
|
|
|
;; This RE is usually referenced by looking at with point at the
|
|
;; beginning of the indentation so we can we don't need to check for
|
|
;; leading word boundary.
|
|
|
|
(when (plsql-reset plsql-sql-statement-re)
|
|
(setq plsql-sql-statement-re
|
|
(concat
|
|
"\\("
|
|
(regexp-opt
|
|
(list
|
|
"access" "add" "all" "alter" "analyse" "and" "any" "as" "asc"
|
|
;; "associate"
|
|
"audit"
|
|
;; "authorization"
|
|
"avg" "between"
|
|
"by"
|
|
;; "char"
|
|
"check" "cluster"
|
|
;; "close"
|
|
;; "cobol"
|
|
"column" "comment" "commit" "compress" "connect" "continue"
|
|
;; "count"
|
|
"create" "current"
|
|
;; "date" "decimal"
|
|
"declare"
|
|
;; "default"
|
|
"delete"
|
|
"desc" "distinct"
|
|
;; "double"
|
|
"drop"
|
|
;; "else"
|
|
"escape"
|
|
;; "exclusive"
|
|
"exec" "exists" "explain"
|
|
;; "fetch"
|
|
;; "file"
|
|
"float"
|
|
;; "for"
|
|
"foreign" "fortran" "found" "from" "go"
|
|
;; "goto"
|
|
"grant" "group" "having" "identified" "immediate" "in" "increment"
|
|
"index"
|
|
;; "indicator"
|
|
"initial" "insert" "integer"
|
|
;; "intersect"
|
|
"into"
|
|
;; "is"
|
|
"key"
|
|
;; "language"
|
|
"level" "like" "lock" "long" "max"
|
|
;; "maxextents"
|
|
"min" "minus"
|
|
;; "mlslabel"
|
|
"mode" "modify" "module" "noaudit"
|
|
;; "nocompress"
|
|
"not" "nowait" "null"
|
|
;; "number"
|
|
"numeric" "of" "offline" "on" "online"
|
|
;; "open"
|
|
"option" "or" "order"
|
|
;; "pascal"
|
|
"pctfree" "pli"
|
|
;; "precision"
|
|
"primary" "prior"
|
|
;; "privileges"
|
|
"public"
|
|
;; "raw"
|
|
;; "references"
|
|
"rename" "resource" "revoke" "revoke" "rollback"
|
|
"row" "rowid" "rownum" "rows"
|
|
;; "savepoint"
|
|
"schema" "section" "select" "session" "set" "share" "size"
|
|
;; "smallint"
|
|
"some" "sqlcode" "sqlerror" "start"
|
|
;; "successful" "sum"
|
|
"synonym" "sysdate" "table"
|
|
;; "then"
|
|
"to" "trigger" "truncate"
|
|
"uid" "union" "unique" "update" "user" "validate" "values"
|
|
;;"varchar" "varchar2"
|
|
"view" "whenever" "where" "with" "work"
|
|
))
|
|
"\\)"
|
|
plsql-white-space-re
|
|
)))
|
|
|
|
(defsubst plsql-sql-statement-adjust ()
|
|
"If we are inside an SQL statement returns extra indentation required to line
|
|
the last character of the SQL keyword that starts each line."
|
|
|
|
;; this is called solely to determine the real indent level of the
|
|
;; statement preceding the active line.
|
|
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-sql-statement-adjust 'append))
|
|
|
|
(save-excursion
|
|
(back-to-indentation)
|
|
(if (looking-at plsql-sql-statement-re)
|
|
(- 8 ;; length of the longest keyword
|
|
(length (thing-at-point 'word)))
|
|
0)))
|
|
|
|
(defvar plsql-extra-indent-re ""
|
|
"A couple of situations in which we might like to add a little indentation.")
|
|
|
|
(when (plsql-reset plsql-extra-indent-re)
|
|
(setq plsql-extra-indent-re
|
|
(concat
|
|
"\\b" ;; open word boundary
|
|
;; "\\(" ;; open group of alternatives
|
|
(regexp-opt
|
|
(list
|
|
"in"
|
|
"default"
|
|
) t )
|
|
;; "\\)" ;; close group of alternatives
|
|
plsql-white-space-re) ;; minimal whitespace after key word
|
|
))
|
|
|
|
(defun plsql-statement-level-adjust (limit indent)
|
|
"Adjust the proposed INDENT column at the statement level.
|
|
LIMIT marks the beginning of the current statement. Here we deal with the
|
|
indentation of SQL keywords starting an SQL block , variable declaration
|
|
keywords, and multiline statements"
|
|
|
|
;; PRE CONDITION:
|
|
;; called after a `back-to-indentation'
|
|
;; POST CONDITION:
|
|
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack
|
|
'plsql-statement-level-adjust 'append))
|
|
|
|
(cond
|
|
((looking-at plsql-sql-statement-re)
|
|
;; line up last character of leading SQL keywords
|
|
(+ indent
|
|
(- 8 ;; length of CONNECT
|
|
(length (thing-at-point 'word)))))
|
|
|
|
((looking-at plsql-extra-indent-re)
|
|
;; so add some extra indentation
|
|
(+ indent plsql-indent))
|
|
|
|
;; some times a section starter involves two words which we may
|
|
;; want to put on different lines e.g. if .. then
|
|
((looking-at (concat "then\\|loop\\|is\\|as" plsql-white-space-re))
|
|
(if (= (point) limit) ;; loop may also start a statement
|
|
indent
|
|
(save-excursion
|
|
(goto-char limit)
|
|
(current-indentation))))
|
|
|
|
((save-excursion (plsql-re-search-backward "\n\\|\r" limit t nil limit))
|
|
;; there a line break between point and the beginning of statement so
|
|
;; add some extra indentation.
|
|
|
|
(+ indent plsql-indent))
|
|
(t indent)
|
|
))
|
|
|
|
;; (defun plsql-in-parenthesis-p ()
|
|
;; (interactive)
|
|
;; (save-excursion
|
|
;; (let ((open 0))
|
|
;; (while (and (< open 1)
|
|
;; (plsql-re-search-backward "(\\|)" nil t))
|
|
;; (cond ((equal "(" (match-string 0))
|
|
;; (setq open (+ open 1)))
|
|
;; (t (setq open (- open 1))
|
|
;;
|
|
;; )))
|
|
;; (if (> open 0)
|
|
;; (point)
|
|
;; nil)
|
|
;; )
|
|
;; ))
|
|
|
|
;; throw and catch may well be our boys for this one
|
|
;; as might be recursion
|
|
|
|
(defun plsql-in-parenthesis-p (limit)
|
|
"If inside a parenthesis group, return the column of the open paren,
|
|
else return nil. Search bound by LIMIT."
|
|
(save-excursion
|
|
(let ((parse-sexp-ignore-comments t))
|
|
|
|
;; bottle-neck is really caused by this condition case
|
|
|
|
;; (result (condition-case nil
|
|
;; (scan-lists (point) -1 1)
|
|
;; (error nil))))
|
|
|
|
(nth 1 (parse-partial-sexp (point) limit)))))
|
|
|
|
(defvar plsql-statement-end-re nil
|
|
"Regexp matching any kind of statement end.")
|
|
|
|
(when (plsql-reset plsql-statement-end-re)
|
|
(setq plsql-statement-end-re
|
|
(concat
|
|
"\\(" ;; mark start of statement end
|
|
|
|
"" ";" ;; generic PL/SQL statement end
|
|
|
|
;;; "\\|" "/" ;; generic SQL statement end
|
|
;;; *don't use since "/" is also used for divison in expressions*
|
|
|
|
"\\|" ;;; some keywords can also be thought of as ending
|
|
;;; a statement so ...
|
|
|
|
plsql-white-space-re ;;; minimal whitespace before key word --
|
|
;;; needed to exclude words in identifiers
|
|
|
|
;; "\\("
|
|
(regexp-opt ;; optimisation actually does nothing:-)
|
|
(list
|
|
"loop" ;; delimits contents of a loop statement
|
|
"then" ;; delimits contents of a conditional
|
|
"else" ;; ends the previous sub-section, starts a new one
|
|
"as" ;; dec section
|
|
"is" ;; dec section
|
|
|
|
;;; "elsif" ;; *don't use since the following "then" is the real end*
|
|
|
|
) t)
|
|
;; "\\)"
|
|
|
|
"\\)" ;;; mark end of statement excluding whitespace
|
|
plsql-white-space-re ;;; minimal whitespace after key word --
|
|
;;; needed to exclude words in identifiers
|
|
)))
|
|
|
|
(defun plsql-in-sql-block-p (limit)
|
|
"If inside (but not just before) an SQL block, return the adjusted
|
|
position of the start of the block the block, otherwise return nil.
|
|
Search bound by LIMIT."
|
|
;; PRE: point is at start of indentation
|
|
(save-excursion
|
|
(let ((start (point)))
|
|
(when (or (plsql-re-search-backward plsql-statement-end-re
|
|
limit t nil limit)
|
|
;; very first statement is a special case
|
|
(and (= limit 1) (goto-char 1)))
|
|
|
|
(when (and (plsql-re-search-forward
|
|
;; ensure we have a leading identifier boundary
|
|
(concat plsql-white-space-re plsql-sql-statement-re)
|
|
start t nil start)
|
|
(progn ;; skip bracketed sql blocks
|
|
(forward-char 1)
|
|
(not (plsql-in-parenthesis-p limit))))
|
|
|
|
(back-to-indentation)
|
|
(- (point)
|
|
(plsql-sql-statement-adjust))
|
|
)))))
|
|
|
|
(defsubst plsql-indent-to-col (target-col)
|
|
"Ensure current indentation is TARGET-COl, but avoid altering the buffer if
|
|
no real change need be made."
|
|
;; assumes point is at start of indentation
|
|
(unless (eq (current-indentation) target-col)
|
|
(delete-horizontal-space)
|
|
(indent-to target-col)))
|
|
|
|
;; An SQL block is a chain of SQL statements ending in either a ")"
|
|
;; parenthesis or a ";", wiht each SQL statement starting with a key word
|
|
;; match `plsql-sql-statement-re'
|
|
|
|
(defun plsql-sql-block-indent (block-start)
|
|
"Indent code inside sql block. BLOCK-start is the adjusted position of
|
|
the start of the bock."
|
|
;; PRE CONDITION:
|
|
;; (1) POINT is at the current indentation of the line we are
|
|
;; indenting.
|
|
;; (2) LIMIT is the column start of the sql statement block
|
|
;; (3) We are not inside a comment or a string.
|
|
|
|
;; POST CONDITION: line indented correctly
|
|
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-sql-block-indent 'append))
|
|
|
|
(let ((block-col (save-excursion
|
|
(goto-char block-start)
|
|
(current-column))))
|
|
(if (looking-at plsql-sql-statement-re)
|
|
(plsql-indent-to-col (+ block-col
|
|
(- 8 ;; length of the longest keyword
|
|
|
|
(length (thing-at-point 'word)))))
|
|
|
|
;; Indent to the same col as the first word following the previous
|
|
;; sql statement
|
|
(plsql-indent-to-col (save-excursion
|
|
(plsql-re-search-backward
|
|
;; ensure we have a leading identifier boundary
|
|
(concat plsql-white-space-re plsql-sql-statement-re)
|
|
block-start)
|
|
(goto-char (match-end 1))
|
|
(skip-chars-forward " \t")
|
|
(if (looking-at "[\n\r]")
|
|
(+ block-col plsql-indent)
|
|
(current-column)))))))
|
|
|
|
(defun plsql-parenthesis-indent (limit)
|
|
"Indent code inside parenthesis group. LIMIT is the start of the group."
|
|
;; PRE CONDITION:
|
|
;; (1) POINT is at the current indentation of the line we are
|
|
;; indenting.
|
|
;; (2) LIMIT is the start of the group
|
|
;; (3) We are not inside a comment or a string.
|
|
|
|
;; POST CONDITION: line indented correctly
|
|
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-paren-indent 'append))
|
|
|
|
(cond ((looking-at "(") ;; line break between statement start and open paren?
|
|
(plsql-indent-to-col
|
|
(save-excursion
|
|
(plsql-re-search-backward plsql-leading-identifier-re limit t nil limit)
|
|
;; only fails if very first non-comment char is a (
|
|
(goto-char (or (match-beginning 1) 1))
|
|
(+ (current-column) plsql-indent))))
|
|
|
|
((save-excursion ;; parentheses around sql block?
|
|
(goto-char limit)
|
|
(forward-char 1)
|
|
(skip-chars-forward " \t") ;; non-line-ending whitespace
|
|
;; return column number of first non whitespace char after point.
|
|
;; making an adjustment for SQL statements
|
|
(looking-at plsql-sql-statement-re))
|
|
(plsql-sql-block-indent (save-excursion
|
|
(goto-char (- (match-end 1) 7))
|
|
(current-column))))
|
|
|
|
(t ;; plain old parentheses
|
|
(plsql-indent-to-col (+ (save-excursion
|
|
(goto-char limit)
|
|
(forward-char 1)
|
|
(skip-chars-forward " \t")
|
|
(current-column))
|
|
(save-excursion
|
|
(back-to-indentation)
|
|
(if (looking-at plsql-extra-indent-re)
|
|
plsql-indent
|
|
0)))
|
|
))))
|
|
|
|
;;;_ + Section Level
|
|
;;
|
|
;; A section contains a list of statements of the following form:
|
|
;;
|
|
;; WHITESPACE KEYWORD EXPRESSION;
|
|
;;
|
|
;; The following special cases also contain sub-sections
|
|
;;
|
|
;; if EXPRESSION then
|
|
;; SUB-SECTION
|
|
;; [else]
|
|
;; SUB-SECTION
|
|
;; [elsif EXPRESSION then]
|
|
;; SUB-SECTION
|
|
;; end if;
|
|
;;
|
|
;; [for | while] EXPRESSION loop
|
|
;; SUB-SECTION
|
|
;; end loop;
|
|
;;
|
|
|
|
(defvar plsql-exec-sec-stmnt-end-re ""
|
|
"Regexp to match the end of a statements in a execution section.
|
|
The end of the 1st match should mark the boundary between statements.")
|
|
|
|
;; first call is after a call to `back-to-indentation'
|
|
;; second call immediately follows
|
|
(when (plsql-reset plsql-exec-sec-stmnt-end-re)
|
|
(setq plsql-exec-sec-stmnt-end-re
|
|
(concat
|
|
"\\(" ;; mark start of statement end
|
|
|
|
"" ";" ;; generic PL/SQL statement end
|
|
|
|
;;; "\\|" "/" ;; generic SQL statement end
|
|
;;; *don't use since "/" is also used for divison in expressions*
|
|
|
|
"\\|" ;;; some keywords can also be thought of as ending
|
|
;;; a statement so ...
|
|
|
|
plsql-white-space-re ;;; minimal whitespace before key word --
|
|
;;; needed to exclude words in identifiers
|
|
|
|
;; "\\("
|
|
(regexp-opt ;; optimisation actually does nothing:-)
|
|
(list
|
|
"loop" ;; delimits contents of a loop statement
|
|
"then" ;; delimits contents of a conditional
|
|
"else" ;; ends the previous sub-section, starts a new one
|
|
|
|
;;; "elsif" ;; *don't use since the following "then" is the real end*
|
|
|
|
) t)
|
|
;; "\\)"
|
|
|
|
"\\)" ;;; mark end of statement excluding whitespace
|
|
plsql-white-space-re ;;; minimal whitespace after key word --
|
|
;;; needed to exclude words in identifiers
|
|
)))
|
|
|
|
|
|
(defvar plsql-open-exec-sec-re ""
|
|
"REGEXP matching words that start a new (sub-)section." )
|
|
|
|
;; assume that this is used by `looking-at' after a call to `back-to-indentation'
|
|
(when (plsql-reset plsql-open-exec-sec-re)
|
|
(setq plsql-open-exec-sec-re
|
|
(concat
|
|
;; "\\("
|
|
(regexp-opt
|
|
(list "if"
|
|
"else" ;; both an open and a close and a boundary
|
|
"elsif"
|
|
|
|
;;; "when" ;; *case statements handled by `plsql-excep-sec-indent'*
|
|
|
|
"for"
|
|
"while"
|
|
"loop" ;; infinite loops
|
|
) t)
|
|
;; "\\)"
|
|
plsql-white-space-re ;; minimal whitespace after key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
)))
|
|
|
|
(defvar plsql-close-exec-sec-re ""
|
|
"REGEXP matching words that end a (sub-)section." )
|
|
|
|
;; assume that this is used by `looking-at' after a call to `back-to-indentation'
|
|
(when (plsql-reset plsql-close-exec-sec-re)
|
|
(setq plsql-close-exec-sec-re
|
|
(concat
|
|
"\\(" ;; open group of alternatives
|
|
"\\b" ;; open word boundary
|
|
(regexp-opt
|
|
(list
|
|
|
|
;;; "when" ;; *case statements handled by `plsql-excep-sec-indent'*
|
|
|
|
;;; "begin" ;;; if we are looking at this then we should be in a
|
|
;;; declaration section
|
|
|
|
"end" ;;
|
|
|
|
"else" ;; end conditional
|
|
"elsif" ;; end conditional
|
|
|
|
"exception" ;; end the section WHAT ABOUT THIS GUY?
|
|
|
|
))
|
|
plsql-white-space-re ;; minimal whitespace after key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
"\\|"
|
|
"end;" ;; one special case
|
|
"\\)" ;; close group of alternatives
|
|
)))
|
|
|
|
(defun plsql-exec-sec-indent (limit)
|
|
"Indent the current line given that it is inside an execution section.
|
|
LIMIT provides a bound for searches and partial scans. The assumption
|
|
is that the context of the current line can be completely determined
|
|
without reference to any text above that point."
|
|
|
|
;; PRE CONDITION:
|
|
;; (1) POINT is at the current indentation of the line we are
|
|
;; indenting.
|
|
;; (2) The section-start at LIMIT is either:
|
|
;;
|
|
;; (a)
|
|
;; (b)
|
|
;;
|
|
;; POST CONDITION: current line is indented correctly.
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-exec-sec-indent 'append))
|
|
|
|
;; limit has been set to the point before the keyword "begin" or "end".
|
|
|
|
;; we are indenting the first statement on current line so ...
|
|
(back-to-indentation)
|
|
|
|
;; First determine some properties of the previous statement
|
|
|
|
(let ((first-statement nil)
|
|
(prev-indent 0) ;; indentation of the previous statement
|
|
(prev-type 'plain) ;; either 'open, 'close, or 'plain
|
|
(statement-start (point))) ;; mark the beginning of this statement
|
|
|
|
(save-excursion
|
|
|
|
;; The previous statement will be delimited by two preceding
|
|
;; statement ends, unless of course we are before the 3rd following
|
|
;; the limit.
|
|
|
|
;;---1. back up one delimiter
|
|
|
|
(if (plsql-re-search-backward
|
|
plsql-exec-sec-stmnt-end-re limit t nil limit)
|
|
(progn
|
|
;; Find the beginning of the statement we started on.
|
|
;; We have to do this in two steps because of potential
|
|
;; intervening comments.
|
|
(save-excursion
|
|
(goto-char (match-end 1)) ;; point is after statement delimiter
|
|
(when (plsql-re-search-forward
|
|
plsql-leading-identifier-re nil t nil limit)
|
|
(setq statement-start (match-beginning 1))))
|
|
|
|
;;---2. back up another delimiter
|
|
|
|
(unless (plsql-re-search-backward
|
|
plsql-exec-sec-stmnt-end-re limit t nil limit)
|
|
|
|
;; we failed so we must have started before the 3rd statement
|
|
;; after the limit
|
|
|
|
(goto-char limit)
|
|
(if (not (looking-at "end")) ;; point is before "begin"
|
|
(forward-word 1) ;; set point after "begin"
|
|
;; point is before"end"
|
|
(setq first-statement t)
|
|
|
|
;; oops! we really started from the 1st statement
|
|
(setq prev-indent (- (current-indentation)
|
|
(plsql-sql-statement-adjust))))
|
|
|
|
)
|
|
|
|
;; set point before the first word of "previous statement"
|
|
(unless first-statement
|
|
(if (plsql-re-search-forward
|
|
plsql-leading-identifier-re
|
|
statement-start t nil limit)
|
|
(progn
|
|
(goto-char (match-beginning 1))
|
|
(setq prev-indent (- (current-indentation)
|
|
(plsql-sql-statement-adjust)))
|
|
(if (looking-at plsql-open-exec-sec-re)
|
|
(setq prev-type 'open)))
|
|
|
|
;; Opps! First word of previous statement was block by
|
|
;; statement-start limit. The only case where I have seen
|
|
;; this happen is if whe are trying to align lik the
|
|
;; following:
|
|
|
|
;; if l_inplace = 0 and l_fullscreen = 0
|
|
;; then l_inplaceradio := 'inframe';
|
|
;; elsif l_inplace = 3 and l_fullscreen = 0
|
|
;; then l_inplaceradio := 'inframe';
|
|
;; else l_inplaceradio := 'fullscreen';
|
|
;; end if;
|
|
|
|
(setq prev-indent (- (current-indentation)
|
|
(plsql-sql-statement-adjust)))
|
|
(setq prev-type 'plain-force)
|
|
)))
|
|
|
|
;; so we started from the 1st statement of this section
|
|
(goto-char limit) ;; point is before "begin" or "end"
|
|
(if (looking-at "begin")
|
|
(setq prev-type 'open)) ;; statement begins this section
|
|
|
|
(setq prev-indent (current-indentation))
|
|
(forward-word 1) ;; point is after "begin" or "end"
|
|
(if (plsql-re-search-forward
|
|
plsql-leading-identifier-re
|
|
nil t nil limit)
|
|
(goto-char (match-beginning 1)))
|
|
(setq statement-start (point))
|
|
))
|
|
|
|
;; Now we know what the previous statement was so we can indent the
|
|
;; current line relative to it
|
|
|
|
(if (looking-at plsql-close-exec-sec-re)
|
|
(if (or (eq prev-type 'open)
|
|
(eq prev-type 'plain-force))
|
|
(setq prev-type 'plain) ;; one-line sub-sections
|
|
(setq prev-type 'close))
|
|
(if (eq prev-type 'plain-force)
|
|
(setq prev-type 'plain)))
|
|
|
|
(let ((new-indent
|
|
(cond ((eq prev-type 'open) (+ prev-indent plsql-indent))
|
|
((eq prev-type 'close) (- prev-indent plsql-indent))
|
|
(t prev-indent))))
|
|
|
|
;; adjust if point is on or after the start of the statement
|
|
;; WHEN DOES THIS FAIL?
|
|
(when (>= (point) statement-start)
|
|
(setq new-indent (plsql-statement-level-adjust statement-start new-indent)))
|
|
|
|
;; avoid altering the buffer if no real change is made
|
|
(unless (eq (current-indentation) new-indent)
|
|
(delete-horizontal-space)
|
|
(indent-to new-indent)))
|
|
|
|
)) ;; -- end plsql-exec-sec-indent
|
|
|
|
;;;_ + Declaration Section
|
|
|
|
(defvar plsql-dec-sec-stmnt-end-re ""
|
|
"Regexp to match the end of a statements in a declaration section.
|
|
The end of the 1st match should mark the boundary between statements.")
|
|
|
|
(when (plsql-reset plsql-dec-sec-stmnt-end-re)
|
|
(setq plsql-dec-sec-stmnt-end-re
|
|
(concat
|
|
"\\(" ;; mark start of statement end
|
|
|
|
"" ";" ;; generic PLSQL statement end
|
|
|
|
;; "/" is also used for divison in expressions
|
|
;; "\\|" "/" ;; generic SQL statement end
|
|
|
|
"\\|" ;; some keywords also end a statement so ...
|
|
|
|
plsql-white-space-re ;; minimal whitespace before key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
|
|
;; "\\("
|
|
(regexp-opt ;; optimisation actually does nothing:-)
|
|
(list
|
|
"as" ;; delimits variable declarations
|
|
"is" ;; delimits variable declarations
|
|
) t)
|
|
;; "\\)"
|
|
|
|
"\\)" ;; mark end of statement excluding whitespace
|
|
plsql-white-space-re ;; minimal whitespace after key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
)))
|
|
|
|
|
|
(defvar plsql-dec-sec-fake-stmnt-end-re ""
|
|
"Regexp matching the start of statements in a declaration section that are not
|
|
always ended by something that matches `plsql-dec-sec-stmnt-end-re'. Will be
|
|
compared with the first match of `plsql-leading-identifier-re'.")
|
|
|
|
(when (plsql-reset plsql-dec-sec-fake-stmnt-end-re)
|
|
(setq plsql-dec-sec-fake-stmnt-end-re
|
|
(concat
|
|
plsql-white-space-re ;; minimal whitespace before key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
(regexp-opt
|
|
(list
|
|
"cursor"
|
|
"type"
|
|
"subtype"
|
|
"procedure"
|
|
"function"
|
|
))
|
|
plsql-white-space-re ;; minimal whitespace after key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
)))
|
|
|
|
(defvar plsql-open-dec-sec-re ""
|
|
"REGEXP matching words that start a new declaration (sub-)section.
|
|
Note that, since there is little difference between the specification section
|
|
and the declaration section, we treat them the same way." )
|
|
|
|
(when (plsql-reset plsql-open-dec-sec-re)
|
|
(setq plsql-open-dec-sec-re
|
|
(concat
|
|
"\\b"
|
|
(regexp-opt
|
|
(list "is"
|
|
"as"
|
|
"procedure"
|
|
"function"
|
|
"trigger"
|
|
"declare"
|
|
))
|
|
plsql-white-space-re ;; minimal whitespace after key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
)))
|
|
|
|
(defvar plsql-close-dec-sec-re ""
|
|
"REGEXP matching words that end a declaration (sub-)section.
|
|
Note that, since there is little difference between the specification section
|
|
and the declaration section, we treat them the same way.")
|
|
|
|
(when (plsql-reset plsql-close-dec-sec-re)
|
|
(setq plsql-close-dec-sec-re
|
|
(concat
|
|
"\\b" ;; open word boundary
|
|
"\\(" ;; open group of alternatives
|
|
(regexp-opt
|
|
(list
|
|
"begin" ;; start the actual section
|
|
"is" ;; both and open and a close
|
|
"as"
|
|
))
|
|
plsql-white-space-re ;; minimal whitespace after key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
"\\|"
|
|
"end;" ;; one special case
|
|
"\\)" ;; close group of alternatives
|
|
)))
|
|
|
|
(defun plsql-dec-sec-indent (limit)
|
|
"Indent the current line given that it is inside declaration section."
|
|
|
|
;; PRE CONDITION:
|
|
;; (1) POINT is at the current indentation of the line we are
|
|
;; indenting.
|
|
;; (2) The section-start at LIMIT is either:
|
|
;;
|
|
;; (a)
|
|
;; (b)
|
|
;;
|
|
;; POST CONDITION: current line is indented correctly.
|
|
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-dec-sec-indent 'append))
|
|
|
|
;; This function is very similar to plsql-exec-sec-indent; separating it
|
|
;; out, however, vastly simplifies the logic, in particular that relating
|
|
;; to cursors and specification sections.
|
|
|
|
;; we are indenting the first statement on current line so ...
|
|
(back-to-indentation)
|
|
|
|
;; First determine some properties of the previous statement
|
|
|
|
(let ((prev-indent 0) ;; indentation of the previous statement
|
|
(prev-type 'plain) ;; either 'open, 'close, or 'plain
|
|
(statement-start (point)));; mark beginning of this statement
|
|
|
|
(save-excursion
|
|
|
|
;; The previous statement is delimited by two preceding statement
|
|
;; ends, unless of course we are either the 1st statement in the
|
|
;; section
|
|
|
|
;;---1. back up one delimiter
|
|
|
|
(if (and (plsql-re-search-backward
|
|
plsql-dec-sec-stmnt-end-re limit t nil limit)
|
|
;; e.g. "is" after a cursor is not a real statement delimiter
|
|
(if (save-excursion
|
|
(save-match-data
|
|
(and
|
|
(plsql-re-search-backward
|
|
plsql-dec-sec-stmnt-end-re limit t nil limit)
|
|
(plsql-re-search-forward
|
|
plsql-leading-identifier-re nil t nil limit)
|
|
(goto-char (match-beginning 1))
|
|
(looking-at plsql-dec-sec-fake-stmnt-end-re))))
|
|
(plsql-re-search-backward
|
|
plsql-dec-sec-stmnt-end-re limit t nil limit)
|
|
t))
|
|
|
|
(progn
|
|
;; Find the statement start (we have to do this in two steps
|
|
;; because of potential intervening comments).
|
|
(save-excursion
|
|
(goto-char (match-end 1)) ;; point is after statement delimiter
|
|
(when (plsql-re-search-forward
|
|
plsql-leading-identifier-re nil t nil limit)
|
|
(setq statement-start (match-beginning 1))))
|
|
|
|
;;---2. back up another delimiter
|
|
|
|
(if (and (plsql-re-search-backward
|
|
plsql-dec-sec-stmnt-end-re limit t nil limit)
|
|
;; e.g. "is" after a "cursor" is not a real statement delimiter
|
|
(if (save-excursion
|
|
(and
|
|
(plsql-re-search-backward
|
|
plsql-dec-sec-stmnt-end-re limit t nil limit)
|
|
(plsql-re-search-forward
|
|
plsql-leading-identifier-re nil t nil limit)
|
|
(goto-char (match-beginning 1))
|
|
(looking-at plsql-dec-sec-fake-stmnt-end-re)))
|
|
(plsql-re-search-backward
|
|
plsql-dec-sec-stmnt-end-re limit t nil limit)
|
|
t))
|
|
|
|
;; so we started after the 2nd statement of this dec lock
|
|
(progn
|
|
(when (looking-at "[ \t\n\r]*[ia]s[ \t\n\r]")
|
|
;; this way we end up treating specification sections
|
|
;; the same as declaration sections
|
|
(forward-word 1))
|
|
|
|
;; forward to next leading word
|
|
(when (plsql-re-search-forward
|
|
plsql-leading-identifier-re
|
|
statement-start t nil limit)
|
|
;; if this fails we don't want any indentation:-)
|
|
(goto-char (match-beginning 1))
|
|
(setq prev-indent (- (current-indentation)
|
|
(plsql-sql-statement-adjust)))
|
|
))
|
|
|
|
;; so we started before the 3rd statement of this dec section
|
|
(goto-char limit) ;; point before "is" or "as"
|
|
(setq prev-type 'open)
|
|
(setq prev-indent (current-indentation))
|
|
))
|
|
|
|
;; so we started before the first "is" or "as"
|
|
(goto-char limit)
|
|
(back-to-indentation)
|
|
(setq prev-indent (current-indentation))
|
|
(setq statement-start (point-max)) ;; don't do statement-level-indent
|
|
(setq prev-type 'open)
|
|
))
|
|
|
|
;; Now we know what the previous statement was so we can indent the
|
|
;; current line relative to it
|
|
|
|
(if (looking-at plsql-close-dec-sec-re)
|
|
(if (eq prev-type 'open)
|
|
(setq prev-type 'plain) ;; one-line sub-sections
|
|
(setq prev-type 'close)))
|
|
|
|
(let ((new-indent
|
|
(cond ((eq prev-type 'open) (+ prev-indent plsql-indent))
|
|
((eq prev-type 'close) (- prev-indent plsql-indent))
|
|
(t prev-indent))))
|
|
|
|
;; point is after the beginning of the statement
|
|
(when (>= (point) statement-start)
|
|
(setq new-indent (plsql-statement-level-adjust statement-start new-indent)))
|
|
|
|
;; avoid altering the buffer if no real change is made
|
|
(unless (eq (current-indentation) new-indent)
|
|
(delete-horizontal-space)
|
|
(indent-to new-indent)))
|
|
|
|
)) ;;-- end plsql-dec-sec-indent
|
|
|
|
|
|
;;;_ + Exception Section
|
|
|
|
;; And in the exception section:
|
|
;;
|
|
;; when ERROR-CODE then
|
|
;; SUB-SECTION
|
|
;;
|
|
;; The CASE statement is another special case: indentation behaves just like
|
|
;; an exception section.
|
|
|
|
(defvar plsql-except-sec-stmnt-end-re ""
|
|
"Regexp to match the end of a statements in a case or exception section.
|
|
The end of the 1st match should mark the boundary between statements.")
|
|
|
|
(when (plsql-reset plsql-except-sec-stmnt-end-re)
|
|
(setq plsql-except-sec-stmnt-end-re
|
|
(concat
|
|
"\\(" ;; mark start of statement end
|
|
|
|
"" ";" ;; generic PLSQL statement end
|
|
|
|
;; "/" is also used for divison in expressions
|
|
;; "\\|" "/" ;; generic SQL statement end
|
|
|
|
"\\|" ;; some keywords also end a statement so ...
|
|
|
|
plsql-white-space-re ;; minimal whitespace before key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
|
|
;; "\\("
|
|
(regexp-opt ;; optimisation actually does nothing:-)
|
|
(list
|
|
"then" ;; delimits contents of a conditional
|
|
"else" ;; delimits contents of a conditional
|
|
) t)
|
|
;; "\\)"
|
|
|
|
"\\)" ;; mark end of statement excluding whitespace
|
|
plsql-white-space-re ;; minimal whitespace after key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
)))
|
|
|
|
(defvar plsql-open-execp-sec-re ""
|
|
"REGEXP matching words that start a new case or exception (sub-)section." )
|
|
|
|
(when (plsql-reset plsql-open-execp-sec-re)
|
|
(setq plsql-open-execp-sec-re
|
|
(concat
|
|
;; "\\("
|
|
(regexp-opt
|
|
(list
|
|
"exception"
|
|
"case"
|
|
"when" ;; start
|
|
"then" ;; one liners
|
|
"if"
|
|
"else"
|
|
) t)
|
|
;; "\\)"
|
|
plsql-white-space-re ;; minimal whitespace after key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
)))
|
|
|
|
|
|
(defvar plsql-close-except-sec-re ""
|
|
"REGEXP matching words that end a new case or exception (sub-)section.")
|
|
|
|
(when (plsql-reset plsql-close-except-sec-re)
|
|
(setq plsql-close-except-sec-re
|
|
(concat
|
|
"\\(" ;; open group of alternatives
|
|
"\\b" ;; open word boundary
|
|
(regexp-opt
|
|
(list
|
|
"when" ;; end one, start another
|
|
"else"
|
|
))
|
|
plsql-white-space-re ;; minimal whitespace after key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
"\\)" ;; close group of alternatives
|
|
)))
|
|
|
|
|
|
(defvar plsql-block-terminator ""
|
|
"Regexp matching the term that terminates a block.")
|
|
|
|
;; assume we are after a call to `back-to-indentation'
|
|
|
|
(when (plsql-reset plsql-block-terminator)
|
|
(setq plsql-block-terminator
|
|
(concat
|
|
"end"
|
|
"\\("
|
|
";"
|
|
"\\|"
|
|
plsql-white-space-re ;; minimal whitespace after key word --
|
|
"" ;; needed to exclude words in identifiers
|
|
"\\)"
|
|
)))
|
|
|
|
(defun plsql-except-sec-indent (limit)
|
|
"Indent the current line given that it is inside a case or exception section."
|
|
|
|
;; PRE CONDITION:
|
|
;; (1) POINT is at the current indentation of the line we are
|
|
;; indenting.
|
|
;; (2) The section-start at LIMIT is either:
|
|
;;
|
|
;; (a)
|
|
;; (b)
|
|
;;
|
|
;; POST CONDITION: current line is indented correctly.
|
|
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-except-sec-indent 'append))
|
|
|
|
;; This function is very similar to plsql-exec-sec-indent; separating it
|
|
;; out, however, vastly simplifies the logic, in particular that relating
|
|
;; to ending a blocks that have exception sections.
|
|
|
|
;; we are indenting the first statement on current line so ...
|
|
(back-to-indentation)
|
|
|
|
;; First determine some properties of the previous statement
|
|
|
|
(let ((prev-indent 0) ;; indentation of the previous statement
|
|
(prev-type 'plain) ;; either 'open, 'open-force', 'close, or 'plain
|
|
(statement-start (point))) ;; mark beginning of this statement
|
|
|
|
(save-excursion
|
|
;; first catch the "end of block" special case;
|
|
;; this includes the "end of case statement" special case
|
|
(if (looking-at plsql-block-terminator)
|
|
(progn
|
|
;; (setq statement-start (point)) ;; why?
|
|
(goto-char limit)
|
|
(setq prev-indent (current-indentation))
|
|
(setq prev-type 'plain))
|
|
|
|
;; The previous statement is delimited by two preceding statement ends,
|
|
;; unless of course we are either the 1st or 2nd statement in the
|
|
;; section
|
|
|
|
;;---1. back up one statement
|
|
|
|
(if (plsql-re-search-backward
|
|
plsql-except-sec-stmnt-end-re limit t nil limit)
|
|
(progn
|
|
;; Find the statement start (we have to do this in two steps
|
|
;; because of potential intervening comments).
|
|
(save-excursion
|
|
(goto-char (match-end 1)) ;; point is after statement delimiter
|
|
(when (plsql-re-search-forward
|
|
plsql-leading-identifier-re nil t nil limit)
|
|
(setq statement-start (match-beginning 1))))
|
|
|
|
;;--2. back up another statement above this line
|
|
(back-to-indentation)
|
|
(unless (plsql-re-search-backward
|
|
plsql-except-sec-stmnt-end-re limit t nil limit)
|
|
;; we started from the 2nd statement of this section
|
|
(goto-char limit) ;; point before "begin"
|
|
(forward-word 1)) ;; point is after "begin"
|
|
|
|
;; set point before the first word of "previous statement"
|
|
(when (plsql-re-search-forward
|
|
plsql-leading-identifier-re statement-start t nil limit)
|
|
;; if this fails we don't want any indentation:-)
|
|
(goto-char (match-beginning 1))
|
|
(setq prev-indent (- (current-indentation)
|
|
(plsql-sql-statement-adjust)))
|
|
(if (looking-at plsql-open-execp-sec-re)
|
|
(setq prev-type 'open))
|
|
))
|
|
|
|
;; we started from the 1st statement of this section
|
|
(goto-char limit)
|
|
(skip-chars-forward " \t") ;; point is before "case" or "exception"
|
|
(setq prev-indent (current-indentation))
|
|
|
|
(if (not (looking-at "case"))
|
|
(forward-word 1) ;; point is after "exception"
|
|
(forward-word 1) ;; point is after case
|
|
|
|
;; This fails if you stick a comment between "case" and its
|
|
;; identifier. This case is too rare (and silly) to really
|
|
;; consider:-)
|
|
|
|
(skip-chars-forward " \t") ;; point is before identifier
|
|
(skip-chars-forward "^ \t")) ;; point is after identifier
|
|
|
|
(if (plsql-re-search-forward plsql-leading-identifier-re
|
|
nil t nil limit)
|
|
(goto-char (match-beginning 1)))
|
|
(setq statement-start (point))
|
|
(setq prev-type 'open-force) ;; previous statement begun this section
|
|
))
|
|
)
|
|
|
|
;; Now we know what the previous statement was so we can indent the
|
|
;; current line relative to it
|
|
|
|
(if (and (looking-at plsql-close-except-sec-re))
|
|
(if (eq prev-type 'open)
|
|
(setq prev-type 'plain) ;; one-line sub-sections
|
|
(if (eq prev-type 'open-force) ;; beginning of section never a one-liner
|
|
(setq prev-type 'open)
|
|
(setq prev-type 'close))))
|
|
|
|
(let ((new-indent
|
|
(cond ((eq prev-type 'open) (+ prev-indent plsql-indent))
|
|
((eq prev-type 'open-force) (+ prev-indent plsql-indent))
|
|
((eq prev-type 'close) (- prev-indent plsql-indent))
|
|
(t prev-indent))))
|
|
|
|
;; maybe add some extra
|
|
(when (>= (point) statement-start)
|
|
(setq new-indent (plsql-statement-level-adjust statement-start new-indent)))
|
|
|
|
;; avoid altering the buffer if no real change is made
|
|
(unless (eq (current-indentation) new-indent)
|
|
(delete-horizontal-space)
|
|
(indent-to new-indent)))
|
|
|
|
)) ;; -- end plsql-except-sec-indent
|
|
|
|
;;;_ + Package Level
|
|
|
|
(defun plsql-package-indent (limit)
|
|
"Indent the current line given that it is outside a module
|
|
definition. Really this is for indenting package variables, first
|
|
line of a module, and the \"begin\" at the start of a packages
|
|
executable block."
|
|
|
|
;; PRE CONDITION:
|
|
;; (1) POINT is at the current indentation of the line we are
|
|
;; indenting.
|
|
;; (2) The section-start at LIMIT is either:
|
|
;; (a) "end" NOT followed by "if", "loop", or "case"
|
|
;; (b) "function" or "procedure" but before "is" or "as"
|
|
;; (c) "package".
|
|
;;
|
|
;; POST CONDITION: current line is indented correctly.
|
|
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-package-indent 'append))
|
|
|
|
(back-to-indentation)
|
|
(let ((new-indent
|
|
(save-excursion
|
|
(cond ((looking-at "\\<begin\\>[ \t\n\r]+\\|^[ \t]*$")
|
|
;; we're starting the executable section of a package body
|
|
;; containing object, so decrease the indentation level
|
|
(goto-char limit)
|
|
(- (current-indentation) plsql-indent))
|
|
|
|
((looking-at "/\\|\\bend\\b")
|
|
;; we're ending a package specification
|
|
0)
|
|
|
|
((looking-at "--\\|\\b\\(procedure\\|function\\|trigger\\)\\b")
|
|
|
|
;; where doing a module specification
|
|
;; hmm should really indent to previous in case
|
|
;; its a local procedure (god does anyone actually do that?)
|
|
|
|
plsql-indent)
|
|
|
|
((looking-at
|
|
"\\<create\\>\\([ \t\n\r]+or[ \t\n\r]+replace\\)?[ \t\n\r]+\\(\\w+\\)")
|
|
;; we're starting a new package body or spec
|
|
(if (string-match "procedure\\|function\\|trigger"
|
|
(match-string 1))
|
|
plsql-indent 0))
|
|
|
|
((looking-at "package")
|
|
;; old fashioned new package body or spec
|
|
0)
|
|
|
|
(t
|
|
;; else we are a package variable decalaration
|
|
nil)))))
|
|
|
|
(if (not new-indent)
|
|
(plsql-dec-sec-indent limit)
|
|
(unless (= (current-indentation) new-indent)
|
|
(delete-horizontal-space)
|
|
(indent-to new-indent)))
|
|
))
|
|
|
|
;;;_ + Top Level
|
|
|
|
;; First lets revise some PL/SQL syntax.
|
|
|
|
;; Anonymous PL/SQL blocks have the following form:
|
|
;;
|
|
;; declare
|
|
;; SECTION --- variable declaration
|
|
;; begin
|
|
;; SECTION --- executable statements
|
|
;; exception
|
|
;; SECTION --- catch errors
|
|
;; end;
|
|
;;
|
|
;; or minimally
|
|
;;
|
|
;; begin
|
|
;; SECTION --- executable statements
|
|
;; end;
|
|
|
|
;; Modules are essentially just named blocks --- instead of the keyword
|
|
;; "declare" they have a "specification" which takes one of the following
|
|
;; forms:
|
|
|
|
;; [create [or replace]]
|
|
;; procedure NAME [body] [(ARGLIST)]
|
|
;; is
|
|
;;
|
|
;; or
|
|
;;
|
|
;; [create [or replace]]
|
|
;; function NAME [body] [(ARGLIST)]
|
|
;; return TYPE
|
|
;; is
|
|
;;
|
|
;; (Note: "as" is a synonym for "is")
|
|
|
|
;; Now since some indentation rules only apply to specific sections, the
|
|
;; indentation logic can be vastly simplified by first establishing which
|
|
;; section the current point is in. There are 4 sections:
|
|
;;
|
|
;; (1) specification
|
|
;; (2) declaration
|
|
;; (3) executable
|
|
;; (4) exception
|
|
;;
|
|
;; In addition there is the case when we are outside of all these
|
|
;; sections. package level/ module level.
|
|
;; called only once but we build it outside to speed things up
|
|
(defvar plsql-top-level-flush-re ""
|
|
"Regexp matching the start of top level statements that should be eft flush.")
|
|
|
|
(when (plsql-reset plsql-top-level-flush-re)
|
|
(setq plsql-top-level-flush-re
|
|
(regexp-opt
|
|
(list
|
|
"create"
|
|
"begin"
|
|
"declare"
|
|
"function"
|
|
"procedure"
|
|
"trigger"
|
|
"package"
|
|
) 'word)
|
|
))
|
|
|
|
;; Not c
|
|
(defun plsql-top-level-indent (limit)
|
|
"The last nail in the coffin for a really rare case. Everything
|
|
outside of a package body or spec is left flushed."
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-top-level-indent 'append))
|
|
|
|
(back-to-indentation)
|
|
(plsql-indent-to-col
|
|
(if (looking-at plsql-top-level-flush-re)
|
|
0 ;; special case
|
|
(plsql-statement-level-adjust (point) 0))))
|
|
;
|
|
(defvar plsql-section-start-re ""
|
|
"Regexp matching the beginning of (or a boundary within) a new type
|
|
of section.")
|
|
|
|
;; called only once but we build it outside to speed things up
|
|
(when (plsql-reset plsql-section-start-re)
|
|
(setq plsql-section-start-re
|
|
(concat
|
|
"\\(?:"
|
|
"^\\|"
|
|
plsql-white-space-re ;; whitespace delimits keywords
|
|
"\\)"
|
|
;; "\\("
|
|
(regexp-opt
|
|
(list
|
|
"end" ;; special case: could be module, except, or exec
|
|
"begin"
|
|
"declare"
|
|
"function"
|
|
"procedure"
|
|
"trigger"
|
|
"package"
|
|
;; NB: "exception" keyword is also a type so we can't use
|
|
;; it as a section start keyword!
|
|
) t)
|
|
;; "\\)"
|
|
"\\(;"
|
|
"\\|"
|
|
plsql-white-space-re ;; whitespace delimits keywords
|
|
"\\)"
|
|
)))
|
|
|
|
;; Not called. Could be customizable?
|
|
|
|
(defun plsql-comment-indent (&optional start)
|
|
"Indent the current line at least as much as the previous."
|
|
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-comment-indent 'append))
|
|
|
|
(let ((new-indent (save-excursion
|
|
(condition-case nil
|
|
(forward-line -1)
|
|
(error nil))
|
|
(current-indentation))))
|
|
(unless (> (current-indentation) new-indent)
|
|
(delete-horizontal-space)
|
|
(indent-to new-indent)))
|
|
)
|
|
|
|
;; Modules and blocks can nest so the following needs to work as well:
|
|
|
|
;; create or replace package body NAME is
|
|
;;
|
|
;; procedure NAME
|
|
;; is
|
|
;;
|
|
;; procedure NAME is
|
|
;; begin
|
|
;; SECTIONS
|
|
;; end NAME;
|
|
;;
|
|
;; procedure NAME is
|
|
;; begin
|
|
;; SECTIONS
|
|
;; end NAME;
|
|
;;
|
|
;; ...
|
|
;;
|
|
;; begin
|
|
;;
|
|
;; begin
|
|
;; STATEMENT
|
|
;; end;
|
|
;;
|
|
;; begin
|
|
;; STATEMENT
|
|
;; end;
|
|
;;
|
|
;; ...
|
|
;;
|
|
;; end NAME;
|
|
;;
|
|
;; end NAME;
|
|
|
|
(defun plsql-after-end-indent (limit)
|
|
"Indent line following a an \"end\" keyword that begins at
|
|
LIMIT. Determines the context and passes the job on to the appropriate
|
|
indentation function."
|
|
|
|
;; PRE CONDITION:
|
|
;; (1) POINT is at the current indentation of the line we are
|
|
;; indenting.
|
|
;; (2) The word starting at LIMIT is "end"
|
|
;;
|
|
;; POST CONDITION: current line is indented correctly.
|
|
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-after-end-indent 'append))
|
|
|
|
(let (indent-function match)
|
|
;; (if (looking-at ""
|
|
|
|
(if (save-excursion
|
|
(goto-char (+ limit 3)) ;; after "end"
|
|
(skip-chars-forward " \t\n\r") ;; whitespace
|
|
(looking-at "\\(if\\|loop\\|case\\)[ \t\n\r]*;"))
|
|
;; after a compound statement (most common case)
|
|
(setq indent-function 'plsql-exec/except-sec-indent)
|
|
|
|
;; find the previous section start with indentation level less
|
|
;; than that of the "end" keyword
|
|
|
|
(save-excursion
|
|
(goto-char limit)
|
|
(let ((end-indent (current-indentation)))
|
|
(while (and
|
|
(> end-indent 0)
|
|
(plsql-re-search-backward plsql-section-start-re nil t nil nil)
|
|
(goto-char (match-beginning 1))
|
|
(>= (current-indentation) end-indent)))))
|
|
|
|
(setq match (downcase (match-string 1)))
|
|
(setq limit (match-beginning 1)) ;; reset the limit
|
|
|
|
(cond ((string-match "begin\\|end" match)
|
|
;; after an anonymous block inside an executable section
|
|
;; can we have anonymous blocks inside exception sections?
|
|
(setq indent-function 'plsql-exec-sec-indent))
|
|
|
|
((or (string-equal "procedure" match)
|
|
(string-equal "function" match))
|
|
;; after a module declaration _inside_ a declaration section
|
|
(setq indent-function 'plsql-dec-sec-indent))
|
|
|
|
((string-equal "package" match)
|
|
;; after a module declaration _inside_ a package
|
|
(setq indent-function 'plsql-package-indent))
|
|
|
|
(t
|
|
;; unpackaged block or
|
|
(message "unpackaged block")
|
|
(setq indent-function 'plsql-top-level-indent))
|
|
))
|
|
|
|
(funcall indent-function limit)))
|
|
|
|
|
|
(defun plsql-exec/except-sec-indent (limit)
|
|
"Special case: exception keyword is also a type so we can't use it
|
|
as a section start keyword. We have to do this outside of the first
|
|
save-excursion of `plsql-indent'."
|
|
|
|
;; PRE CONDITION:
|
|
;; (1) POINT is at the current indentation of the line we are
|
|
;; indenting.
|
|
;; (2) The section-start at LIMIT is either:
|
|
;;
|
|
;; (a) "begin", or
|
|
;; (b) "end" followed by "if", "loop", or "case"
|
|
;;
|
|
;; so we are either in an executable or exception-like
|
|
;; section. Since a "case" construct behaves just like an exception
|
|
;; section, we pass that case (no pun intended:-) onto
|
|
;; `plsql-except-sec-indent'.
|
|
|
|
;;
|
|
;; POST CONDITION: current line is indented correctly.
|
|
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack 'plsql-exec/except-sec-indent 'append))
|
|
|
|
(let ((indent-function 'plsql-exec-sec-indent)) ;; the default
|
|
|
|
(save-excursion ;; override the default?
|
|
(back-to-indentation)
|
|
(when (plsql-re-search-backward "[ \t\n\r]\\(exception\\|case\\)[ \t\n\r]"
|
|
limit t nil limit)
|
|
(setq limit (match-beginning 1)) ;; new limit
|
|
(unless (and (string-equal "case" (match-string 1))
|
|
(back-to-indentation)
|
|
(looking-at "end"))
|
|
;; really inside an exception like section
|
|
(setq indent-function 'plsql-except-sec-indent))
|
|
))
|
|
(funcall indent-function limit)
|
|
))
|
|
|
|
|
|
(defun plsql-indent ()
|
|
"Indent the current line appropriate to the current structural unit."
|
|
(interactive)
|
|
|
|
(let ((target-col nil)
|
|
(target-start nil)
|
|
(indent-function nil)
|
|
(limit 1)
|
|
(match nil)
|
|
(in-comment-p nil)
|
|
(in-string-p nil)
|
|
(case-fold-search t));; ignore case in searches
|
|
|
|
;; first guess at indentation context
|
|
;; and set search limits
|
|
(save-excursion
|
|
(back-to-indentation)
|
|
(cond (;; skip comments
|
|
(and (funcall plsql-in-comment-predicate 1)
|
|
(not (looking-at "--"))) ;; pass this comment starter on
|
|
(setq in-comment-p t)) ;; do nothing for the moment
|
|
|
|
(;; skip strings
|
|
(and (funcall plsql-in-string-predicate 1)
|
|
(not (looking-at "'"))) ;; pass on string starters
|
|
(setq in-string-p t)) ;; do nothing for the moment
|
|
|
|
(;; indent after first block starter
|
|
(plsql-re-search-backward
|
|
plsql-section-start-re nil t nil nil)
|
|
|
|
;; determine section type and pass on to appropriate
|
|
;; indentation function
|
|
(setq match (downcase (match-string 1)))
|
|
|
|
;; get a preliminary bound for searches and parsing
|
|
(setq limit (match-beginning 1))
|
|
|
|
(cond ((string-equal "end" match) ;; most common match
|
|
(setq indent-function 'plsql-after-end-indent))
|
|
|
|
((string-equal "begin" match)
|
|
;; inside an execution or exception type section
|
|
(setq indent-function 'plsql-exec/except-sec-indent))
|
|
|
|
;; inside a declaration section
|
|
((string-equal "declare" match)
|
|
(setq indent-function 'plsql-dec-sec-indent))
|
|
|
|
;; inside either a declaration section or a
|
|
;; package specification
|
|
((or (string-equal "procedure" match)
|
|
(string-equal "function" match))
|
|
|
|
(plsql-re-search-forward
|
|
plsql-dec-sec-stmnt-end-re
|
|
nil t nil limit)
|
|
|
|
(if (string-match "is\\|as" (or (match-string 1) "dummy"))
|
|
;; it really a declaration section
|
|
(setq indent-function 'plsql-dec-sec-indent)
|
|
;; no its really a package specification
|
|
(setq indent-function 'plsql-package-indent)))
|
|
|
|
;; inside a package definition but before any declarations
|
|
((string-equal "package" match)
|
|
(setq indent-function 'plsql-package-indent))
|
|
))
|
|
|
|
(t ;; indent before first block starter
|
|
;; so we might be looking at block-less PL/SQL
|
|
(setq limit 1)
|
|
(cond ((plsql-re-search-backward
|
|
plsql-exec-sec-stmnt-end-re nil t nil nil)
|
|
;; bare pl/sql
|
|
(setq indent-function 'plsql-exec/except-sec-indent))
|
|
|
|
((looking-at plsql-section-start-re)
|
|
(setq indent-function 'plsql-package-indent))
|
|
|
|
(t ;; start of bare pl/sql
|
|
(setq indent-function 'plsql-top-level-indent)
|
|
)))
|
|
))
|
|
|
|
(setq plsql-indent-function-stack nil)
|
|
;; We deal with parentheses and sql blocks differently. We can't move these
|
|
;; tests higher because they really need the search limit to be set first.
|
|
|
|
(save-excursion
|
|
(back-to-indentation)
|
|
(when (and (not in-comment-p) (not in-string-p))
|
|
(cond (;; parenthesis
|
|
(setq target-col (plsql-in-parenthesis-p limit))
|
|
(setq indent-function 'plsql-parenthesis-indent)
|
|
(setq limit target-col))
|
|
|
|
(;; SQL block
|
|
(setq target-start (plsql-in-sql-block-p limit))
|
|
(setq indent-function 'plsql-sql-block-indent)
|
|
(setq limit target-start))
|
|
))
|
|
|
|
(when indent-function
|
|
(when plsql-debug
|
|
(add-to-list 'plsql-indent-function-stack indent-function 'append))
|
|
(funcall indent-function limit)))
|
|
|
|
;; maybe reposition cursor to the start of the indentation
|
|
;; (only happens when indenting a blank line)
|
|
(if (= (current-column) 0) (skip-chars-forward " \t"))
|
|
(when plsql-debug
|
|
;; print a pseudo stack trace
|
|
(message "%s"
|
|
(mapconcat
|
|
(lambda (x)
|
|
(when x (substring (substring (symbol-name x) 6) 0 -7)))
|
|
plsql-indent-function-stack
|
|
" -> ")))
|
|
))
|
|
|
|
;;;_ + Imenu
|
|
|
|
(eval-and-compile (require 'imenu)) ;; quieten compiler
|
|
|
|
(defvar plsql-imenu-title "Contents"
|
|
"*Title of the menu which will be added to the menu bar.")
|
|
|
|
(defvar plsql-imenu-regexp ""
|
|
"*A regular expression matching a head line to be added to the menu.")
|
|
|
|
(when (plsql-reset plsql-imenu-regexp)
|
|
(setq plsql-imenu-regexp
|
|
(concat
|
|
"\\(?:"
|
|
"^\\|"
|
|
plsql-white-space-re
|
|
;; "[ \t\n]*" ;; leading whitespace
|
|
"\\)"
|
|
"\\("
|
|
"" "package"
|
|
"\\|" "function"
|
|
"\\|" "procedure"
|
|
"\\)"
|
|
plsql-white-space-re ;; trailing whitespace
|
|
)))
|
|
|
|
;; Thanks to Bret (?) for spotting this one
|
|
(eval-when-compile (require 'cl))
|
|
|
|
;; This doesn't work? Why?
|
|
;;
|
|
;;(defun push (obj stack)
|
|
;; "Poor man's push (rather than requiring 'cl)."
|
|
;; (setf stack (cons obj stack)))
|
|
|
|
;; Make an index for imenu
|
|
(defun plsql-imenu-index ()
|
|
"Return an table of contents for an PL/SQL buffer for use with Imenu."
|
|
(interactive)
|
|
(let ((case-fold-search t)
|
|
(toc-string nil)
|
|
(procedure-alist '())
|
|
(variable-alist '())
|
|
(function-alist '())
|
|
(type-alist '())
|
|
(package-alist '())
|
|
(index-alist '())
|
|
(in-body nil)
|
|
(body-defn nil)
|
|
(spec-defn nil)
|
|
(package nil)
|
|
prev-pos ;; dummy used by imenu-progress-message
|
|
match start end indent)
|
|
|
|
(save-excursion
|
|
(goto-char (point-max))
|
|
(imenu-progress-message prev-pos 0)
|
|
(while (and (plsql-re-search-backward plsql-imenu-regexp nil t)
|
|
(not (funcall plsql-in-string-predicate 1)))
|
|
|
|
(imenu-progress-message prev-pos nil t)
|
|
(setq end (match-end 1)
|
|
start (match-beginning 1)
|
|
match (downcase (match-string 1)))
|
|
(goto-char start)
|
|
(beginning-of-line)
|
|
(setq indent (/ (current-indentation) plsql-indent))
|
|
;; extra indent for local modules
|
|
(setq indent (if (< indent 2) ""
|
|
(make-string (* (- indent 1) 2) ?\ )))
|
|
(goto-char end)
|
|
(save-excursion
|
|
(skip-chars-forward "[ \t\n\r]+")
|
|
(when (looking-at "body")
|
|
(setq in-body t)
|
|
(forward-word 1)
|
|
(skip-chars-forward "[ \t\n\r]+"))
|
|
(setq toc-string (concat indent (thing-at-point 'symbol)))
|
|
(cond ((string-equal match "procedure")
|
|
(push (cons toc-string (point)) procedure-alist))
|
|
((string-equal match "function")
|
|
(push (cons toc-string (point)) function-alist))
|
|
((string-equal match "type")
|
|
(push (cons toc-string (point)) type-alist))
|
|
|
|
((string-equal match "package")
|
|
|
|
(if in-body
|
|
(progn
|
|
(setq package (concat toc-string" body"))
|
|
(setq body-defn t))
|
|
(setq package (concat toc-string" spec"))
|
|
(setq spec-defn t))
|
|
|
|
;; try to minimise the number of sublists
|
|
(if procedure-alist
|
|
(if (or function-alist type-alist)
|
|
(push (cons "Procedures" procedure-alist) package-alist)
|
|
(setq package-alist procedure-alist)))
|
|
|
|
(if function-alist
|
|
(if (or procedure-alist type-alist)
|
|
(push (cons "Functions" function-alist) package-alist)
|
|
(setq package-alist function-alist)))
|
|
|
|
(if type-alist
|
|
(if (or procedure-alist function-alist)
|
|
(push (cons "Types" type-alist) package-alist)
|
|
(setq package-alist type-alist)))
|
|
|
|
(when package-alist ;; don't do it if empty
|
|
(push (cons package package-alist) index-alist))
|
|
|
|
(setq procedure-alist '()
|
|
function-alist '()
|
|
type-alist '()
|
|
package-alist '()
|
|
in-body nil))
|
|
|
|
(t
|
|
(push (cons toc-string (point)) variable-alist)
|
|
))
|
|
)
|
|
(goto-char start)
|
|
))
|
|
(imenu-progress-message prev-pos 100)
|
|
|
|
(unless package
|
|
;; else objects are package less
|
|
(if procedure-alist
|
|
(if (or function-alist type-alist)
|
|
(push (cons "Procedures" procedure-alist) index-alist)
|
|
(setq index-alist procedure-alist)))
|
|
|
|
(if function-alist
|
|
(if (or procedure-alist type-alist)
|
|
(push (cons "Functions" function-alist) index-alist)
|
|
(setq index-alist function-alist)))
|
|
|
|
(if type-alist
|
|
(if (or procedure-alist function-alist)
|
|
(push (cons "Types" type-alist) index-alist)
|
|
(setq index-alist type-alist))))
|
|
|
|
(unless (and body-defn spec-defn)
|
|
;; pop off the superfluous identifier
|
|
(setq index-alist (cdr (car index-alist))))
|
|
|
|
index-alist))
|
|
|
|
(defun plsql-imenu-setup ()
|
|
"*Setup the variables to support imenu."
|
|
(interactive)
|
|
(setq imenu-case-fold-search t)
|
|
(setq imenu-sort-function nil) ;; sorting the menu defeats the purpose
|
|
(setq imenu-create-index-function 'plsql-imenu-index)
|
|
(imenu-add-to-menubar plsql-imenu-title)
|
|
)
|
|
|
|
;;;_ + Align
|
|
|
|
;; Should I make so that anything that is highlighted will line up?
|
|
;; Should we make a block anything inside ()?
|
|
|
|
(eval-and-compile
|
|
|
|
(defcustom plsql-align-rules-list '() ""
|
|
:group 'plsql
|
|
:type 'align-rules-list-type)
|
|
|
|
;; Should I make so that anything that is highlighted will line up?
|
|
;; Should we make a block anything inside ()?
|
|
|
|
(when (condition-case nil
|
|
(require 'align)
|
|
(error nil))
|
|
|
|
;; these are way too slow to use with indent before aligning
|
|
(unless (and plsql-align-rules-list plsql-debug)
|
|
(setq plsql-align-rules-list
|
|
'(
|
|
(plsql-assignment
|
|
(regexp . "\\(\\s-*\\):=\\(\\s-*\\)")
|
|
(group . (1 2))
|
|
(modes . '(plsql-mode))
|
|
(repeat t)
|
|
(tab-stop . nil))
|
|
|
|
(plsql-arrorw
|
|
(regexp . "\\(\\s-*\\)=>\\(\\s-*\\)")
|
|
(group . (1 2))
|
|
(modes . '(plsql-mode))
|
|
(repeat t)
|
|
(tab-stop . nil))
|
|
|
|
(plsql-equals ;; exclude the previous two cases
|
|
(regexp . "\\(\\s-*[^:]\\)=\\([^>]\\s-*\\)")
|
|
(group . (1 2))
|
|
(repeat t)
|
|
(tab-stop . nil)
|
|
(modes . '(plsql-mode)))
|
|
|
|
(plsql-operator ;; watch out for comments
|
|
(regexp . "\\(\\s-*\\)[-+/]{1}\\(\\s-*\\)")
|
|
(group . (1 2))
|
|
(repeat t)
|
|
(tab-stop . nil)
|
|
(modes . '(plsql-mode)))
|
|
|
|
(plsql-keywords
|
|
(regexp . "\\(\\s-+\\)\\(in\\|default\\|number\\|varchar2\\|blob\\|raw\\)\\b")
|
|
(group 1)
|
|
(repeat t)
|
|
(case-fold t)
|
|
(tab-stop . nil)
|
|
(modes . '(plsql-mode)))
|
|
)
|
|
))
|
|
|
|
(put 'plsql-align-rules-list 'risky-local-variable t)
|
|
(add-to-list 'align-c++-modes 'plsql-mode) ;; eg expression functions ...
|
|
(add-to-list 'align-sq-string-modes 'plsql-mode)
|
|
(add-to-list 'align-open-comment-modes 'plsql-mode)
|
|
|
|
;; Should we re-bind new-line-and-indent to align the current
|
|
;; region? That sounds expensive.
|
|
))
|
|
|
|
;;;_ + Font-Lock
|
|
|
|
(defvar plsql-oracle-font-lock-fix-re "")
|
|
|
|
(when (or (null plsql-oracle-font-lock-fix-re) plsql-debug)
|
|
(setq plsql-oracle-font-lock-fix-re
|
|
(list (cons
|
|
;; these guys would otherwise be in font-lock-function-name-face
|
|
(concat
|
|
"\\b" "\\(" (regexp-opt
|
|
(list
|
|
"if"
|
|
"then"
|
|
"when"
|
|
"else"
|
|
"elsif"
|
|
"begin"
|
|
"end"
|
|
"loop"
|
|
"for"
|
|
"while"
|
|
"return"
|
|
"exit"
|
|
)) "\\)" "\\b")
|
|
'font-lock-keyword-face)
|
|
(cons
|
|
(concat
|
|
"\\b" "\\(" (regexp-opt
|
|
(list
|
|
"true"
|
|
"false"
|
|
"number"
|
|
"raw"
|
|
)) "\\)" "\\b")
|
|
'font-lock-type-face)
|
|
|
|
(cons
|
|
(concat
|
|
"\\b" "\\(" (regexp-opt
|
|
(list
|
|
"open"
|
|
"fetch"
|
|
"close"
|
|
"count"
|
|
)) "\\)" "\\b")
|
|
'font-lock-builtin-face)
|
|
|
|
;; types and properties
|
|
(cons
|
|
"%[_#:$a-z,A-Z]+"
|
|
'font-lock-constant-face)
|
|
|
|
;; err should probably use an anchored highlight
|
|
(cons
|
|
(concat
|
|
"\\b"
|
|
"\\(" "function"
|
|
"\\|" "procedure"
|
|
"\\|" "package body" ;; ick damn those groupings
|
|
"\\|" "package"
|
|
"\\)"
|
|
plsql-white-space-re
|
|
"\\([_#:$a-z,A-Z]+\\)")
|
|
(list
|
|
'(1 font-lock-type-face)
|
|
'(2 font-lock-function-name-face))
|
|
)
|
|
|
|
;; this guy is bad
|
|
(cons
|
|
(concat
|
|
"\\b" "\\(" (regexp-opt
|
|
(list
|
|
"language"
|
|
)) "\\)" "\\b")
|
|
''default)
|
|
)))
|
|
|
|
;;;_ + Mode
|
|
|
|
(eval-when-compile (require 'sql)) ;; quieten compiler
|
|
|
|
(defun plsql-indent-region (beg end)
|
|
"Indent the region between BEG and END with a progress display."
|
|
(interactive "*r")
|
|
(goto-char beg)
|
|
(let* ((line-count (count-lines beg end))
|
|
(lines-indented 0)
|
|
(lines-remaining line-count)
|
|
(endmark (copy-marker end)))
|
|
(while (< (point) endmark)
|
|
;; Report % progress every every 40 lines
|
|
(when (> lines-indented 39)
|
|
(setq lines-remaining (- lines-remaining lines-indented)
|
|
lines-indented 0)
|
|
(message "Indenting region...(%d%%)"
|
|
(/ (* (- line-count lines-remaining) 100) line-count)))
|
|
(plsql-indent)
|
|
(forward-line 1)
|
|
(setq lines-indented (1+ lines-indented)))
|
|
(message "Indenting region...done")))
|
|
|
|
(defun plsql-mode ()
|
|
"Programming support mode for PL/SQL code."
|
|
|
|
(interactive)
|
|
(require 'sql)
|
|
|
|
;; (modify-syntax-entry ?# "w" sql-mode-syntax-table)
|
|
;; (modify-syntax-entry ?_ "w" sql-mode-syntax-table)
|
|
;; (modify-syntax-entry ?$ "w" sql-mode-syntax-table)
|
|
|
|
(setq sql-mode-font-lock-keywords
|
|
(append plsql-oracle-font-lock-fix-re ;; override some bad bits
|
|
sql-mode-oracle-font-lock-keywords))
|
|
(setq font-lock-mark-block-function 'mark-visible)
|
|
(sql-mode)
|
|
|
|
(setq major-mode 'plsql-mode)
|
|
(setq mode-name "PL/SQL")
|
|
|
|
(if plsql-uses-font-lock
|
|
(progn
|
|
(setq plsql-in-comment-predicate 'plsql-comment-face-p)
|
|
(setq plsql-in-string-predicate 'plsql-string-face-p))
|
|
(setq plsql-in-comment-predicate 'plsql-in-comment-p)
|
|
(setq plsql-in-string-predicate 'plsql-in-string-p))
|
|
|
|
(plsql-imenu-setup)
|
|
|
|
(set (make-local-variable 'indent-line-function) 'plsql-indent)
|
|
(set (make-local-variable 'indent-region-function) 'plsql-indent-region)
|
|
(set (make-local-variable 'align-mode-rules-list) 'plsql-align-rules-list)
|
|
(local-set-key [(return)] 'newline-and-indent)
|
|
(run-hooks 'plsql-mode-hook)
|
|
)
|
|
|
|
(provide 'plsql)
|
|
|
|
;;; plsql.el ends here
|