Mercurial > hg > xemacs-beta
diff lisp/modes/pascal.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | 0293115a14e9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/modes/pascal.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,1563 @@ +;;; pascal.el - Major mode for editing pascal source in emacs. + +;;; Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc. + +;;; Author: Espen Skoglund (espensk@stud.cs.uit.no) +;;; Keywords: languages + +;; This file is part of XEmacs. + +;; XEmacs 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. + +;; XEmacs 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 XEmacs; see the file COPYING. If not, write to the Free +;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Synched up with: FSF 19.30. + +;;; Commentary: + +;;; USAGE +;;; ===== + +;;; Emacs should enter Pascal mode when you find a Pascal source file. +;;; When you have entered Pascal mode, you may get more info by pressing +;;; C-h m. You may also get online help describing various functions by: +;;; C-h f <Name of function you want described> + +;;; If you want to customize Pascal mode to fit you better, you may add +;;; these lines (the values of the variables presented here are the defaults): +;;; +;;; ;; User customization for Pascal mode +;;; (setq pascal-indent-level 3 +;;; pascal-case-indent 2 +;;; pascal-auto-newline nil +;;; pascal-tab-always-indent t +;;; pascal-auto-endcomments t +;;; pascal-auto-lineup '(all) +;;; pascal-toggle-completions nil +;;; pascal-type-keywords '("array" "file" "packed" "char" +;;; "integer" "real" "string" "record") +;;; pascal-start-keywords '("begin" "end" "function" "procedure" +;;; "repeat" "until" "while" "read" "readln" +;;; "reset" "rewrite" "write" "writeln") +;;; pascal-separator-keywords '("downto" "else" "mod" "div" "then")) + +;;; KNOWN BUGS / BUGREPORTS +;;; ======================= +;;; As far as I know, there are no bugs in the current version of this +;;; package. This may not be true however, since I never use this mode +;;; myself and therefore would never notice them anyway. If you do +;;; find any bugs, you may submit them to: espensk@stud.cs.uit.no +;;; as well as to bug-gnu-emacs@prep.ai.mit.edu. + +;;; Code: + +(defconst pascal-mode-version "2.4" + "Version of `pascal.el'.") + +(defvar pascal-mode-abbrev-table nil + "Abbrev table in use in Pascal-mode buffers.") +(define-abbrev-table 'pascal-mode-abbrev-table ()) + +(defvar pascal-mode-map () + "Keymap used in Pascal mode.") +(if pascal-mode-map + () + (setq pascal-mode-map (make-sparse-keymap)) + (define-key pascal-mode-map ";" 'electric-pascal-semi-or-dot) + (define-key pascal-mode-map "." 'electric-pascal-semi-or-dot) + (define-key pascal-mode-map ":" 'electric-pascal-colon) + (define-key pascal-mode-map "=" 'electric-pascal-equal) + (define-key pascal-mode-map "#" 'electric-pascal-hash) + (define-key pascal-mode-map "\r" 'electric-pascal-terminate-line) + (define-key pascal-mode-map "\t" 'electric-pascal-tab) + (define-key pascal-mode-map "\M-\t" 'pascal-complete-word) + (define-key pascal-mode-map "\M-?" 'pascal-show-completions) + (define-key pascal-mode-map "\177" 'backward-delete-char-untabify) + (define-key pascal-mode-map "\M-\C-h" 'pascal-mark-defun) + (define-key pascal-mode-map "\C-c\C-b" 'pascal-insert-block) + (define-key pascal-mode-map "\M-*" 'pascal-star-comment) + (define-key pascal-mode-map "\C-c\C-c" 'pascal-comment-area) + (define-key pascal-mode-map "\C-c\C-u" 'pascal-uncomment-area) + (define-key pascal-mode-map "\M-\C-a" 'pascal-beg-of-defun) + (define-key pascal-mode-map "\M-\C-e" 'pascal-end-of-defun) + (define-key pascal-mode-map "\C-c\C-d" 'pascal-goto-defun) + (define-key pascal-mode-map "\C-c\C-o" 'pascal-outline) +;;; A command to change the whole buffer won't be used terribly +;;; often, so no need for a key binding. +; (define-key pascal-mode-map "\C-cd" 'pascal-downcase-keywords) +; (define-key pascal-mode-map "\C-cu" 'pascal-upcase-keywords) +; (define-key pascal-mode-map "\C-cc" 'pascal-capitalize-keywords) + ) + +;(defvar pascal-imenu-generic-expression +; '("^[ \t]*\\(function\\|procedure\\)[ \t\n]+\\([a-zA-Z0-9_.:]+\\)" . (2)) +; "Imenu expression for Pascal-mode. See `imenu-generic-expression'.") + +(defvar pascal-keywords + '("and" "array" "begin" "case" "const" "div" "do" "downto" "else" "end" + "file" "for" "function" "goto" "if" "in" "label" "mod" "nil" "not" "of" + "or" "packed" "procedure" "program" "record" "repeat" "set" "then" "to" + "type" "until" "var" "while" "with" + ;; The following are not standard in pascal, but widely used. + "get" "put" "input" "output" "read" "readln" "reset" "rewrite" "write" + "writeln")) + +;;; +;;; Regular expressions used to calculate indent, etc. +;;; +(defconst pascal-symbol-re "\\<[a-zA-Z_][a-zA-Z_0-9.]*\\>") +(defconst pascal-beg-block-re "\\<\\(begin\\|case\\|record\\|repeat\\)\\>") +(defconst pascal-end-block-re "\\<\\(end\\|until\\)\\>") +(defconst pascal-declaration-re "\\<\\(const\\|label\\|type\\|var\\)\\>") +(defconst pascal-defun-re "\\<\\(function\\|procedure\\|program\\)\\>") +(defconst pascal-sub-block-re "\\<\\(if\\|else\\|for\\|while\\|with\\)\\>") +(defconst pascal-noindent-re "\\<\\(begin\\|end\\|until\\|else\\)\\>") +(defconst pascal-nosemi-re "\\<\\(begin\\|repeat\\|then\\|do\\|else\\)\\>") +(defconst pascal-autoindent-lines-re + "\\<\\(label\\|var\\|type\\|const\\|until\\|end\\|begin\\|repeat\\|else\\)\\>") + +;;; Strings used to mark beginning and end of excluded text +(defconst pascal-exclude-str-start "{-----\\/----- EXCLUDED -----\\/-----") +(defconst pascal-exclude-str-end " -----/\\----- EXCLUDED -----/\\-----}") + +(defvar pascal-mode-syntax-table nil + "Syntax table in use in Pascal-mode buffers.") + +(if pascal-mode-syntax-table + () + (setq pascal-mode-syntax-table (make-syntax-table)) + (modify-syntax-entry ?\\ "." pascal-mode-syntax-table) + (modify-syntax-entry ?( "()1" pascal-mode-syntax-table) + (modify-syntax-entry ?) ")(4" pascal-mode-syntax-table) + (modify-syntax-entry ?* ". 23" pascal-mode-syntax-table) + (modify-syntax-entry ?{ "<" pascal-mode-syntax-table) + (modify-syntax-entry ?} ">" pascal-mode-syntax-table) + (modify-syntax-entry ?+ "." pascal-mode-syntax-table) + (modify-syntax-entry ?- "." pascal-mode-syntax-table) + (modify-syntax-entry ?= "." pascal-mode-syntax-table) + (modify-syntax-entry ?% "." pascal-mode-syntax-table) + (modify-syntax-entry ?< "." pascal-mode-syntax-table) + (modify-syntax-entry ?> "." pascal-mode-syntax-table) + (modify-syntax-entry ?& "." pascal-mode-syntax-table) + (modify-syntax-entry ?| "." pascal-mode-syntax-table) + (modify-syntax-entry ?_ "w" pascal-mode-syntax-table) + (modify-syntax-entry ?\' "\"" pascal-mode-syntax-table)) + +(defconst pascal-font-lock-keywords (purecopy + (list + '("^[ \t]*\\(function\\|pro\\(cedure\\|gram\\)\\)\\>[ \t]*\\(\\sw+\\)?" + 1 font-lock-keyword-face) + '("^[ \t]*\\(function\\|pro\\(cedure\\|gram\\)\\)\\>[ \t]*\\(\\sw+\\)?" + 3 font-lock-function-name-face t) +; ("type" "const" "real" "integer" "char" "boolean" "var" +; "record" "array" "file") + (cons (concat "\\<\\(array\\|boolean\\|c\\(har\\|onst\\)\\|file\\|" + "integer\\|re\\(al\\|cord\\)\\|type\\|var\\)\\>") + 'font-lock-type-face) + '("\\<\\(label\\|external\\|forward\\)\\>" . font-lock-function-name-face) + '("\\<\\([0-9]+\\)[ \t]*:" 1 font-lock-function-name-face) +; ("of" "to" "for" "if" "then" "else" "case" "while" +; "do" "until" "and" "or" "not" "in" "with" "repeat" "begin" "end") + (concat "\\<\\(" + "and\\|begin\\|case\\|do\\|e\\(lse\\|nd\\)\\|for\\|i[fn]\\|" + "not\\|o[fr]\\|repeat\\|t\\(hen\\|o\\)\\|until\\|w\\(hile\\|ith\\)" + "\\)\\>") + '("\\<\\(goto\\)\\>[ \t]*\\([0-9]+\\)?" + 1 font-lock-keyword-face) + '("\\<\\(goto\\)\\>[ \t]*\\([0-9]+\\)?" + 2 font-lock-keyword-face t))) + "Additional expressions to highlight in Pascal mode.") +(put 'pascal-mode 'font-lock-defaults '(pascal-font-lock-keywords nil t)) + +(defvar pascal-indent-level 3 + "*Indentation of Pascal statements with respect to containing block.") + +(defvar pascal-case-indent 2 + "*Indentation for case statements.") + +(defvar pascal-auto-newline nil + "*Non-nil means automatically newline after simcolons and the punctation mark +after an end.") + +(defvar pascal-tab-always-indent t + "*Non-nil means TAB in Pascal mode should always reindent the current line, +regardless of where in the line point is when the TAB command is used.") + +(defvar pascal-auto-endcomments t + "*Non-nil means a comment { ... } is set after the ends which ends cases and +functions. The name of the function or case will be set between the braces.") + +(defvar pascal-auto-lineup '(all) + "*List of contexts where auto lineup of :'s or ='s should be done. +Elements can be of type: 'paramlist', 'declaration' or 'case', which will +do auto lineup in parameterlist, declarations or case-statements +respectively. The word 'all' will do all lineups. '(case paramlist) for +instance will do lineup in case-statements and parameterlist, while '(all) +will do all lineups.") + +(defvar pascal-toggle-completions nil + "*Non-nil means that \\<pascal-mode-map>\\[pascal-complete-label] should \ +not display a completion buffer when +the label couldn't be completed, but instead toggle the possible completions +with repeated \\[pascal-complete-label]'s.") + +(defvar pascal-type-keywords + '("array" "file" "packed" "char" "integer" "real" "string" "record") + "*Keywords for types used when completing a word in a declaration or parmlist. +\(eg. integer, real, char.) The types defined within the Pascal program +will be completed runtime, and should not be added to this list.") + +(defvar pascal-start-keywords + '("begin" "end" "function" "procedure" "repeat" "until" "while" + "read" "readln" "reset" "rewrite" "write" "writeln") + "*Keywords to complete when standing at the first word of a statement. +\(eg. begin, repeat, until, readln.) +The procedures and variables defined within the Pascal program +will be completed runtime and should not be added to this list.") + +(defvar pascal-separator-keywords + '("downto" "else" "mod" "div" "then") + "*Keywords to complete when NOT standing at the first word of a statement. +\(eg. downto, else, mod, then.) +Variables and function names defined within the +Pascal program are completed runtime and should not be added to this list.") + +;;; +;;; Macros +;;; + +(defsubst pascal-get-beg-of-line (&optional arg) + (save-excursion + (beginning-of-line arg) + (point))) + +(defsubst pascal-get-end-of-line (&optional arg) + (save-excursion + (end-of-line arg) + (point))) + +(defun pascal-declaration-end () + (let ((nest 1)) + (while (and (> nest 0) + (re-search-forward + "[:=]\\|\\(\\<record\\>\\)\\|\\(\\<end\\>\\)" + (save-excursion (end-of-line 2) (point)) t)) + (cond ((match-beginning 1) (setq nest (1+ nest))) + ((match-beginning 2) (setq nest (1- nest))) + ((looking-at "[^(\n]+)") (setq nest 0)))))) + + +(defun pascal-declaration-beg () + (let ((nest 1)) + (while (and (> nest 0) + (re-search-backward "[:=]\\|\\<\\(type\\|var\\|label\\|const\\)\\>\\|\\(\\<record\\>\\)\\|\\(\\<end\\>\\)" (pascal-get-beg-of-line 0) t)) + (cond ((match-beginning 1) (setq nest 0)) + ((match-beginning 2) (setq nest (1- nest))) + ((match-beginning 3) (setq nest (1+ nest))))) + (= nest 0))) + + +(defsubst pascal-within-string () + (save-excursion + (nth 3 (parse-partial-sexp (pascal-get-beg-of-line) (point))))) + + +;;;###autoload +(defun pascal-mode () + "Major mode for editing Pascal code. \\<pascal-mode-map> +TAB indents for Pascal code. Delete converts tabs to spaces as it moves back. + +\\[pascal-complete-word] completes the word around current point with respect \ +to position in code +\\[pascal-show-completions] shows all possible completions at this point. + +Other useful functions are: + +\\[pascal-mark-defun]\t- Mark function. +\\[pascal-insert-block]\t- insert begin ... end; +\\[pascal-star-comment]\t- insert (* ... *) +\\[pascal-comment-area]\t- Put marked area in a comment, fixing nested comments. +\\[pascal-uncomment-area]\t- Uncomment an area commented with \ +\\[pascal-comment-area]. +\\[pascal-beg-of-defun]\t- Move to beginning of current function. +\\[pascal-end-of-defun]\t- Move to end of current function. +\\[pascal-goto-defun]\t- Goto function prompted for in the minibuffer. +\\[pascal-outline]\t- Enter pascal-outline-mode (see also pascal-outline). + +Variables controlling indentation/edit style: + + pascal-indent-level (default 3) + Indentation of Pascal statements with respect to containing block. + pascal-case-indent (default 2) + Indentation for case statements. + pascal-auto-newline (default nil) + Non-nil means automatically newline after simcolons and the punctation mark + after an end. + pascal-tab-always-indent (default t) + Non-nil means TAB in Pascal mode should always reindent the current line, + regardless of where in the line point is when the TAB command is used. + pascal-auto-endcomments (default t) + Non-nil means a comment { ... } is set after the ends which ends cases and + functions. The name of the function or case will be set between the braces. + pascal-auto-lineup (default t) + List of contexts where auto lineup of :'s or ='s hould be done. + +See also the user variables pascal-type-keywords, pascal-start-keywords and +pascal-separator-keywords. + +Turning on Pascal mode calls the value of the variable pascal-mode-hook with +no args, if that value is non-nil." + (interactive) + (kill-all-local-variables) + (use-local-map pascal-mode-map) + (setq major-mode 'pascal-mode) + (setq mode-name "Pascal") + (setq local-abbrev-table pascal-mode-abbrev-table) + (set-syntax-table pascal-mode-syntax-table) + (make-local-variable 'indent-line-function) + (setq indent-line-function 'pascal-indent-line) + (setq comment-indent-function 'pascal-indent-comment) + (make-local-variable 'comment-start) + (setq comment-start "{") + (make-local-variable 'parse-sexp-ignore-comments) + (setq parse-sexp-ignore-comments nil) + (make-local-variable 'case-fold-search) + (setq case-fold-search t) + (make-local-variable 'comment-start-skip) + (setq comment-start-skip "(\\*+ *\\|{ *") + (make-local-variable 'comment-end) + (setq comment-end "}") +; (make-local-variable 'imenu-generic-expression) +; (setq imenu-generic-expression pascal-imenu-generic-expression) + (run-hooks 'pascal-mode-hook)) + + + +;;; +;;; Electric functions +;;; +(defun electric-pascal-terminate-line () + "Terminate line and indent next line." + (interactive) + ;; First, check if current line should be indented + (save-excursion + (beginning-of-line) + (skip-chars-forward " \t") + (if (looking-at pascal-autoindent-lines-re) + (pascal-indent-line))) + (delete-horizontal-space) ; Removes trailing whitespaces + (newline) + ;; Indent next line + (pascal-indent-line) + ;; Maybe we should set some endcomments + (if pascal-auto-endcomments + (pascal-set-auto-comments)) + ;; Check if we shall indent inside comment + (let ((setstar nil)) + (save-excursion + (forward-line -1) + (skip-chars-forward " \t") + (cond ((looking-at "\\*[ \t]+)") + ;; Delete region between `*' and `)' if there is only whitespaces. + (forward-char 1) + (delete-horizontal-space)) + ((and (looking-at "(\\*\\|\\*[^)]") + (not (save-excursion + (search-forward "*)" (pascal-get-end-of-line) t)))) + (setq setstar t)))) + ;; If last line was a star comment line then this one shall be too. + (if (null setstar) + (pascal-indent-line) + (insert "* ")))) + + +(defun electric-pascal-semi-or-dot () + "Insert `;' or `.' character and reindent the line." + (interactive) + (insert last-command-char) + (save-excursion + (beginning-of-line) + (pascal-indent-line)) + (if pascal-auto-newline + (electric-pascal-terminate-line))) + +(defun electric-pascal-colon () + "Insert `:' and do all indentions except line indent on this line." + (interactive) + (insert last-command-char) + ;; Do nothing if within string. + (if (pascal-within-string) + () + (save-excursion + (beginning-of-line) + (pascal-indent-line)) + (let ((pascal-tab-always-indent nil)) + (pascal-indent-command)))) + +(defun electric-pascal-equal () + "Insert `=', and do indention if within type declaration." + (interactive) + (insert last-command-char) + (if (eq (car (pascal-calculate-indent)) 'declaration) + (let ((pascal-tab-always-indent nil)) + (pascal-indent-command)))) + +(defun electric-pascal-hash () + "Insert `#', and indent to coulmn 0 if this is a CPP directive." + (interactive) + (insert last-command-char) + (if (save-excursion (beginning-of-line) (looking-at "^[ \t]*#")) + (save-excursion (beginning-of-line) + (delete-horizontal-space)))) + +(defun electric-pascal-tab () + "Function called when TAB is pressed in Pascal mode." + (interactive) + ;; Do nothing if within a string or in a CPP directive. + (if (or (pascal-within-string) + (and (not (bolp)) + (save-excursion (beginning-of-line) (eq (following-char) ?#)))) + (insert "\t") + ;; If pascal-tab-always-indent, indent the beginning of the line. + (if pascal-tab-always-indent + (save-excursion + (beginning-of-line) + (pascal-indent-line)) + (if (save-excursion + (skip-chars-backward " \t") + (bolp)) + (pascal-indent-line) + (insert "\t"))) + (pascal-indent-command))) + + + +;;; +;;; Interactive functions +;;; +(defun pascal-insert-block () + "Insert Pascal begin ... end; block in the code with right indentation." + (interactive) + (pascal-indent-line) + (insert "begin") + (electric-pascal-terminate-line) + (save-excursion + (electric-pascal-terminate-line) + (insert "end;") + (beginning-of-line) + (pascal-indent-line))) + +(defun pascal-star-comment () + "Insert Pascal star comment at point." + (interactive) + (pascal-indent-line) + (insert "(*") + (electric-pascal-terminate-line) + (save-excursion + (electric-pascal-terminate-line) + (delete-horizontal-space) + (insert ")")) + (insert " ")) + +(defun pascal-mark-defun () + "Mark the current pascal function (or procedure). +This puts the mark at the end, and point at the beginning." + (interactive) + (push-mark (point)) + (pascal-end-of-defun) + (push-mark (point)) + (pascal-beg-of-defun) + (if (fboundp 'zmacs-activate-region) + (zmacs-activate-region))) + +(defun pascal-comment-area (start end) + "Put the region into a Pascal comment. +The comments that are in this area are \"deformed\": +`*)' becomes `!(*' and `}' becomes `!{'. +These deformed comments are returned to normal if you use +\\[pascal-uncomment-area] to undo the commenting. + +The commented area starts with `pascal-exclude-str-start', and ends with +`pascal-include-str-end'. But if you change these variables, +\\[pascal-uncomment-area] won't recognize the comments." + (interactive "r") + (save-excursion + ;; Insert start and endcomments + (goto-char end) + (if (and (save-excursion (skip-chars-forward " \t") (eolp)) + (not (save-excursion (skip-chars-backward " \t") (bolp)))) + (forward-line 1) + (beginning-of-line)) + (insert pascal-exclude-str-end) + (setq end (point)) + (newline) + (goto-char start) + (beginning-of-line) + (insert pascal-exclude-str-start) + (newline) + ;; Replace end-comments within commented area + (goto-char end) + (save-excursion + (while (re-search-backward "\\*)" start t) + (replace-match "!(*" t t))) + (save-excursion + (while (re-search-backward "}" start t) + (replace-match "!{" t t))))) + +(defun pascal-uncomment-area () + "Uncomment a commented area; change deformed comments back to normal. +This command does nothing if the pointer is not in a commented +area. See also `pascal-comment-area'." + (interactive) + (save-excursion + (let ((start (point)) + (end (point))) + ;; Find the boundaries of the comment + (save-excursion + (setq start (progn (search-backward pascal-exclude-str-start nil t) + (point))) + (setq end (progn (search-forward pascal-exclude-str-end nil t) + (point)))) + ;; Check if we're really inside a comment + (if (or (equal start (point)) (<= end (point))) + (message "Not standing within commented area.") + (progn + ;; Remove endcomment + (goto-char end) + (beginning-of-line) + (let ((pos (point))) + (end-of-line) + (delete-region pos (1+ (point)))) + ;; Change comments back to normal + (save-excursion + (while (re-search-backward "!{" start t) + (replace-match "}" t t))) + (save-excursion + (while (re-search-backward "!(\\*" start t) + (replace-match "*)" t t))) + ;; Remove startcomment + (goto-char start) + (beginning-of-line) + (let ((pos (point))) + (end-of-line) + (delete-region pos (1+ (point))))))))) + +(defun pascal-beg-of-defun () + "Move backward to the beginning of the current function or procedure." + (interactive) + (catch 'found + (if (not (looking-at (concat "\\s \\|\\s)\\|" pascal-defun-re))) + (forward-sexp 1)) + (let ((nest 0) (max -1) (func 0) + (reg (concat pascal-beg-block-re "\\|" + pascal-end-block-re "\\|" + pascal-defun-re))) + (while (re-search-backward reg nil 'move) + (cond ((let ((state (save-excursion + (parse-partial-sexp (point-min) (point))))) + (or (nth 3 state) (nth 4 state))) ; Inside string or comment + ()) + ((match-end 1) ; begin|case|record|repeat + (if (and (looking-at "\\<record\\>") (>= max 0)) + (setq func (1- func))) + (setq nest (1+ nest) + max (max nest max))) + ((match-end 2) ; end|until + (if (and (= nest max) (>= max 0)) + (setq func (1+ func))) + (setq nest (1- nest))) + ((match-end 3) ; function|procedure + (if (= 0 func) + (throw 'found t) + (setq func (1- func))))))) + nil)) + +(defun pascal-end-of-defun () + "Move forward to the end of the current function or procedure." + (interactive) + (if (looking-at "\\s ") + (forward-sexp 1)) + (if (not (looking-at pascal-defun-re)) + (pascal-beg-of-defun)) + (forward-char 1) + (let ((nest 0) (func 1) + (reg (concat pascal-beg-block-re "\\|" + pascal-end-block-re "\\|" + pascal-defun-re))) + (while (and (/= func 0) + (re-search-forward reg nil 'move)) + (cond ((let ((state (save-excursion + (parse-partial-sexp (point-min) (point))))) + (or (nth 3 state) (nth 4 state))) ; Inside string or comment + ()) + ((match-end 1) + (setq nest (1+ nest)) + (if (save-excursion + (goto-char (match-beginning 0)) + (looking-at "\\<record\\>")) + (setq func (1+ func)))) + ((match-end 2) + (setq nest (1- nest)) + (if (= nest 0) + (setq func (1- func)))) + ((match-end 3) + (setq func (1+ func)))))) + (forward-line 1)) + +(defun pascal-end-of-statement () + "Move forward to end of current statement." + (interactive) + (let ((parse-sexp-ignore-comments t) + (nest 0) pos + (regexp (concat "\\(" pascal-beg-block-re "\\)\\|\\(" + pascal-end-block-re "\\)"))) + (if (not (looking-at "[ \t\n]")) (forward-sexp -1)) + (or (looking-at pascal-beg-block-re) + ;; Skip to end of statement + (setq pos (catch 'found + (while t + (forward-sexp 1) + (cond ((looking-at "[ \t]*;") + (skip-chars-forward "^;") + (forward-char 1) + (throw 'found (point))) + ((save-excursion + (forward-sexp -1) + (looking-at pascal-beg-block-re)) + (goto-char (match-beginning 0)) + (throw 'found nil)) + ((eobp) + (throw 'found (point)))))))) + (if (not pos) + ;; Skip a whole block + (catch 'found + (while t + (re-search-forward regexp nil 'move) + (setq nest (if (match-end 1) + (1+ nest) + (1- nest))) + (cond ((eobp) + (throw 'found (point))) + ((= 0 nest) + (throw 'found (pascal-end-of-statement)))))) + pos))) + +(defun pascal-downcase-keywords () + "Downcase all Pascal keywords in the buffer." + (interactive) + (pascal-change-keywords 'downcase-word)) + +(defun pascal-upcase-keywords () + "Upcase all Pascal keywords in the buffer." + (interactive) + (pascal-change-keywords 'upcase-word)) + +(defun pascal-capitalize-keywords () + "Capitalize all Pascal keywords in the buffer." + (interactive) + (pascal-change-keywords 'capitalize-word)) + +;; Change the keywords according to argument. +(defun pascal-change-keywords (change-word) + (save-excursion + (let ((keyword-re (concat "\\<\\(" + (mapconcat 'identity pascal-keywords "\\|") + "\\)\\>"))) + (goto-char (point-min)) + (while (re-search-forward keyword-re nil t) + (funcall change-word -1))))) + + + +;;; +;;; Other functions +;;; +(defun pascal-set-auto-comments () + "Insert `{ case }' or `{ NAME }' on this line if appropriate. +Insert `{ case }' if there is an `end' on the line which +ends a case block. Insert `{ NAME }' if there is an `end' +on the line which ends a function or procedure named NAME." + (save-excursion + (forward-line -1) + (skip-chars-forward " \t") + (if (and (looking-at "\\<end;") + (not (save-excursion + (end-of-line) + (search-backward "{" (pascal-get-beg-of-line) t)))) + (let ((type (car (pascal-calculate-indent)))) + (if (eq type 'declaration) + () + (if (eq type 'case) + ;; This is a case block + (progn + (end-of-line) + (delete-horizontal-space) + (insert " { case }")) + (let ((nest 1)) + ;; Check if this is the end of a function + (save-excursion + (while (not (or (looking-at pascal-defun-re) (bobp))) + (backward-sexp 1) + (cond ((looking-at pascal-beg-block-re) + (setq nest (1- nest))) + ((looking-at pascal-end-block-re) + (setq nest (1+ nest))))) + (if (bobp) + (setq nest 1))) + (if (zerop nest) + (progn + (end-of-line) + (delete-horizontal-space) + (insert " { ") + (let (b e) + (save-excursion + (setq b (progn (pascal-beg-of-defun) + (skip-chars-forward "^ \t") + (skip-chars-forward " \t") + (point)) + e (progn (skip-chars-forward "a-zA-Z0-9_") + (point)))) + (insert-buffer-substring (current-buffer) b e)) + (insert " }")))))))))) + + + +;;; +;;; Indentation +;;; +(defconst pascal-indent-alist + '((block . (+ ind pascal-indent-level)) + (case . (+ ind pascal-case-indent)) + (caseblock . ind) (cpp . 0) + (declaration . (+ ind pascal-indent-level)) + (paramlist . (pascal-indent-paramlist t)) + (comment . (pascal-indent-comment t)) + (defun . ind) (contexp . ind) + (unknown . 0) (string . 0))) + +(defun pascal-indent-command () + "Indent for special part of code." + (let* ((indent-str (pascal-calculate-indent)) + (type (car indent-str)) + (ind (car (cdr indent-str)))) + (cond ((and (eq type 'paramlist) + (or (memq 'all pascal-auto-lineup) + (memq 'paramlist pascal-auto-lineup))) + (pascal-indent-paramlist) + (pascal-indent-paramlist)) + ((and (eq type 'declaration) + (or (memq 'all pascal-auto-lineup) + (memq 'declaration pascal-auto-lineup))) + (pascal-indent-declaration)) + ((and (eq type 'case) (not (looking-at "^[ \t]*$")) + (or (memq 'all pascal-auto-lineup) + (memq 'case pascal-auto-lineup))) + (pascal-indent-case))) + (if (looking-at "[ \t]+$") + (skip-chars-forward " \t")))) + +(defun pascal-indent-line () + "Indent current line as a Pascal statement." + (let* ((indent-str (pascal-calculate-indent)) + (type (car indent-str)) + (ind (car (cdr indent-str)))) + (if (looking-at "^[0-9a-zA-Z]+[ \t]*:[^=]") + (search-forward ":" nil t)) + (delete-horizontal-space) + ;; Some things should not be indented + (if (or (and (eq type 'declaration) (looking-at pascal-declaration-re)) + (eq type 'cpp) + (looking-at pascal-defun-re)) + () + ;; Other things should have no extra indent + (if (looking-at pascal-noindent-re) + (indent-to ind) + ;; But most lines are treated this way: + (indent-to (eval (cdr (assoc type pascal-indent-alist)))) + )))) + +(defun pascal-calculate-indent () + "Calculate the indent of the current Pascal line. +Return a list of two elements: (INDENT-TYPE INDENT-LEVEL)." + (save-excursion + (let* ((parse-sexp-ignore-comments t) + (oldpos (point)) + (state (save-excursion (parse-partial-sexp (point-min) (point)))) + (nest 0) (par 0) (complete (looking-at "[ \t]*end\\>")) + (elsed (looking-at "[ \t]*else\\>")) + (type (catch 'nesting + ;; Check if inside a string, comment or parenthesis + (cond ((nth 3 state) (throw 'nesting 'string)) + ((nth 4 state) (throw 'nesting 'comment)) + ((> (car state) 0) + (goto-char (scan-lists (point) -1 (car state))) + (setq par (1+ (current-column)))) + ((save-excursion (beginning-of-line) + (eq (following-char) ?#)) + (throw 'nesting 'cpp))) + ;; Loop until correct indent is found + (while t + (backward-sexp 1) + (cond (;--Escape from case statements + (and (looking-at "[A-Za-z0-9]+[ \t]*:[^=]") + (not complete) + (save-excursion (skip-chars-backward " \t") + (bolp)) + (= (save-excursion + (end-of-line) (backward-sexp) (point)) + (point)) + (> (save-excursion (goto-char oldpos) + (beginning-of-line) + (point)) + (point))) + (throw 'nesting 'caseblock)) + (;--Nest block outwards + (looking-at pascal-beg-block-re) + (if (= nest 0) + (cond ((looking-at "case\\>") + (throw 'nesting 'case)) + ((looking-at "record\\>") + (throw 'nesting 'declaration)) + (t (throw 'nesting 'block))) + (setq nest (1- nest)))) + (;--Nest block inwards + (looking-at pascal-end-block-re) + (if (and (looking-at "end\\s ") + elsed (not complete)) + (throw 'nesting 'block)) + (setq complete t + nest (1+ nest))) + (;--Defun (or parameter list) + (looking-at pascal-defun-re) + (if (= 0 par) + (throw 'nesting 'defun) + (setq par 0) + (let ((n 0)) + (while (re-search-forward + "\\(\\<record\\>\\)\\|\\<end\\>" + oldpos t) + (if (match-end 1) + (setq n (1+ n)) (setq n (1- n)))) + (if (> n 0) + (throw 'nesting 'declaration) + (throw 'nesting 'paramlist))))) + (;--Declaration part + (looking-at pascal-declaration-re) + (if (save-excursion + (goto-char oldpos) + (forward-line -1) + (looking-at "^[ \t]*$")) + (throw 'nesting 'unknown) + (throw 'nesting 'declaration))) + (;--If, else or while statement + (and (not complete) + (looking-at pascal-sub-block-re)) + (throw 'nesting 'block)) + (;--Found complete statement + (save-excursion (forward-sexp 1) + (= (following-char) ?\;)) + (setq complete t)) + (;--No known statements + (bobp) + (throw 'nesting 'unknown)) + ))))) + + ;; Return type of block and indent level. + (if (> par 0) ; Unclosed Parenthesis + (list 'contexp par) + (list type (pascal-indent-level)))))) + +(defun pascal-indent-level () + "Return the indent-level the current statement has. +Do not count labels, case-statements or records." + (save-excursion + (beginning-of-line) + (if (looking-at "[ \t]*[0-9a-zA-Z]+[ \t]*:[^=]") + (search-forward ":" nil t) + (if (looking-at ".*=[ \t]*record\\>") + (search-forward "=" nil t))) + (skip-chars-forward " \t") + (current-column))) + +(defun pascal-indent-comment (&optional arg) + "Indent current line as comment. +If optional arg is non-nil, just return the +column number the line should be indented to." + (let* ((stcol (save-excursion + (re-search-backward "(\\*\\|{" nil t) + (1+ (current-column))))) + (if arg stcol + (delete-horizontal-space) + (indent-to stcol)))) + +(defun pascal-indent-case () + "Indent within case statements." + (let ((savepos (point-marker)) + (end (prog2 + (end-of-line) + (point-marker) + (re-search-backward "\\<case\\>" nil t))) + (beg (point)) oldpos + (ind 0)) + ;; Get right indent + (while (< (point) (marker-position end)) + (if (re-search-forward + "^[ \t]*[^ \t,:]+[ \t]*\\(,[ \t]*[^ \t,:]+[ \t]*\\)*:" + (marker-position end) 'move) + (forward-char -1)) + (if (< (point) (marker-position end)) + (progn + (delete-horizontal-space) + (if (> (current-column) ind) + (setq ind (current-column))) + (pascal-end-of-statement)))) + (goto-char beg) + (setq oldpos (marker-position end)) + ;; Indent all case statements + (while (< (point) (marker-position end)) + (if (re-search-forward + "^[ \t]*[^][ \t,\\.:]+[ \t]*\\(,[ \t]*[^ \t,:]+[ \t]*\\)*:" + (marker-position end) 'move) + (forward-char -1)) + (indent-to (1+ ind)) + (if (/= (following-char) ?:) + () + (forward-char 1) + (delete-horizontal-space) + (insert " ")) + (setq oldpos (point)) + (pascal-end-of-statement)) + (goto-char savepos))) + +(defun pascal-indent-paramlist (&optional arg) + "Indent current line in parameterlist. +If optional arg is non-nil, just return the +indent of the current line in parameterlist." + (save-excursion + (let* ((oldpos (point)) + (stpos (progn (goto-char (scan-lists (point) -1 1)) (point))) + (stcol (1+ (current-column))) + (edpos (progn (pascal-declaration-end) + (search-backward ")" (pascal-get-beg-of-line) t) + (point))) + (usevar (re-search-backward "\\<var\\>" stpos t))) + (if arg (progn + ;; If arg, just return indent + (goto-char oldpos) + (beginning-of-line) + (if (or (not usevar) (looking-at "[ \t]*var\\>")) + stcol (+ 4 stcol))) + (goto-char stpos) + (forward-char 1) + (delete-horizontal-space) + (if (and usevar (not (looking-at "var\\>"))) + (indent-to (+ 4 stcol))) + (pascal-indent-declaration nil stpos edpos))))) + +(defun pascal-indent-declaration (&optional arg start end) + "Indent current lines as declaration, lining up the `:'s or `='s." + (let ((pos (point-marker))) + (if (and (not (or arg start)) (not (pascal-declaration-beg))) + () + (let ((lineup (if (or (looking-at "\\<var\\>\\|\\<record\\>") arg start) + ":" "=")) + (stpos (if start start + (forward-word 2) (backward-word 1) (point))) + (edpos (set-marker (make-marker) + (if end end + (max (progn (pascal-declaration-end) + (point)) + pos)))) + ind) + + (goto-char stpos) + ;; Indent lines in record block + (if arg + (while (<= (point) (marker-position edpos)) + (beginning-of-line) + (delete-horizontal-space) + (if (looking-at "end\\>") + (indent-to arg) + (indent-to (+ arg pascal-indent-level))) + (forward-line 1))) + + ;; Do lineup + (setq ind (pascal-get-lineup-indent stpos edpos lineup)) + (goto-char stpos) + (while (<= (point) (marker-position edpos)) + (if (search-forward lineup (pascal-get-end-of-line) 'move) + (forward-char -1)) + (delete-horizontal-space) + (indent-to ind) + (if (not (looking-at lineup)) + (forward-line 1) ; No more indent if there is no : or = + (forward-char 1) + (delete-horizontal-space) + (insert " ") + ;; Indent record block + (if (looking-at "record\\>") + (pascal-indent-declaration (current-column))) + (forward-line 1))))) + + ;; If arg - move point + (if arg (forward-line -1) + (goto-char (marker-position pos))))) + +; "Return the indent level that will line up several lines within the region +;from b to e nicely. The lineup string is str." +(defun pascal-get-lineup-indent (b e str) + (save-excursion + (let ((ind 0) + (reg (concat str "\\|\\(\\<record\\>\\)")) + nest) + (goto-char b) + ;; Get rightmost position + (while (< (point) e) + (setq nest 1) + (if (re-search-forward reg (min e (pascal-get-end-of-line 2)) 'move) + (progn + ;; Skip record blocks + (if (match-beginning 1) + (pascal-declaration-end) + (progn + (goto-char (match-beginning 0)) + (skip-chars-backward " \t") + (if (> (current-column) ind) + (setq ind (current-column))) + (goto-char (match-end 0))))))) + ;; In case no lineup was found + (if (> ind 0) + (1+ ind) + ;; No lineup-string found + (goto-char b) + (end-of-line) + (skip-chars-backward " \t") + (1+ (current-column)))))) + + + +;;; +;;; Completion +;;; +(defvar pascal-str nil) +(defvar pascal-all nil) +(defvar pascal-pred nil) +(defvar pascal-buffer-to-use nil) +(defvar pascal-flag nil) + +(defun pascal-string-diff (str1 str2) + "Return index of first letter where STR1 and STR2 differs." + (catch 'done + (let ((diff 0)) + (while t + (if (or (> (1+ diff) (length str1)) + (> (1+ diff) (length str2))) + (throw 'done diff)) + (or (equal (aref str1 diff) (aref str2 diff)) + (throw 'done diff)) + (setq diff (1+ diff)))))) + +;; Calculate all possible completions for functions if argument is `function', +;; completions for procedures if argument is `procedure' or both functions and +;; procedures otherwise. + +(defun pascal-func-completion (type) + ;; Build regular expression for function/procedure names + (if (string= pascal-str "") + (setq pascal-str "[a-zA-Z_]")) + (let ((pascal-str (concat (cond + ((eq type 'procedure) "\\<\\(procedure\\)\\s +") + ((eq type 'function) "\\<\\(function\\)\\s +") + (t "\\<\\(function\\|procedure\\)\\s +")) + "\\<\\(" pascal-str "[a-zA-Z0-9_.]*\\)\\>")) + match) + + (if (not (looking-at "\\<\\(function\\|procedure\\)\\>")) + (re-search-backward "\\<\\(function\\|procedure\\)\\>" nil t)) + (forward-char 1) + + ;; Search through all reachable functions + (while (pascal-beg-of-defun) + (if (re-search-forward pascal-str (pascal-get-end-of-line) t) + (progn (setq match (buffer-substring (match-beginning 2) + (match-end 2))) + (if (or (null pascal-pred) + (funcall pascal-pred match)) + (setq pascal-all (cons match pascal-all))))) + (goto-char (match-beginning 0))))) + +(defun pascal-get-completion-decl () + ;; Macro for searching through current declaration (var, type or const) + ;; for matches of `str' and adding the occurence tp `all' + (let ((end (save-excursion (pascal-declaration-end) + (point))) + match) + ;; Traverse lines + (while (< (point) end) + (if (re-search-forward "[:=]" (pascal-get-end-of-line) t) + ;; Traverse current line + (while (and (re-search-backward + (concat "\\((\\|\\<\\(var\\|type\\|const\\)\\>\\)\\|" + pascal-symbol-re) + (pascal-get-beg-of-line) t) + (not (match-end 1))) + (setq match (buffer-substring (match-beginning 0) (match-end 0))) + (if (string-match (concat "\\<" pascal-str) match) + (if (or (null pascal-pred) + (funcall pascal-pred match)) + (setq pascal-all (cons match pascal-all)))))) + (if (re-search-forward "\\<record\\>" (pascal-get-end-of-line) t) + (pascal-declaration-end) + (forward-line 1))))) + +(defun pascal-type-completion () + "Calculate all possible completions for types." + (let ((start (point)) + goon) + ;; Search for all reachable type declarations + (while (or (pascal-beg-of-defun) + (setq goon (not goon))) + (save-excursion + (if (and (< start (prog1 (save-excursion (pascal-end-of-defun) + (point)) + (forward-char 1))) + (re-search-forward + "\\<type\\>\\|\\<\\(begin\\|function\\|procedure\\)\\>" + start t) + (not (match-end 1))) + ;; Check current type declaration + (pascal-get-completion-decl)))))) + +(defun pascal-var-completion () + "Calculate all possible completions for variables (or constants)." + (let ((start (point)) + goon twice) + ;; Search for all reachable var declarations + (while (or (pascal-beg-of-defun) + (setq goon (not goon))) + (save-excursion + (if (> start (prog1 (save-excursion (pascal-end-of-defun) + (point)))) + () ; Declarations not reacable + (if (search-forward "(" (pascal-get-end-of-line) t) + ;; Check parameterlist + (pascal-get-completion-decl)) + (setq twice 2) + (while (>= (setq twice (1- twice)) 0) + (cond ((and (re-search-forward + (concat "\\<\\(var\\|const\\)\\>\\|" + "\\<\\(begin\\|function\\|procedure\\)\\>") + start t) + (not (match-end 2))) + ;; Check var/const declarations + (pascal-get-completion-decl)) + ((match-end 2) + (setq twice 0))))))))) + + +(defun pascal-keyword-completion (keyword-list) + "Give list of all possible completions of keywords in KEYWORD-LIST." + (mapcar '(lambda (s) + (if (string-match (concat "\\<" pascal-str) s) + (if (or (null pascal-pred) + (funcall pascal-pred s)) + (setq pascal-all (cons s pascal-all))))) + keyword-list)) + +;; Function passed to completing-read, try-completion or +;; all-completions to get completion on STR. If predicate is non-nil, +;; it must be a function to be called for every match to check if this +;; should really be a match. If flag is t, the function returns a list +;; of all possible completions. If it is nil it returns a string, the +;; longest possible completion, or t if STR is an exact match. If flag +;; is 'lambda, the function returns t if STR is an exact match, nil +;; otherwise. + +(defun pascal-completion (pascal-str pascal-pred pascal-flag) + (save-excursion + (let ((pascal-all nil)) + ;; Set buffer to use for searching labels. This should be set + ;; within functins which use pascal-completions + (set-buffer pascal-buffer-to-use) + + ;; Determine what should be completed + (let ((state (car (pascal-calculate-indent)))) + (cond (;--Within a declaration or parameterlist + (or (eq state 'declaration) (eq state 'paramlist) + (and (eq state 'defun) + (save-excursion + (re-search-backward ")[ \t]*:" + (pascal-get-beg-of-line) t)))) + (if (or (eq state 'paramlist) (eq state 'defun)) + (pascal-beg-of-defun)) + (pascal-type-completion) + (pascal-keyword-completion pascal-type-keywords)) + (;--Starting a new statement + (and (not (eq state 'contexp)) + (save-excursion + (skip-chars-backward "a-zA-Z0-9_.") + (backward-sexp 1) + (or (looking-at pascal-nosemi-re) + (progn + (forward-sexp 1) + (looking-at "\\s *\\(;\\|:[^=]\\)"))))) + (save-excursion (pascal-var-completion)) + (pascal-func-completion 'procedure) + (pascal-keyword-completion pascal-start-keywords)) + (t;--Anywhere else + (save-excursion (pascal-var-completion)) + (pascal-func-completion 'function) + (pascal-keyword-completion pascal-separator-keywords)))) + + ;; Now we have built a list of all matches. Give response to caller + (pascal-completion-response)))) + +(defun pascal-completion-response () + (cond ((or (equal pascal-flag 'lambda) (null pascal-flag)) + ;; This was not called by all-completions + (if (null pascal-all) + ;; Return nil if there was no matching label + nil + ;; Get longest string common in the labels + (let* ((elm (cdr pascal-all)) + (match (car pascal-all)) + (min (length match)) + exact tmp) + (if (string= match pascal-str) + ;; Return t if first match was an exact match + (setq match t) + (while (not (null elm)) + ;; Find longest common string + (if (< (setq tmp (pascal-string-diff match (car elm))) min) + (progn + (setq min tmp) + (setq match (substring match 0 min)))) + ;; Terminate with match=t if this is an exact match + (if (string= (car elm) pascal-str) + (progn + (setq match t) + (setq elm nil)) + (setq elm (cdr elm))))) + ;; If this is a test just for exact match, return nil ot t + (if (and (equal pascal-flag 'lambda) (not (equal match 't))) + nil + match)))) + ;; If flag is t, this was called by all-completions. Return + ;; list of all possible completions + (pascal-flag + pascal-all))) + +(defvar pascal-last-word-numb 0) +(defvar pascal-last-word-shown nil) +(defvar pascal-last-completions nil) + +(defun pascal-complete-word () + "Complete word at current point. +\(See also `pascal-toggle-completions', `pascal-type-keywords', +`pascal-start-keywords' and `pascal-separator-keywords'.)" + (interactive) + (let* ((b (save-excursion (skip-chars-backward "a-zA-Z0-9_") (point))) + (e (save-excursion (skip-chars-forward "a-zA-Z0-9_") (point))) + (pascal-str (buffer-substring b e)) + ;; The following variable is used in pascal-completion + (pascal-buffer-to-use (current-buffer)) + (allcomp (if (and pascal-toggle-completions + (string= pascal-last-word-shown pascal-str)) + pascal-last-completions + (all-completions pascal-str 'pascal-completion))) + (match (if pascal-toggle-completions + "" (try-completion + pascal-str (mapcar '(lambda (elm) + (cons elm 0)) allcomp))))) + ;; Delete old string + (delete-region b e) + + ;; Toggle-completions inserts whole labels + (if pascal-toggle-completions + (progn + ;; Update entry number in list + (setq pascal-last-completions allcomp + pascal-last-word-numb + (if (>= pascal-last-word-numb (1- (length allcomp))) + 0 + (1+ pascal-last-word-numb))) + (setq pascal-last-word-shown (elt allcomp pascal-last-word-numb)) + ;; Display next match or same string if no match was found + (if (not (null allcomp)) + (insert "" pascal-last-word-shown) + (insert "" pascal-str) + (message "(No match)"))) + ;; The other form of completion does not necessarly do that. + + ;; Insert match if found, or the original string if no match + (if (or (null match) (equal match 't)) + (progn (insert "" pascal-str) + (message "(No match)")) + (insert "" match)) + ;; Give message about current status of completion + (cond ((equal match 't) + (if (not (null (cdr allcomp))) + (message "(Complete but not unique)") + (message "(Sole completion)"))) + ;; Display buffer if the current completion didn't help + ;; on completing the label. + ((and (not (null (cdr allcomp))) (= (length pascal-str) + (length match))) + (with-output-to-temp-buffer "*Completions*" + (display-completion-list allcomp)) + ;; Wait for a keypress. Then delete *Completion* window + (momentary-string-display "" (point)) + (delete-window (get-buffer-window (get-buffer "*Completions*"))) + ))))) + +(defun pascal-show-completions () + "Show all possible completions at current point." + (interactive) + (let* ((b (save-excursion (skip-chars-backward "a-zA-Z0-9_") (point))) + (e (save-excursion (skip-chars-forward "a-zA-Z0-9_") (point))) + (pascal-str (buffer-substring b e)) + ;; The following variable is used in pascal-completion + (pascal-buffer-to-use (current-buffer)) + (allcomp (if (and pascal-toggle-completions + (string= pascal-last-word-shown pascal-str)) + pascal-last-completions + (all-completions pascal-str 'pascal-completion)))) + ;; Show possible completions in a temporary buffer. + (with-output-to-temp-buffer "*Completions*" + (display-completion-list allcomp)) + ;; Wait for a keypress. Then delete *Completion* window + (momentary-string-display "" (point)) + (delete-window (get-buffer-window (get-buffer "*Completions*"))))) + + +(defun pascal-get-default-symbol () + "Return symbol around current point as a string." + (save-excursion + (buffer-substring (progn + (skip-chars-backward " \t") + (skip-chars-backward "a-zA-Z0-9_") + (point)) + (progn + (skip-chars-forward "a-zA-Z0-9_") + (point))))) + +(defun pascal-build-defun-re (str &optional arg) + "Return function/procedure starting with STR as regular expression. +With optional second arg non-nil, STR is the complete name of the instruction." + (if arg + (concat "^\\(function\\|procedure\\)[ \t]+\\(" str "\\)\\>") + (concat "^\\(function\\|procedure\\)[ \t]+\\(" str "[a-zA-Z0-9_]*\\)\\>"))) + +;; Function passed to completing-read, try-completion or +;; all-completions to get completion on any function name. If +;; predicate is non-nil, it must be a function to be called for every +;; match to check if this should really be a match. If flag is t, the +;; function returns a list of all possible completions. If it is nil +;; it returns a string, the longest possible completion, or t if STR +;; is an exact match. If flag is 'lambda, the function returns t if +;; STR is an exact match, nil otherwise. + +(defun pascal-comp-defun (pascal-str pascal-pred pascal-flag) + (save-excursion + (let ((pascal-all nil) + match) + + ;; Set buffer to use for searching labels. This should be set + ;; within functins which use pascal-completions + (set-buffer pascal-buffer-to-use) + + (let ((pascal-str pascal-str)) + ;; Build regular expression for functions + (if (string= pascal-str "") + (setq pascal-str (pascal-build-defun-re "[a-zA-Z_]")) + (setq pascal-str (pascal-build-defun-re pascal-str))) + (goto-char (point-min)) + + ;; Build a list of all possible completions + (while (re-search-forward pascal-str nil t) + (setq match (buffer-substring (match-beginning 2) (match-end 2))) + (if (or (null pascal-pred) + (funcall pascal-pred match)) + (setq pascal-all (cons match pascal-all))))) + + ;; Now we have built a list of all matches. Give response to caller + (pascal-completion-response)))) + +(defun pascal-goto-defun () + "Move to specified Pascal function/procedure. +The default is a name found in the buffer around point." + (interactive) + (let* ((default (pascal-get-default-symbol)) + ;; The following variable is used in pascal-comp-function + (pascal-buffer-to-use (current-buffer)) + (default (if (pascal-comp-defun default nil 'lambda) + default "")) + (label (if (not (string= default "")) + ;; Do completion with default + (completing-read (concat "Label: (default " default ") ") + 'pascal-comp-defun nil t "") + ;; There is no default value. Complete without it + (completing-read "Label: " + 'pascal-comp-defun nil t "")))) + ;; If there was no response on prompt, use default value + (if (string= label "") + (setq label default)) + ;; Goto right place in buffer if label is not an empty string + (or (string= label "") + (progn + (goto-char (point-min)) + (re-search-forward (pascal-build-defun-re label t)) + (beginning-of-line))))) + + + +;;; +;;; Pascal-outline-mode +;;; +(defvar pascal-outline-map nil "Keymap used in Pascal Outline mode.") + +(if pascal-outline-map + nil + (if (boundp 'set-keymap-name) + (set-keymap-name pascal-outline-map 'pascal-outline-map)) + (if (not (boundp 'set-keymap-parent)) + (setq pascal-outline-map (copy-keymap pascal-mode-map)) + (setq pascal-outline-map (make-sparse-keymap)) + (set-keymap-parent pascal-outline-map pascal-mode-map)) + (define-key pascal-outline-map "\M-\C-a" 'pascal-outline-prev-defun) + (define-key pascal-outline-map "\M-\C-e" 'pascal-outline-next-defun) + (define-key pascal-outline-map "\C-c\C-d" 'pascal-outline-goto-defun) + (define-key pascal-outline-map "\C-c\C-s" 'pascal-show-all) + (define-key pascal-outline-map "\C-c\C-h" 'pascal-hide-other-defuns)) + +(defvar pascal-outline-mode nil "Non-nil while using Pascal Outline mode.") +(make-variable-buffer-local 'pascal-outline-mode) +(set-default 'pascal-outline-mode nil) +(if (not (assoc 'pascal-outline-mode minor-mode-alist)) + (setq minor-mode-alist (append minor-mode-alist + (list '(pascal-outline-mode " Outl"))))) + +(defun pascal-outline (&optional arg) + "Outline-line minor mode for Pascal mode. +When in Pascal Outline mode, portions +of the text being edited may be made invisible. \\<pascal-outline-map> + +Pascal Outline mode provides some additional commands. + +\\[pascal-outline-prev-defun]\ +\t- Move to previous function/procedure, hiding everything else. +\\[pascal-outline-next-defun]\ +\t- Move to next function/procedure, hiding everything else. +\\[pascal-outline-goto-defun]\ +\t- Goto function/procedure prompted for in minibuffer, +\t hide all other functions. +\\[pascal-show-all]\t- Show the whole buffer. +\\[pascal-hide-other-defuns]\ +\t- Hide everything but the current function (function under the cursor). +\\[pascal-outline]\t- Leave pascal-outline-mode." + (interactive "P") + (setq pascal-outline-mode + (if (null arg) (not pascal-outline-mode) t)) + (if (boundp 'redraw-mode-line) + (redraw-mode-line)) + (if pascal-outline-mode + (progn + (setq selective-display t) + (use-local-map pascal-outline-map)) + (progn + (setq selective-display nil) + (pascal-show-all) + (use-local-map pascal-mode-map)))) + +(defun pascal-outline-change (b e pascal-flag) + (let ((modp (buffer-modified-p))) + (unwind-protect + (subst-char-in-region b e (if (= pascal-flag ?\n) + ?\^M ?\n) pascal-flag) + (set-buffer-modified-p modp)))) + +(defun pascal-show-all () + "Show all of the text in the buffer." + (interactive) + (pascal-outline-change (point-min) (point-max) ?\n)) + +(defun pascal-hide-other-defuns () + "Show only the current defun." + (interactive) + (save-excursion + (let ((beg (progn (if (not (looking-at "\\(function\\|procedure\\)\\>")) + (pascal-beg-of-defun)) + (point))) + (end (progn (pascal-end-of-defun) + (backward-sexp 1) + (search-forward "\n\\|\^M" nil t) + (point))) + (opoint (point-min))) + (goto-char (point-min)) + + ;; Hide all functions before current function + (while (re-search-forward "^\\(function\\|procedure\\)\\>" beg 'move) + (pascal-outline-change opoint (1- (match-beginning 0)) ?\^M) + (setq opoint (point)) + ;; Functions may be nested + (if (> (progn (pascal-end-of-defun) (point)) beg) + (goto-char opoint))) + (if (> beg opoint) + (pascal-outline-change opoint (1- beg) ?\^M)) + + ;; Show current function + (pascal-outline-change beg end ?\n) + ;; Hide nested functions + (forward-char 1) + (while (re-search-forward "^\\(function\\|procedure\\)\\>" end 'move) + (setq opoint (point)) + (pascal-end-of-defun) + (pascal-outline-change opoint (point) ?\^M)) + + (goto-char end) + (setq opoint end) + + ;; Hide all function after current function + (while (re-search-forward "^\\(function\\|procedure\\)\\>" nil 'move) + (pascal-outline-change opoint (1- (match-beginning 0)) ?\^M) + (setq opoint (point)) + (pascal-end-of-defun)) + (pascal-outline-change opoint (point-max) ?\^M) + + ;; Hide main program + (if (< (progn (forward-line -1) (point)) end) + (progn + (goto-char beg) + (pascal-end-of-defun) + (backward-sexp 1) + (pascal-outline-change (point) (point-max) ?\^M)))))) + +(defun pascal-outline-next-defun () + "Move to next function/procedure, hiding all others." + (interactive) + (pascal-end-of-defun) + (pascal-hide-other-defuns)) + +(defun pascal-outline-prev-defun () + "Move to previous function/procedure, hiding all others." + (interactive) + (pascal-beg-of-defun) + (pascal-hide-other-defuns)) + +(defun pascal-outline-goto-defun () + "Move to specified function/procedure, hiding all others." + (interactive) + (pascal-goto-defun) + (pascal-hide-other-defuns)) + +;;; pascal.el ends here