Mercurial > emacs
changeset 26956:67b3331ff24c
Major mode for editing files of the Interactive Data Language
author | Carsten Dominik <dominik@science.uva.nl> |
---|---|
date | Mon, 20 Dec 1999 11:10:02 +0000 |
parents | 0e1d037cdcdd |
children | 3abce8d1097b |
files | lisp/progmodes/idlwave.el |
diffstat | 1 files changed, 5751 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/progmodes/idlwave.el Mon Dec 20 11:10:02 1999 +0000 @@ -0,0 +1,5751 @@ +;;; idlwave.el --- IDL and WAVE CL editing mode for GNU Emacs +;; Copyright (c) 1994-1997 Chris Chase +;; Copyright (c) 1999 Carsten Dominik +;; Copyright (c) 1999 Free Software Foundation + +;; Author: Chris Chase <chase@att.com> +;; Maintainer: Carsten Dominik <dominik@strw.leidenuniv.nl> +;; Version: 3.11 +;; Date: $Date: 1999/12/16 10:42:46 $ +;; Keywords: languages + +;; This file is part of the GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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: + +;; In distant past, based on pascal.el. Though bears little +;; resemblance to that now. +;; +;; Incorporates many ideas, such as abbrevs, action routines, and +;; continuation line indenting, from wave.el. +;; wave.el original written by Lubos Pochman, Precision Visuals, Boulder. +;; +;; See the mode description ("C-h m" in idlwave-mode or "C-h f idlwave-mode") +;; for features, key bindings, and info. +;; Also, Info format documentation is available with `M-x idlwave-info' +;; +;; +;; INSTALLATION +;; ============ +;; +;; Follow the instructions in the INSTALL file of the distribution. +;; In short, put this file on your load path and add the following +;; lines to your .emacs file: +;; +;; (autoload 'idlwave-mode "idlwave" "IDLWAVE Mode" t) +;; (autoload 'idlwave-shell "idlwave-shell" "IDLWAVE Shell" t) +;; (setq auto-mode-alist (cons '("\\.pro\\'" . idlwave-mode) auto-mode-alist)) +;; +;; +;; SOURCE +;; ====== +;; +;; The newest version of this file is available from the maintainers +;; Webpage. +;; +;; http://www.strw.leidenuniv.el/~dominik/Tools/idlwave +;; +;; DOCUMENTATION +;; ============= +;; +;; IDLWAVE is documented online in info format. +;; A printable version of the documentation is available from the +;; maintainers webpage (see under SOURCE) +;; +;; +;; ACKNOWLEDGMENTS +;; =============== +;; +;; Thanks to the following people for their contributions and comments: +;; +;; Ulrik Dickow <dickow@nbi.dk> +;; Eric E. Dors <edors@lanl.gov> +;; Stein Vidar H. Haugan <s.v.h.haugan@astro.uio.no> +;; David Huenemoerder <dph@space.mit.edu> +;; Kevin Ivory <Kevin.Ivory@linmpi.mpg.de> +;; Xuyong Liu <liu@stsci.edu> +;; Simon Marshall <Simon.Marshall@esrin.esa.it> +;; Laurent Mugnier <mugnier@onera.fr> +;; Lubos Pochman <lubos@rsinc.com> +;; Patrick M. Ryan <pat@jaameri.gsfc.nasa.gov> +;; Marty Ryba <ryba@ll.mit.edu> +;; Phil Williams <williams@irc.chmcc.org> +;; J.D. Smith <jdsmith@astrosun.tn.cornell.edu> +;; Phil Sterne <sterne@dublin.llnl.gov> +;; +;; CUSTOMIZATION: +;; ============= +;; +;; IDLWAVE has customize support - so if you want to learn about the +;; variables which control the behavior of the mode, use +;; `M-x idlwave-customize'. +;; +;; You can set your own preferred values with Customize, or with Lisp +;; code in .emacs. For an example of what to put into .emacs, check +;; the TexInfo documentation. +;; +;; KNOWN PROBLEMS: +;; ============== +;; +;; Moving the point backwards in conjunction with abbrev expansion +;; does not work as I would like it, but this is a problem with +;; emacs abbrev expansion done by the self-insert-command. It ends +;; up inserting the character that expanded the abbrev after moving +;; point backward, e.g., "\cl" expanded with a space becomes +;; "LONG( )" with point before the close paren. This is solved by +;; using a temporary function in `post-command-hook' - not pretty, +;; but it works.< +;; +;; Tabs and spaces are treated equally as whitespace when filling a +;; comment paragraph. To accomplish this, tabs are permanently +;; replaced by spaces in the text surrounding the paragraph, which +;; may be an undesirable side-effect. Replacing tabs with spaces is +;; limited to comments only and occurs only when a comment +;; paragraph is filled via `idlwave-fill-paragraph'. +;; +;; "&" is ignored when parsing statements. +;; Avoid muti-statement lines (using "&") on block begin and end +;; lines. Multi-statement lines can mess up the formatting, for +;; example, multiple end statements on a line: endif & endif. +;; Using "&" outside of block begin/end lines should be okay. +;; +;; It is possible that the parser which decides what to complete has +;; problems with pointer dereferencing statements. I don't use +;; pointers often enough to find out - please report any problems. +;; +;; Completion of keywords for SETPROPERTY and GETPROPERTY assumes that +;; all INIT keywords are allowed in these methods as well. In some +;; cases, there are exceptions to this rule and IDLWAVE will offer +;; a few illegal keyword parameters. +;; +;; Completion and Routine Info do not know about inheritance. Thus, +;; Keywords inherited from superclasses are not displayed and cannot +;; completed. +;; +;; When forcing completion of method keywords, the initial +;; query for a method has multiple entries for some methods. Would +;; be too difficult to fix this hardly used problem. +;; + +;;; Code: + +(eval-when-compile (require 'cl)) + +(eval-and-compile + ;; Kludge to allow `defcustom' for Emacs 19. + (condition-case () (require 'custom) (error nil)) + (if (and (featurep 'custom) (fboundp 'custom-declare-variable)) + nil ;; We've got what we needed + ;; We have the old or no custom-library, hack around it! + (defmacro defgroup (&rest args) nil) + (defmacro defcustom (var value doc &rest args) + (` (defvar (, var) (, value) (, doc)))))) + +(defgroup idlwave nil + "Major mode for editing IDL/WAVE CL .pro files" + :tag "IDLWAVE" + :link '(url-link :tag "Home Page" + "http://strw.leidenuniv.nl/~dominik/Tools/idlwave") + :link '(emacs-commentary-link :tag "Commentary in idlwave-shell.el" + "idlwave-shell.el") + :link '(emacs-commentary-link :tag "Commentary in idlwave.el" "idlwave.el") + :link '(custom-manual "(idlwave)Top") + :prefix "idlwave" + :group 'languages) + +;;; Variables for indentation behavior --------------------------------------- + +(defgroup idlwave-code-formatting nil + "Indentation and formatting options for IDLWAVE mode." + :group 'idlwave) + +(defcustom idlwave-main-block-indent 0 + "*Extra indentation for the main block of code. +That is the block between the FUNCTION/PRO statement and the END +statement for that program unit." + :group 'idlwave-code-formatting + :type 'integer) + +(defcustom idlwave-block-indent 4 + "*Extra indentation applied to block lines. +If you change this, you probably also want to change `idlwave-end-offset'." + :group 'idlwave-code-formatting + :type 'integer) + +(defcustom idlwave-end-offset -4 + "*Extra indentation applied to block END lines. +A value equal to negative `idlwave-block-indent' will make END lines +line up with the block BEGIN lines." + :group 'idlwave-code-formatting + :type 'integer) + +(defcustom idlwave-continuation-indent 2 + "*Extra indentation applied to continuation lines. +This extra offset applies to the first of a set of continuation lines. +The following lines receive the same indentation as the first. +Also, the value of this variable applies to continuation lines inside +parenthesis. When the current line contains an open unmatched ([{, +the next line is indented to that parenthesis plus the value of this variable." + :group 'idlwave-code-formatting + :type 'integer) + +(defcustom idlwave-hanging-indent t + "*If set non-nil then comment paragraphs are indented under the +hanging indent given by `idlwave-hang-indent-regexp' match in the first line +of the paragraph." + :group 'idlwave-code-formatting + :type 'boolean) + +(defcustom idlwave-hang-indent-regexp "- " + "*Regular expression matching the position of the hanging indent +in the first line of a comment paragraph. The size of the indent +extends to the end of the match for the regular expression." + :group 'idlwave-code-formatting + :type 'regexp) + +(defcustom idlwave-use-last-hang-indent nil + "*If non-nil then use last match on line for `idlwave-indent-regexp'." + :group 'idlwave-code-formatting + :type 'boolean) + +(defcustom idlwave-fill-comment-line-only t + "*If non-nil then auto fill will only operate on comment lines." + :group 'idlwave-code-formatting + :type 'boolean) + +(defcustom idlwave-auto-fill-split-string t + "*If non-nil then auto fill will split strings with the IDL `+' operator. +When the line end falls within a string, string concatenation with the +'+' operator will be used to distribute a long string over lines. +If nil and a string is split then a terminal beep and warning are issued. + +This variable is ignored when `idlwave-fill-comment-line-only' is +non-nil, since in this case code is not auto-filled." + :group 'idlwave-code-formatting + :type 'boolean) + +(defcustom idlwave-split-line-string t + "*If non-nil then `idlwave-split-line' will split strings with `+'. +When the splitting point of a line falls inside a string, split the string +using the `+' string concatenation operator. If nil and a string is +split then a terminal beep and warning are issued." + :group 'idlwave-code-formatting + :type 'boolean) + +(defcustom idlwave-no-change-comment ";;;" + "*The indentation of a comment that starts with this regular +expression will not be changed. Note that the indentation of a comment +at the beginning of a line is never changed." + :group 'idlwave-code-formatting + :type 'string) + +(defcustom idlwave-begin-line-comment nil + "*A comment anchored at the beginning of line. +A comment matching this regular expression will not have its +indentation changed. If nil the default is \"^;\", i.e., any line +beginning with a \";\". Expressions for comments at the beginning of +the line should begin with \"^\"." + :group 'idlwave-code-formatting + :type '(choice (const :tag "Any line beginning with `;'" nil) + 'regexp)) + +(defcustom idlwave-code-comment ";;[^;]" + "*A comment that starts with this regular expression on a line by +itself is indented as if it is a part of IDL code. As a result if +the comment is not preceded by whitespace it is unchanged." + :group 'idlwave-code-formatting + :type 'regexp) + +;; Comments not matching any of the above will be indented as a +;; right-margin comment, i.e., to a minimum of `comment-column'. + + +;;; Routine Info and Completion --------------------------------------- + +(defgroup idlwave-routine-info-and-completion nil + "Routine info and name/keyword completion options for IDLWAVE mode." + :group 'idlwave) + +(defcustom idlwave-scan-all-buffers-for-routine-info t + "*Non-nil means, scan all buffers for IDL programs when updating info. +`idlwave-update-routine-info' scans buffers of the current Emacs session +for routine definitions. When this variable is nil, it only parses the +current buffer. When non-nil, all buffers are searched. +A prefix to \\[idlwave-update-routine-info] toggles the meaning of this +variable for the duration of the command." + :group 'idlwave-routine-info-and-completion + :type 'boolean) + +(defcustom idlwave-query-shell-for-routine-info t + "*Non-nil means query the shell for info about compiled routines. +Querying the shell is useful to get information about compiled modules, +and it is turned on by default. However, when you have a complete library +scan, this is not necessary." + :group 'idlwave-routine-info-and-completion + :type 'boolean) + +(defcustom idlwave-library-path nil + "Library path for Windows and MacOS. Not needed under Unix. +When selecting the directories to scan for IDL library routine info, +IDLWAVE can under UNIX query the shell for the exact search path. +However, under Windows and MacOS, the IDLWAVE shell does not work. In this +case, this variable specifies the path where IDLWAVE can find library files. +The shell will only be asked when this variable is nil. +The value is a list of directories. A directory preceeded by a `+' will +be search recursively." + :group 'idlwave-routine-info-and-completion + :type '(repeat (directory))) + +(defcustom idlwave-libinfo-file nil + "*File for routine information of the IDL library. +When this points to a file, the file will be loaded when IDLWAVE first +accesses routine info (or does completion). +When you scan the library with `idlwave-create-libinfo-file', this file +will be used to store the result." + :group 'idlwave-routine-info-and-completion + :type 'file) + +(eval-and-compile + (defconst idlwave-tmp + '(choice :tag "by applying the function" + (const upcase) + (const downcase) + (const capitalize) + (const preserve) + (symbol :tag "Other")))) + + +(defcustom idlwave-completion-case '((routine . upcase) + (keyword . upcase) + (class . preserve) + (method . preserve)) + "Association list setting the case of completed words. + +This variable determines the case (UPPER/lower/Capitalized...) of +words inserted into the buffer by completion. The preferred case can +be specified separately for routine names, keywords, classes and +methods. +This alist should therefore have entries for `routine' (normal +functions and procedures, i.e. non-methods), `keyword', `class', and +`method'. Plausible values are + +upcase upcase whole word, like `BOX_CURSOR' +downcase downcase whole word, like `read_ppm' +capitalize capitalize each part, like `Widget_Control' +preserve preserve case as is, like `IDLgrView' + +The value can also be any Emacs Lisp function which transforms the +case of characters in a string. + +A value of `preserve' means that the case of the completed word is +identical to the way it was written in the definition statement of the +routine. This was implemented to allow for mixed-case completion, in +particular of object classes and methods. +If a completable word is defined in multiple locations, the meaning of +`preserve' is not unique since the different definitions might be +cased differently. Therefore IDLWAVE always takes the case of the +*first* definition it encounters during routine info collection and +uses the case derived from it consistently. + +Note that a lowercase-only string in the buffer will always be completed in +lower case (but see the variable `idlwave-completion-force-default-case'). + +After changing this variable, you need to either restart Emacs or press +`C-u C-c C-i' to update the internal lists." + :group 'idlwave-routine-info-and-completion + :type `(repeat + (cons (symbol :tag "Derive completion case for") + ,idlwave-tmp))) + +(defcustom idlwave-completion-force-default-case nil + "*Non-nil means, completion will always honor `idlwave-completion-case'. +When nil, only the completion of a mixed case or upper case string +will honor the default settings in `idlwave-completion-case', while +the completion of lower case strings will be completed entirely in +lower case." + :group 'idlwave-routine-info-and-completion + :type 'boolean) + +(defcustom idlwave-complete-empty-string-as-lower-case nil + "*Non-nil means, the empty string is considered downcase for completion. +The case of what is already in the buffer determines the case of completions. +When this variable is non-nil, the empty string is considered to be downcase. +Completing on the empty string then offers downcase versions of the possible +completions." + :group 'idlwave-routine-info-and-completion + :type 'boolean) + +(defvar idlwave-default-completion-case-is-down nil + "Obsolete variable. See `idlwave-complete-empty-string-as-lower-case' and +`idlwave-completion-case'.") + +(defcustom idlwave-buffer-case-takes-precedence nil + "*Non-nil means, the case of tokens in buffers dominates over system stuff. +To make this possible, we need to re-case everything each time we update +the routine info from the buffers. This is slow. +The default is to consider the case given in the system and library files +first which makes updating much faster." + :group 'idlwave-routine-info-and-completion + :type 'boolean) + +(defcustom idlwave-completion-show-classes 1 + "*Number of classes to show when completing object methods and keywords. +When completing methods or keywords for an object with unknown class, +the *Completions* buffer will show the legal classes for each completion +like this: + +MyMethod <Class1,Class2,Class3> + +The value of this variable may be nil to inhibit display, or an integer to +indicate the maximum number of classes to display. + +On XEmacs, a full list of classes will also be placed into a `help-echo' +property on the competion items, so that the list of classes for the current +item is displayed in the echo area. If the value of this variable is a +negative integer, the `help-echo' property will be suppressed." + :group 'idlwave-routine-info-and-completion + :type '(choice (const :tag "Don't show" nil) + (integer :tag "Number of classes shown" 1))) + +(defcustom idlwave-completion-fontify-classes t + "*Non-nil means, fontify the classes in completions buffer. +This makes it easier to distinguish the completion items from the extra +class info listed. See `idlwave-completion-show-classes'." + :group 'idlwave-routine-info-and-completion + :type 'boolean) + +(defcustom idlwave-query-class '((method-default . nil) + (keyword-default . nil)) + "Association list governing specification of object classes for completion. + +When IDLWAVE is trying to complete items which belong to the object +oriented part of IDL, it usually cannot determine the class of a given +object from context. In order to provide the user with a correct list +of methods or keywords, it would have to determine the appropriate +class. IDLWAVE has two ways to deal with this problem. + +1. One possibility is to combine the items of all available + classes for the purpose of completion. So when completing a + method, all methods of all classes are available, and when + completing a keyword, all keywords allowed for this method in any + class will be possible completions. This behavior is very much + like normal completion and is therefore the default. It works much + better than one might think - only for the INIT, GETPROPERTY and + SETPROPERTY the keyword lists become uncomfortably long. + See also `idlwave-completion-show-classes'. + +2. The second possibility is to ask the user on each occasion. To + make this less interruptive, IDLWAVE can store the class as a text + property on the object operator `->'. For a given object in the + source code, class selection will then be needed only once + - for example to complete the method. Keywords to the method can + then be completed directly, because the class is already known. + You will have to turn on the storage of the selected class + explicitly with the variable `idlwave-store-inquired-class'. + +This variable allows to configure IDLWAVE's behavior during +completion. Its value is an alist, which should contain at least two +elements: (method-default . VALUE) and (keyword-default . VALUE), +where VALUE is either t or nil. These specify if the class should be +determined during method and keyword completion, respectively. + +The alist may have additional entries specifying exceptions from the +keyword completion rule for specific methods, like INIT or +GETPROPERTY. In order to turn on class specification for the INIT +method, add an entry (\"INIT\" . t). The method name must be ALL-CAPS." + :group 'idlwave-routine-info-and-completion + :type '(list + (cons (const method-default) + (boolean :tag "Determine class when completing METHODS ")) + (cons (const keyword-default) + (boolean :tag "Determine class when completing KEYWORDS ")) + (repeat + :tag "Exceptions to defaults" + :inline t + (cons (string :tag "MODULE" :value "") + (boolean :tag "Determine class for this method"))))) + +(defcustom idlwave-store-inquired-class nil + "*Non-nil means, store class of a method call as text property on `->'. +IDLWAVE sometimes has to ask the user for the class associated with a +particular object method call. This happens during the commands +`idlwave-routine-info' and `idlwave-complete', depending upon the +value of the variable `idlwave-query-class'. + +When you specify a class, this information can be stored as a text +property on the `->' arrow in the source code, so that during the same +editing session, IDLWAVE will not have to ask again. When this +variable is non-nil, IDLWAVE will store and reuse the class information. +The class stored can be checked and removed with `\\[idlwave-routine-info]' +on the arrow. + +The default of this variable is nil, since the result of commands then +is more predictable. However, if you know what you are doing, it can +be nice to turn this on. + +An arrow which knows the class will be highlighted with +`idlwave-class-arrow-face'. The command \\[idlwave-routine-info] +displays (with prefix arg: deletes) the class stored on the arrow +at point." + :group 'idlwave-routine-info-and-completion + :type 'boolean) + +(defcustom idlwave-class-arrow-face 'bold + "*Face to highlight object operator arrows `->' which carry a class property. +When IDLWAVE stores a class name as text property on an object arrow +(see variable `idlwave-store-inquired-class', it highlights the arrow +with this font in order to remind the user that this arrow is special." + :group 'idlwave-routine-info-and-completion + :type 'symbol) + +(defcustom idlwave-resize-routine-help-window t + "*Non-nil means, resize the Routine-info *Help* window to fit the content." + :group 'idlwave-routine-info-and-completion + :type 'boolean) + +(defcustom idlwave-keyword-completion-adds-equal t + "*Non-nil means, completion automatically adds `=' after completed keywords." + :group 'idlwave-routine-info + :type 'boolean) + +(defcustom idlwave-function-completion-adds-paren t + "*Non-nil means, completion automatically adds `(' after completed function. +Nil means, don't add anything. +A value of `2' means, also add the closing parenthesis and position cursor +between the two." + :group 'idlwave-routine-info + :type '(choice (const :tag "Nothing" nil) + (const :tag "(" t) + (const :tag "()" 2))) + +(defcustom idlwave-completion-restore-window-configuration t + "*Non-nil means, try to restore the window configuration after completion. +When completion is not unique, Emacs displays a list of completions. +This messes up your window configuration. With this variable set, IDLWAVE +restores the old configuration after successful completion." + :group 'idlwave-routine-info-and-completion + :type 'boolean) + +;;; Variables for abbrev and action behavior ----------------------------- + +(defgroup idlwave-abbrev-and-indent-action nil + "IDLWAVE performs actions when expanding abbreviations or indenting lines. +The variables in this group govern this." + :group 'idlwave) + +(defcustom idlwave-do-actions nil + "*Non-nil means performs actions when indenting. +The actions that can be performed are listed in `idlwave-indent-action-table'." + :group 'idlwave-abbrev-and-indent-action + :type 'boolean) + +(defcustom idlwave-abbrev-start-char "\\" + "*A single character string used to start abbreviations in abbrev mode. +Possible characters to chose from: ~`\% +or even '?'. '.' is not a good choice because it can make structure +field names act like abbrevs in certain circumstances. + +Changes to this in `idlwave-mode-hook' will have no effect. Instead a user +must set it directly using `setq' in the .emacs file before idlwave.el +is loaded." + :group 'idlwave-abbrev-and-indent-action + :type 'string) + +(defcustom idlwave-surround-by-blank nil + "*Non-nil means, enable `idlwave-surround'. +If non-nil, `=',`<',`>',`&',`,' are surrounded with spaces by +`idlwave-surround'. +See help for `idlwave-indent-action-table' for symbols using `idlwave-surround'. + +Also see the default key bindings for keys using `idlwave-surround'. +Keys are bound and made into actions calling `idlwave-surround' with +`idlwave-action-and-binding'. +See help for `idlwave-action-and-binding' for examples. + +Also see help for `idlwave-surround'." + :group 'idlwave-abbrev-and-indent-action + :type 'boolean) + +(defcustom idlwave-pad-keyword t + "*Non-nil means pad '=' for keywords like assignments. +Whenever `idlwave-surround' is non-nil then this affects how '=' is padded +for keywords. If non-nil it is padded the same as for assignments. +If nil then spaces are removed." + :group 'idlwave-abbrev-and-indent-action + :type 'boolean) + +(defcustom idlwave-show-block t + "*Non-nil means point blinks to block beginning for `idlwave-show-begin'." + :group 'idlwave-abbrev-and-indent-action + :type 'boolean) + +(defcustom idlwave-expand-generic-end nil + "*Non-nil means expand generic END to ENDIF/ENDELSE/ENDWHILE etc." + :group 'idlwave-abbrev-and-indent-action + :type 'boolean) + +(defcustom idlwave-abbrev-move t + "*Non-nil means the abbrev hook can move point. +Set to nil by `idlwave-expand-region-abbrevs'. To see the abbrev +definitions, use the command `list-abbrevs', for abbrevs that move +point. Moving point is useful, for example, to place point between +parentheses of expanded functions. + +See `idlwave-check-abbrev'." + :group 'idlwave-abbrev-and-indent-action + :type 'boolean) + +(defcustom idlwave-abbrev-change-case nil + "*Non-nil means all abbrevs will be forced to either upper or lower case. +If the value t, all expanded abbrevs will be upper case. +If the value is 'down then abbrevs will be forced to lower case. +If nil, the case will not change. +If `idlwave-reserved-word-upcase' is non-nil, reserved words will always be +upper case, regardless of this variable." + :group 'idlwave-abbrev-and-indent-action + :type 'boolean) + +(defcustom idlwave-reserved-word-upcase nil + "*Non-nil means, reserved words will be made upper case via abbrev expansion. +If nil case of reserved words is controlled by `idlwave-abbrev-change-case'. +Has effect only if in abbrev-mode." + :group 'idlwave-abbrev-and-indent-action + :type 'boolean) + +;;; Action/Expand Tables. +;; +;; The average user may have difficulty modifying this directly. It +;; can be modified/set in idlwave-mode-hook, but it is easier to use +;; idlwave-action-and-binding. See help for idlwave-action-and-binding for +;; examples of how to add an action. +;; +;; The action table is used by `idlwave-indent-line' whereas both the +;; action and expand tables are used by `idlwave-indent-and-action'. In +;; general, the expand table is only used when a line is explicitly +;; indented. Whereas, in addition to being used when the expand table +;; is used, the action table is used when a line is indirectly +;; indented via line splitting, auto-filling or a new line creation. +;; +;; Example actions: +;; +;; Capitalize system vars +;; (idlwave-action-and-binding idlwave-sysvar '(capitalize-word 1) t) +;; +;; Capitalize procedure name +;; (idlwave-action-and-binding "\\<\\(pro\\|function\\)\\>[ \t]*\\<" +;; '(capitalize-word 1) t) +;; +;; Capitalize common block name +;; (idlwave-action-and-binding "\\<common\\>[ \t]+\\<" +;; '(capitalize-word 1) t) +;; Capitalize label +;; (idlwave-action-and-binding (concat "^[ \t]*" idlwave-label) +;; '(capitalize-word -1) t) + +(defvar idlwave-indent-action-table nil + "*Associated array containing action lists of search string (car), +and function as a cdr. This table is used by `idlwave-indent-line'. +See documentation for `idlwave-do-action' for a complete description of +the action lists. + +Additions to the table are made with `idlwave-action-and-binding' when a +binding is not requested. +See help on `idlwave-action-and-binding' for examples.") + +(defvar idlwave-indent-expand-table nil + "*Associated array containing action lists of search string (car), +and function as a cdr. The table is used by the +`idlwave-indent-and-action' function. See documentation for +`idlwave-do-action' for a complete description of the action lists. + +Additions to the table are made with `idlwave-action-and-binding' when a +binding is requested. +See help on `idlwave-action-and-binding' for examples.") + +;;; Documentation header and history keyword --------------------------------- + +(defgroup idlwave-documentation nil + "Options for documenting IDLWAVE files." + :group 'idlwave) + +;; FIXME: make defcustom? +(defvar idlwave-file-header + (list nil + ";+ +; NAME: +; +; +; +; PURPOSE: +; +; +; +; CATEGORY: +; +; +; +; CALLING SEQUENCE: +; +; +; +; INPUTS: +; +; +; +; OPTIONAL INPUTS: +; +; +; +; KEYWORD PARAMETERS: +; +; +; +; OUTPUTS: +; +; +; +; OPTIONAL OUTPUTS: +; +; +; +; COMMON BLOCKS: +; +; +; +; SIDE EFFECTS: +; +; +; +; RESTRICTIONS: +; +; +; +; PROCEDURE: +; +; +; +; EXAMPLE: +; +; +; +; MODIFICATION HISTORY: +; +;- +") + "*A list (PATHNAME STRING) specifying the doc-header template to use for +summarizing a file. If PATHNAME is non-nil then this file will be included. +Otherwise STRING is used. If NIL, the file summary will be omitted. +For example you might set PATHNAME to the path for the +lib_template.pro file included in the IDL distribution.") + +(defcustom idlwave-timestamp-hook 'idlwave-default-insert-timestamp + "*The hook function used to update the timestamp of a function." + :group 'idlwave-documentation + :type 'function) + +(defcustom idlwave-doc-modifications-keyword "HISTORY" + "*The modifications keyword to use with the log documentation commands. +A ':' is added to the keyword end. +Inserted by doc-header and used to position logs by doc-modification. +If nil it will not be inserted." + :group 'idlwave-documentation + :type 'string) + +(defcustom idlwave-doclib-start "^;+\\+" + "*Regexp matching the start of a document library header." + :group 'idlwave-documentation + :type 'regexp) + +(defcustom idlwave-doclib-end "^;+-" + "*Regexp matching the end of a document library header." + :group 'idlwave-documentation + :type 'regexp) + +;;; External Programs ------------------------------------------------------- + +(defgroup idlwave-external-programs nil + "Miscellaneous options for IDLWAVE mode." + :group 'idlwave) + +;; WARNING: The following variable has recently been moved from +;; idlwave-shell.el to this file. I hope this does not break +;; anything. + +(defcustom idlwave-shell-explicit-file-name "idl" + "*If non-nil, is the command to run IDL. +Should be an absolute file path or path relative to the current environment +execution search path." + :group 'idlwave-external-programs + :type 'string) + +;; FIXME: Document a case when is this needed. +(defcustom idlwave-shell-command-line-options nil + "*A list of command line options for calling the IDL program." + :type '(repeat (string :value "")) + :group 'idlwave-external-programs) + +(defcustom idlwave-help-application "idlhelp" + "*The external application providing reference help for programming." + :group 'idlwave-external-programs + :type 'string) + +;;; Miscellaneous variables ------------------------------------------------- + +(defgroup idlwave-misc nil + "Miscellaneous options for IDLWAVE mode." + :group 'idlwave) + +(defcustom idlwave-startup-message t + "*Non-nil displays a startup message when `idlwave-mode' is first called." + :group 'idlwave-misc + :type 'boolean) + +(defcustom idlwave-default-font-lock-items + '(pros-and-functions batch-files idl-keywords label goto + common-blocks class-arrows) + "Items which should be fontified on the default fontification level 2. +IDLWAVE defines 3 levels of fontification. Level 1 is very little, level 3 +is everything and level 2 is specified by this list. +This variable must be set before IDLWAVE gets loaded. It is +a list of symbols, the following symbols are allowed. + +pros-and-functions Procedure and Function definitions +batch-files Batch Files +idl-keywords IDL Keywords +label Statement Labels +goto Goto Statements +common-blocks Common Blocks +keyword-parameters Keyword Parameters in routine definitions and calls +system-variables System Variables +fixme FIXME: Warning in comments (on XEmacs only v. 21.0 and up) +class-arrows Object Arrows with class property" + :group 'idlwave-misc + :type '(set + :inline t :greedy t + (const :tag "Procedure and Function definitions" pros-and-functions) + (const :tag "Batch Files" batch-files) + (const :tag "IDL Keywords (reserved words)" idl-keywords) + (const :tag "Statement Labels" label) + (const :tag "Goto Statements" goto) + (const :tag "Common Blocks" common-blocks) + (const :tag "Keyword Parameters" keyword-parameters) + (const :tag "System Variables" system-variables) + (const :tag "FIXME: Warning" fixme) + (const :tag "Object Arrows with class property " class-arrows))) + +(defcustom idlwave-mode-hook nil + "Normal hook. Executed when a buffer is put into `idlwave-mode'." + :group 'idlwave-misc + :type 'hook) + +(defcustom idlwave-load-hook nil + "Normal hook. Executed when idlwave.el is loaded." + :group 'idlwave-misc + :type 'hook) + +;;; +;;; End customization variables section +;;; + +;;; Non customization variables + +;;; font-lock mode - Additions by Phil Williams, Ulrik Dickow and +;;; Simon Marshall <simon@gnu.ai.mit.edu> +;;; and Carsten Dominik... + +(defconst idlwave-font-lock-keywords-1 nil + "Subdued level highlighting for IDLWAVE mode.") + +(defconst idlwave-font-lock-keywords-2 nil + "Medium level highlighting for IDLWAVE mode.") + +(defconst idlwave-font-lock-keywords-3 nil + "Gaudy level highlighting for IDLWAVE mode.") + +(let* ((oldp (or (string-match "Lucid" emacs-version) + (not (boundp 'emacs-minor-version)) + (and (<= emacs-major-version 19) + (<= emacs-minor-version 29)))) + + ;; The following are the reserved words in IDL. Maybe we should + ;; highlight some more stuff as well? + (idl-keywords +; '("and" "or" "xor" "not" +; "eq" "ge" "gt" "le" "lt" "ne" +; "for" "do" "endfor" +; "if" "then" "endif" "else" "endelse" +; "case" "of" "endcase" +; "begin" "end" +; "repeat" "until" "endrep" +; "while" "endwhile" +; "goto" "return" +; "inherits" "mod" "on_error" "on_ioerror") ;; on_error is not reserved + (concat "\\<\\(" + "and\\|begin\\|case\\|do\\|e\\(lse\\|nd\\(case\\|else\\|" + "for\\|if\\|rep\\|while\\)?\\|q\\)\\|for\\|g\\(oto\\|[et]\\)" + "\\|i\\(f\\|nherits\\)\\|l[et]\\|mod\\|n\\(e\\|ot\\)\\|" + "o\\(n_ioerror\\|[fr]\\)\\|re\\(peat\\|turn\\)\\|then\\|" + "until\\|while\\|xor" + "\\)\\>")) + + ;; Procedure declarations. Fontify keyword plus procedure name. + ;; Function declarations. Fontify keyword plus function name. + (pros-and-functions + '("\\<\\(function\\|pro\\)\\>[ \t]+\\(\\sw+\\(::\\sw+\\)?\\)" + (1 font-lock-keyword-face) + (2 font-lock-function-name-face nil t))) + + ;; Common blocks + (common-blocks + '("\\<\\(common\\)\\>[ \t]*\\(\\sw+\\)?[ \t]*,?" + (1 font-lock-keyword-face) ; "common" + (2 font-lock-reference-face nil t) ; block name + (font-lock-match-c++-style-declaration-item-and-skip-to-next + ;; Start with point after block name and comma + (goto-char (match-end 0)) ; needed for XEmacs, could be nil + nil + (1 font-lock-variable-name-face) ; variable names + ))) + + ;; Batch files + (batch-files + '("^[ \t]*\\(@[^ \t\n]+\\)" (1 font-lock-string-face))) + + ;; FIXME warning. + (fixme + '("\\<FIXME:" (0 font-lock-warning-face t))) + + ;; Labels + (label + '("^[ \t]*\\([a-zA-Z]\\sw*:\\)" (1 font-lock-reference-face))) + + ;; The goto statement and its label + (goto + '("\\(goto\\)[ \t]*,[ \t]*\\([a-zA-Z]\\sw*\\)" + (1 font-lock-keyword-face) + (2 font-lock-reference-face))) + + ;; Named parameters, like /xlog or ,xrange=[] + ;; This is anchored to the comma preceeding the keyword. + ;; With continuation lines, works only during whole buffer fontification. + (keyword-parameters + '("[(,][ \t]*\\(\\$[ \t]*\n[ \t]*\\)?\\(/[a-zA-Z_]\\sw*\\|[a-zA-Z_]\\sw*[ \t]*=\\)" + (2 font-lock-reference-face))) + + ;; System variables stars with a bang. + (system-variables + '("\\(![a-zA-Z_]+\\(\\.\\sw+\\)?\\)" + (1 font-lock-variable-name-face))) + + ;; Special and unusual operators (not used because too noisy) + (special-operators + '("[<>#]" (0 font-lock-keyword-face))) + + ;; All operators (not used because too noisy) + (all-operators + '("[-*^#+<>/]" (0 font-lock-keyword-face))) + + ;; Arrows with text property `idlwave-class' + (class-arrows + (list 'idlwave-match-class-arrows + (list 0 (if (featurep 'xemacs) + idlwave-class-arrow-face + 'idlwave-class-arrow-face)))) + + ) + + ;; The following lines are just a dummy to make the compiler shut up + ;; about variables bound but not used. + (setq oldp oldp + idl-keywords idl-keywords + pros-and-functions pros-and-functions + common-blocks common-blocks + batch-files batch-files + fixme fixme + label label + goto goto + keyword-parameters keyword-parameters + system-variables system-variables + special-operators special-operators + all-operators all-operators + class-arrows class-arrows) + + (setq idlwave-font-lock-keywords-1 + (list pros-and-functions + batch-files + )) + + (setq idlwave-font-lock-keywords-2 + (mapcar 'symbol-value idlwave-default-font-lock-items)) + + (setq idlwave-font-lock-keywords-3 + (list pros-and-functions + batch-files + idl-keywords + label goto + common-blocks + keyword-parameters + system-variables + class-arrows + )) + ) + +(defun idlwave-match-class-arrows (limit) + ;; Match an object arrow with class property + (and idlwave-store-inquired-class + (re-search-forward "->" limit 'limit) + (get-text-property (match-beginning 0) 'idlwave-class))) + +(defvar idlwave-font-lock-keywords idlwave-font-lock-keywords-2 + "Default expressions to highlight in IDLWAVE mode.") + +(defvar idlwave-font-lock-defaults + '((idlwave-font-lock-keywords + idlwave-font-lock-keywords-1 + idlwave-font-lock-keywords-2 + idlwave-font-lock-keywords-3) + nil t + ((?$ . "w") (?_ . "w") (?. . "w")) + beginning-of-line)) + +(put 'idlwave-mode 'font-lock-defaults + idlwave-font-lock-defaults) ; XEmacs + +(defconst idlwave-comment-line-start-skip "^[ \t]*;" + "Regexp to match the start of a full-line comment. +That is the _beginning_ of a line containing a comment delimiter `;' preceded +only by whitespace.") + +(defconst idlwave-begin-block-reg "\\<\\(pro\\|function\\|begin\\|case\\)\\>" + "Regular expression to find the beginning of a block. The case does +not matter. The search skips matches in comments.") + +(defconst idlwave-begin-unit-reg "\\<\\(pro\\|function\\)\\>\\|\\`" + "Regular expression to find the beginning of a unit. The case does +not matter.") + +(defconst idlwave-end-unit-reg "\\<\\(pro\\|function\\)\\>\\|\\'" + "Regular expression to find the line that indicates the end of unit. +This line is the end of buffer or the start of another unit. The case does +not matter. The search skips matches in comments.") + +(defconst idlwave-continue-line-reg "\\<\\$" + "Regular expression to match a continued line.") + +(defconst idlwave-end-block-reg + "\\<end\\(\\|case\\|else\\|for\\|if\\|rep\\|while\\)\\>" + "Regular expression to find the end of a block. The case does +not matter. The search skips matches found in comments.") + +(defconst idlwave-block-matches + '(("pro" . "end") + ("function" . "end") + ("case" . "endcase") + ("else" . "endelse") + ("for" . "endfor") + ("then" . "endif") + ("repeat" . "endrep") + ("while" . "endwhile")) + "Matches between statements and the corresponding END variant. +The cars are the reserved words starting a block. If the block really +begins with BEGIN, the cars are the reserved words before the begin +which can be used to identify the block type. +This is used to check for the correct END type, to close blocks and +to expand generic end statements to their detailed form.") + +(defconst idlwave-block-match-regexp + "\\<\\(else\\|for\\|then\\|repeat\\|while\\)\\>" +"Regular expression matching reserved words which can stand before +blocks starting with a BEGIN statement. The matches must have associations +`idlwave-block-matches'") + +(defconst idlwave-identifier "[a-zA-Z][a-zA-Z0-9$_]*" + "Regular expression matching an IDL identifier.") + +(defconst idlwave-sysvar (concat "!" idlwave-identifier) + "Regular expression matching IDL system variables.") + +(defconst idlwave-variable (concat idlwave-identifier "\\|" idlwave-sysvar) + "Regular expression matching IDL variable names.") + +(defconst idlwave-label (concat idlwave-identifier ":") + "Regular expression matching IDL labels.") + +(defconst idlwave-statement-match + (list + ;; "endif else" is the the only possible "end" that can be + ;; followed by a statement on the same line. + '(endelse . ("end\\(\\|if\\)\\s +else" "end\\(\\|if\\)\\s +else")) + ;; all other "end"s can not be followed by a statement. + (cons 'end (list idlwave-end-block-reg nil)) + '(if . ("if\\>" "then")) + '(for . ("for\\>" "do")) + '(begin . ("begin\\>" nil)) + '(pdef . ("pro\\>\\|function\\>" nil)) + '(while . ("while\\>" "do")) + '(repeat . ("repeat\\>" "repeat")) + '(goto . ("goto\\>" nil)) + '(case . ("case\\>" nil)) + (cons 'call (list (concat idlwave-identifier "\\(\\s *$\\|\\s *,\\)") nil)) + '(assign . ("[^=\n]*=" nil))) + + "Associated list of statement matching regular expressions. +Each regular expression matches the start of an IDL statement. The +first element of each association is a symbol giving the statement +type. The associated value is a list. The first element of this list +is a regular expression matching the start of an IDL statement for +identifying the statement type. The second element of this list is a +regular expression for finding a substatement for the type. The +substatement starts after the end of the found match modulo +whitespace. If it is nil then the statement has no substatement. The +list order matters since matching an assignment statement exactly is +not possible without parsing. Thus assignment statement become just +the leftover unidentified statements containing and equal sign. " ) + +(defvar idlwave-fill-function 'auto-fill-function + "IDL mode auto fill function.") + +(defvar idlwave-comment-indent-function 'comment-indent-function + "IDL mode comment indent function.") + +;; Note that this is documented in the v18 manuals as being a string +;; of length one rather than a single character. +;; The code in this file accepts either format for compatibility. +(defvar idlwave-comment-indent-char ?\ + "Character to be inserted for IDL comment indentation. +Normally a space.") + +(defconst idlwave-continuation-char ?$ + "Character which is inserted as a last character on previous line by + \\[idlwave-split-line] to begin a continuation line. Normally $.") + +(defconst idlwave-mode-version " 3.11") + +(defmacro idlwave-keyword-abbrev (&rest args) + "Creates a function for abbrev hooks to call `idlwave-check-abbrev' with args." + (` (quote (lambda () + (, (append '(idlwave-check-abbrev) args)))))) + +;; If I take the time I can replace idlwave-keyword-abbrev with +;; idlwave-code-abbrev and remove the quoted abbrev check from +;; idlwave-check-abbrev. Then, e.g, (idlwave-keyword-abbrev 0 t) becomes +;; (idlwave-code-abbrev idlwave-check-abbrev 0 t). In fact I should change +;; the name of idlwave-check-abbrev to something like idlwave-modify-abbrev. + +(defmacro idlwave-code-abbrev (&rest args) + "Creates a function for abbrev hooks that ensures abbrevs are not quoted. +Specifically, if the abbrev is in a comment or string it is unexpanded. +Otherwise ARGS forms a list that is evaluated." + (` (quote (lambda () + (, (prin1-to-string args)) ;; Puts the code in the doc string + (if (idlwave-quoted) (progn (unexpand-abbrev) nil) + (, (append args))))))) + +(defvar idlwave-mode-map (make-sparse-keymap) + "Keymap used in IDL mode.") + +(defvar idlwave-mode-syntax-table (make-syntax-table) + "Syntax table in use in `idlwave-mode' buffers.") + +(modify-syntax-entry ?+ "." idlwave-mode-syntax-table) +(modify-syntax-entry ?- "." idlwave-mode-syntax-table) +(modify-syntax-entry ?* "." idlwave-mode-syntax-table) +(modify-syntax-entry ?/ "." idlwave-mode-syntax-table) +(modify-syntax-entry ?^ "." idlwave-mode-syntax-table) +(modify-syntax-entry ?# "." idlwave-mode-syntax-table) +(modify-syntax-entry ?= "." idlwave-mode-syntax-table) +(modify-syntax-entry ?% "." idlwave-mode-syntax-table) +(modify-syntax-entry ?< "." idlwave-mode-syntax-table) +(modify-syntax-entry ?> "." idlwave-mode-syntax-table) +(modify-syntax-entry ?\' "\"" idlwave-mode-syntax-table) +(modify-syntax-entry ?\" "\"" idlwave-mode-syntax-table) +(modify-syntax-entry ?\\ "." idlwave-mode-syntax-table) +(modify-syntax-entry ?_ "_" idlwave-mode-syntax-table) +(modify-syntax-entry ?{ "(}" idlwave-mode-syntax-table) +(modify-syntax-entry ?} "){" idlwave-mode-syntax-table) +(modify-syntax-entry ?$ "_" idlwave-mode-syntax-table) +(modify-syntax-entry ?. "." idlwave-mode-syntax-table) +(modify-syntax-entry ?\; "<" idlwave-mode-syntax-table) +(modify-syntax-entry ?\n ">" idlwave-mode-syntax-table) +(modify-syntax-entry ?\f ">" idlwave-mode-syntax-table) + +(defvar idlwave-find-symbol-syntax-table + (copy-syntax-table idlwave-mode-syntax-table) + "Syntax table that treats symbol characters as word characters.") + +(modify-syntax-entry ?$ "w" idlwave-find-symbol-syntax-table) +(modify-syntax-entry ?_ "w" idlwave-find-symbol-syntax-table) + +(defun idlwave-action-and-binding (key cmd &optional select) + "KEY and CMD are made into a key binding and an indent action. +KEY is a string - same as for the `define-key' function. CMD is a +function of no arguments or a list to be evaluated. CMD is bound to +KEY in `idlwave-mode-map' by defining an anonymous function calling +`self-insert-command' followed by CMD. If KEY contains more than one +character a binding will only be set if SELECT is 'both. + +(KEY . CMD\ is also placed in the `idlwave-indent-expand-table', +replacing any previous value for KEY. If a binding is not set then it +will instead be placed in `idlwave-indent-action-table'. + +If the optional argument SELECT is nil then an action and binding are +created. If SELECT is 'noaction, then a binding is always set and no +action is created. If SELECT is 'both then an action and binding +will both be created even if KEY contains more than one character. +Otherwise, if SELECT is non-nil then only an action is created. + +Some examples: +No spaces before and 1 after a comma + (idlwave-action-and-binding \",\" '(idlwave-surround 0 1)) +A minimum of 1 space before and after `=' (see `idlwave-expand-equal'). + (idlwave-action-and-binding \"=\" '(idlwave-expand-equal -1 -1)) +Capitalize system variables - action only + (idlwave-action-and-binding idlwave-sysvar '(capitalize-word 1) t)" + (if (not (equal select 'noaction)) + ;; Add action + (let* ((table (if select 'idlwave-indent-action-table + 'idlwave-indent-expand-table)) + (cell (assoc key (eval table)))) + (if cell + ;; Replace action command + (setcdr cell cmd) + ;; New action + (set table (append (eval table) (list (cons key cmd))))))) + ;; Make key binding for action + (if (or (and (null select) (= (length key) 1)) + (equal select 'noaction) + (equal select 'both)) + (define-key idlwave-mode-map key + (append '(lambda () + (interactive) + (self-insert-command 1)) + (list (if (listp cmd) + cmd + (list cmd))))))) + +(fset 'idlwave-debug-map (make-sparse-keymap)) + +(define-key idlwave-mode-map "'" 'idlwave-show-matching-quote) +(define-key idlwave-mode-map "\"" 'idlwave-show-matching-quote) +(define-key idlwave-mode-map "\C-c;" 'idlwave-toggle-comment-region) +(define-key idlwave-mode-map "\C-\M-a" 'idlwave-beginning-of-subprogram) +(define-key idlwave-mode-map "\C-\M-e" 'idlwave-end-of-subprogram) +(define-key idlwave-mode-map "\C-c{" 'idlwave-beginning-of-block) +(define-key idlwave-mode-map "\C-c}" 'idlwave-end-of-block) +(define-key idlwave-mode-map "\C-c]" 'idlwave-close-block) +(define-key idlwave-mode-map "\M-\C-h" 'idlwave-mark-subprogram) +(define-key idlwave-mode-map "\M-\C-n" 'idlwave-forward-block) +(define-key idlwave-mode-map "\M-\C-p" 'idlwave-backward-block) +(define-key idlwave-mode-map "\M-\C-d" 'idlwave-down-block) +(define-key idlwave-mode-map "\M-\C-u" 'idlwave-backward-up-block) +(define-key idlwave-mode-map "\M-\r" 'idlwave-split-line) +(define-key idlwave-mode-map "\M-\C-q" 'idlwave-indent-subprogram) +(define-key idlwave-mode-map "\C-c\C-p" 'idlwave-previous-statement) +(define-key idlwave-mode-map "\C-c\C-n" 'idlwave-next-statement) +;; (define-key idlwave-mode-map "\r" 'idlwave-newline) +;; (define-key idlwave-mode-map "\t" 'idlwave-indent-line) +(define-key idlwave-mode-map "\C-c\C-a" 'idlwave-auto-fill-mode) +(define-key idlwave-mode-map "\M-q" 'idlwave-fill-paragraph) +(define-key idlwave-mode-map "\M-s" 'idlwave-edit-in-idlde) +(define-key idlwave-mode-map "\C-c\C-h" 'idlwave-doc-header) +(define-key idlwave-mode-map "\C-c\C-m" 'idlwave-doc-modification) +(define-key idlwave-mode-map "\C-c\C-c" 'idlwave-case) +(define-key idlwave-mode-map "\C-c\C-d" 'idlwave-debug-map) +(define-key idlwave-mode-map "\C-c\C-d\C-c" 'idlwave-shell-save-and-run) +(define-key idlwave-mode-map "\C-c\C-f" 'idlwave-for) +;; (define-key idlwave-mode-map "\C-c\C-f" 'idlwave-function) +;; (define-key idlwave-mode-map "\C-c\C-p" 'idlwave-procedure) +(define-key idlwave-mode-map "\C-c\C-r" 'idlwave-repeat) +(define-key idlwave-mode-map "\C-c\C-w" 'idlwave-while) +(define-key idlwave-mode-map "\C-c\C-s" 'idlwave-shell) +(define-key idlwave-mode-map "\C-c\C-l" 'idlwave-shell-recenter-shell-window) +(autoload 'idlwave-shell-send-command "idlwave-shell") +(autoload 'idlwave-shell-recenter-shell-window "idlwave-shell" + "Run `idlwave-shell' and switch back to current window" t) +(autoload 'idlwave-shell-save-and-run "idlwave-shell" + "Save and run buffer under the shell." t) +(define-key idlwave-mode-map "\C-c\C-v" 'idlwave-find-module) +(define-key idlwave-mode-map "\C-c?" 'idlwave-routine-info) +(define-key idlwave-mode-map "\M-?" 'idlwave-routine-info-from-idlhelp) +(define-key idlwave-mode-map [(meta tab)] 'idlwave-complete) +(define-key idlwave-mode-map "\C-c\C-i" 'idlwave-update-routine-info) +(define-key idlwave-mode-map "\C-c=" 'idlwave-resolve) + +;; Set action and key bindings. +;; See description of the function `idlwave-action-and-binding'. +;; Automatically add spaces for the following characters +(idlwave-action-and-binding "&" '(idlwave-surround -1 -1)) +(idlwave-action-and-binding "<" '(idlwave-surround -1 -1)) +(idlwave-action-and-binding ">" '(idlwave-surround -1 -1 '(?-))) +(idlwave-action-and-binding "," '(idlwave-surround 0 -1)) +;; Automatically add spaces to equal sign if not keyword +(idlwave-action-and-binding "=" '(idlwave-expand-equal -1 -1)) + +;;; +;;; Abbrev Section +;;; +;;; When expanding abbrevs and the abbrev hook moves backward, an extra +;;; space is inserted (this is the space typed by the user to expanded +;;; the abbrev). +;;; + +(condition-case nil + (modify-syntax-entry (string-to-char idlwave-abbrev-start-char) + "w" idlwave-mode-syntax-table) + (error nil)) + +(defvar idlwave-mode-abbrev-table nil + "Abbreviation table used for IDLWAVE mode") +(define-abbrev-table 'idlwave-mode-abbrev-table ()) +(let ((abbrevs-changed nil) ;; mask the current value to avoid save + (tb idlwave-mode-abbrev-table) + (c idlwave-abbrev-start-char)) + ;; + ;; Templates + ;; + (define-abbrev tb (concat c "c") "" (idlwave-code-abbrev idlwave-case)) + (define-abbrev tb (concat c "f") "" (idlwave-code-abbrev idlwave-for)) + (define-abbrev tb (concat c "fu") "" (idlwave-code-abbrev idlwave-function)) + (define-abbrev tb (concat c "pr") "" (idlwave-code-abbrev idlwave-procedure)) + (define-abbrev tb (concat c "r") "" (idlwave-code-abbrev idlwave-repeat)) + (define-abbrev tb (concat c "w") "" (idlwave-code-abbrev idlwave-while)) + (define-abbrev tb (concat c "i") "" (idlwave-code-abbrev idlwave-if)) + (define-abbrev tb (concat c "elif") "" (idlwave-code-abbrev idlwave-elif)) + ;; + ;; Keywords, system functions, conversion routines + ;; + (define-abbrev tb (concat c "b") "begin" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb (concat c "co") "common" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb (concat c "cb") "byte()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "cx") "fix()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "cl") "long()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "cf") "float()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "cs") "string()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "cc") "complex()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "cd") "double()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "e") "else" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb (concat c "ec") "endcase" 'idlwave-show-begin) + (define-abbrev tb (concat c "ee") "endelse" 'idlwave-show-begin) + (define-abbrev tb (concat c "ef") "endfor" 'idlwave-show-begin) + (define-abbrev tb (concat c "ei") "endif else if" 'idlwave-show-begin) + (define-abbrev tb (concat c "el") "endif else" 'idlwave-show-begin) + (define-abbrev tb (concat c "en") "endif" 'idlwave-show-begin) + (define-abbrev tb (concat c "er") "endrep" 'idlwave-show-begin) + (define-abbrev tb (concat c "ew") "endwhile" 'idlwave-show-begin) + (define-abbrev tb (concat c "g") "goto," (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb (concat c "h") "help," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "k") "keyword_set()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "n") "n_elements()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "on") "on_error," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "oi") "on_ioerror," (idlwave-keyword-abbrev 0 1)) + (define-abbrev tb (concat c "ow") "openw," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "or") "openr," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "ou") "openu," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "p") "print," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "pt") "plot," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "re") "read," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "rf") "readf," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "ru") "readu," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "rt") "return" (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "sc") "strcompress()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "sn") "strlen()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "sl") "strlowcase()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "su") "strupcase()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "sm") "strmid()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "sp") "strpos()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "st") "strput()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "sr") "strtrim()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "t") "then" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb (concat c "u") "until" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb (concat c "wu") "writeu," (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "ine") "if n_elements() eq 0 then" + (idlwave-keyword-abbrev 11)) + (define-abbrev tb (concat c "inn") "if n_elements() ne 0 then" + (idlwave-keyword-abbrev 11)) + (define-abbrev tb (concat c "np") "n_params()" (idlwave-keyword-abbrev 0)) + (define-abbrev tb (concat c "s") "size()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "wi") "widget_info()" (idlwave-keyword-abbrev 1)) + (define-abbrev tb (concat c "wc") "widget_control," (idlwave-keyword-abbrev 0)) + + ;; This section is reserved words only. (From IDL user manual) + ;; + (define-abbrev tb "and" "and" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "begin" "begin" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "case" "case" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "common" "common" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "do" "do" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "else" "else" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "end" "end" 'idlwave-show-begin-check) + (define-abbrev tb "endcase" "endcase" 'idlwave-show-begin-check) + (define-abbrev tb "endelse" "endelse" 'idlwave-show-begin-check) + (define-abbrev tb "endfor" "endfor" 'idlwave-show-begin-check) + (define-abbrev tb "endif" "endif" 'idlwave-show-begin-check) + (define-abbrev tb "endrep" "endrep" 'idlwave-show-begin-check) + (define-abbrev tb "endwhi" "endwhi" 'idlwave-show-begin-check) + (define-abbrev tb "endwhile" "endwhile" 'idlwave-show-begin-check) + (define-abbrev tb "eq" "eq" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "for" "for" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "function" "function" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "ge" "ge" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "goto" "goto" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "gt" "gt" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "if" "if" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "le" "le" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "lt" "lt" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "mod" "mod" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "ne" "ne" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "not" "not" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "of" "of" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "on_ioerror" "on_ioerror" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "or" "or" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "pro" "pro" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "repeat" "repeat" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "then" "then" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "until" "until" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "while" "while" (idlwave-keyword-abbrev 0 t)) + (define-abbrev tb "xor" "xor" (idlwave-keyword-abbrev 0 t))) + +(defvar imenu-create-index-function) +(defvar extract-index-name-function) +(defvar prev-index-position-function) +(defvar imenu-extract-index-name-function) +(defvar imenu-prev-index-position-function) +;; defined later - so just make the compiler shut up +(defvar idlwave-mode-menu) +(defvar idlwave-mode-debug-menu) + +;;;###autoload +(defun idlwave-mode () + "Major mode for editing IDL and WAVE CL .pro files. + +The main features of this mode are + +1. Indentation and Formatting + -------------------------- + Like other Emacs programming modes, C-j inserts a newline and indents. + TAB is used for explicit indentation of the current line. + + To start a continuation line, use \\[idlwave-split-line]. This function can also + be used in the middle of a line to split the line at that point. + When used inside a long constant string, the string is split at + that point with the `+' concatenation operator. + + Comments are indented as follows: + + `;;;' Indentation remains unchanged. + `;;' Indent like the surrounding code + `;' Indent to a minimum column. + + The indentation of comments starting in column 0 is never changed. + + Use \\[idlwave-fill-paragraph] to refill a paragraph inside a comment. The indentation + of the second line of the paragraph relative to the first will be + retained. Use \\[idlwave-auto-fill-mode] to toggle auto-fill mode for these comments. + When the variable `idlwave-fill-comment-line-only' is nil, code + can also be auto-filled and auto-indented (not recommended). + + To convert pre-existing IDL code to your formatting style, mark the + entire buffer with \\[mark-whole-buffer] and execute \\[idlwave-expand-region-abbrevs]. + Then mark the entire buffer again followed by \\[indent-region] (`indent-region'). + +2. Routine Info + ------------ + IDLWAVE displays information about the calling sequence and the accepted + keyword parameters of a procedure or function with \\[idlwave-routine-info]. + \\[idlwave-find-module] jumps to the source file of a module. + These commands know about system routines, all routines in idlwave-mode + buffers and (when the idlwave-shell is active) about all modules + currently compiled under this shell. Use \\[idlwave-update-routine-info] to update this + information, which is also used for completion (see next item). + +3. Completion + ---------- + \\[idlwave-complete] completes the names of procedures, functions and + keyword parameters. It is context sensitive and figures out what + is expected at point (procedure/function/keyword). Lower case + strings are completed in lower case, other strings in mixed or + upper case. + +4. Code Templates and Abbreviations + -------------------------------- + Many Abbreviations are predefined to expand to code fragments and templates. + The abbreviations start generally with a `\\`. Some examples + + \\pr PROCEDURE template + \\fu FUNCTION template + \\c CASE statement template + \\f FOR loop template + \\r REPEAT Loop template + \\w WHILE loop template + \\i IF statement template + \\elif IF-ELSE statement template + \\b BEGIN + + For a full list, use \\[idlwave-list-abbrevs]. Some templates also have + direct keybindings - see the list of keybindings below. + + \\[idlwave-doc-header] inserts a documentation header at the beginning of the + current program unit (pro, function or main). Change log entries + can be added to the current program unit with \\[idlwave-doc-modification]. + +5. Automatic Case Conversion + ------------------------- + The case of reserved words and some abbrevs is controlled by + `idlwave-reserved-word-upcase' and `idlwave-abbrev-change-case'. + +6. Automatic END completion + ------------------------ + If the variable `idlwave-expand-generic-end' is non-nil, each END typed + will be converted to the specific version, like ENDIF, ENDFOR, etc. + +7. Hooks + ----- + Loading idlwave.el runs `idlwave-load-hook'. + Turning on `idlwave-mode' runs `idlwave-mode-hook'. + +8. Documentation and Customization + ------------------------------- + Info documentation for this package is available. Use \\[idlwave-info] + to display (complain to your sysadmin if that does not work). + For Postscript and HTML versions of the documentation, check IDLWAVE's + homepage at `http://www.strw.leidenuniv.nl/~dominik/Tools/idlwave'. + IDLWAVE has customize support - see the group `idlwave'. + +9. Keybindings + ----------- + Here is a list of all keybindings of this mode. + If some of the key bindings below show with ??, use \\[describe-key] + followed by the key sequence to see what the key sequence does. + +\\{idlwave-mode-map}" + + (interactive) + (kill-all-local-variables) + + (if idlwave-startup-message + (message "Emacs IDLWAVE mode version %s." idlwave-mode-version)) + (setq idlwave-startup-message nil) + + (setq local-abbrev-table idlwave-mode-abbrev-table) + (set-syntax-table idlwave-mode-syntax-table) + + (set (make-local-variable 'indent-line-function) 'idlwave-indent-and-action) + + (make-local-variable idlwave-comment-indent-function) + (set idlwave-comment-indent-function 'idlwave-comment-hook) + + (set (make-local-variable 'comment-start-skip) ";+[ \t]*") + (set (make-local-variable 'comment-start) ";") + (set (make-local-variable 'require-final-newline) t) + (set (make-local-variable 'abbrev-all-caps) t) + (set (make-local-variable 'indent-tabs-mode) nil) + (set (make-local-variable 'completion-ignore-case) t) + + (use-local-map idlwave-mode-map) + + (when (featurep 'easymenu) + (easy-menu-add idlwave-mode-menu idlwave-mode-map) + (easy-menu-add idlwave-mode-debug-menu idlwave-mode-map)) + + (setq mode-name "IDLWAVE") + (setq major-mode 'idlwave-mode) + (setq abbrev-mode t) + + (set (make-local-variable idlwave-fill-function) 'idlwave-auto-fill) + (setq comment-end "") + (set (make-local-variable 'comment-multi-line) nil) + (set (make-local-variable 'paragraph-separate) "[ \t\f]*$\\|[ \t]*;+[ \t]*$") + (set (make-local-variable 'paragraph-start) "[ \t\f]\\|[ \t]*;+[ \t]") + (set (make-local-variable 'paragraph-ignore-fill-prefix) nil) + (set (make-local-variable 'parse-sexp-ignore-comments) nil) + + ;; Set tag table list to use IDLTAGS as file name. + (if (boundp 'tag-table-alist) + (add-to-list 'tag-table-alist '("\\.pro$" . "IDLTAGS"))) + + ;; Font-lock additions - originally Phil Williams, then Ulrik Dickow + ;; Following line is for Emacs - XEmacs uses the corresponding porperty + ;; on the `idlwave-mode' symbol. + (set (make-local-variable 'font-lock-defaults) idlwave-font-lock-defaults) + + ;; Imenu setup + (set (make-local-variable 'imenu-create-index-function) + 'imenu-default-create-index-function) + (set (make-local-variable 'imenu-extract-index-name-function) + 'idlwave-unit-name) + (set (make-local-variable 'imenu-prev-index-position-function) + 'idlwave-prev-index-position) + + ;; Make a local post-command-hook and add our hook to it + (make-local-hook 'post-command-hook) + (add-hook 'post-command-hook 'idlwave-command-hook nil t) + + ;; Run the mode hook + (run-hooks 'idlwave-mode-hook)) + +;; +;; Done with start up and initialization code. +;; The remaining routines are the code formatting functions. +;; + +(defun idlwave-push-mark (&rest rest) + "Push mark for compatibility with Emacs 18/19." + (if (fboundp 'iconify-frame) + (apply 'push-mark rest) + (push-mark))) + +(defun idlwave-hard-tab () + "Inserts TAB in buffer in current position." + (interactive) + (insert "\t")) + +;;; This stuff is experimental + +(defvar idlwave-command-hook nil + "If non-nil, a list that can be evaluated using `eval'. +It is evaluated in the lisp function `idlwave-command-hook' which is +placed in `post-command-hook'.") + +(defun idlwave-command-hook () + "Command run after every command. +Evaluates a non-nil value of the *variable* `idlwave-command-hook' and +sets the variable to zero afterwards." + (and idlwave-command-hook + (listp idlwave-command-hook) + (condition-case nil + (eval idlwave-command-hook) + (error nil))) + (setq idlwave-command-hook nil)) + +;;; End experiment + +;; It would be better to use expand.el for better abbrev handling and +;; versatility. + +(defun idlwave-check-abbrev (arg &optional reserved) + "Reverses abbrev expansion if in comment or string. +Argument ARG is the number of characters to move point +backward if `idlwave-abbrev-move' is non-nil. +If optional argument RESERVED is non-nil then the expansion +consists of reserved words, which will be capitalized if +`idlwave-reserved-word-upcase' is non-nil. +Otherwise, the abbrev will be capitalized if `idlwave-abbrev-change-case' +is non-nil, unless its value is \`down in which case the abbrev will be +made into all lowercase. +Returns non-nil if abbrev is left expanded." + (if (idlwave-quoted) + (progn (unexpand-abbrev) + nil) + (if (and reserved idlwave-reserved-word-upcase) + (upcase-region last-abbrev-location (point)) + (cond + ((equal idlwave-abbrev-change-case 'down) + (downcase-region last-abbrev-location (point))) + (idlwave-abbrev-change-case + (upcase-region last-abbrev-location (point))))) + (if (and idlwave-abbrev-move (> arg 0)) + (if (boundp 'post-command-hook) + (setq idlwave-command-hook (list 'backward-char (1+ arg))) + (backward-char arg))) + t)) + +(defun idlwave-in-comment () + "Returns t if point is inside a comment, nil otherwise." + (save-excursion + (let ((here (point))) + (and (idlwave-goto-comment) (> here (point)))))) + +(defun idlwave-goto-comment () + "Move to start of comment delimiter on current line. +Moves to end of line if there is no comment delimiter. +Ignores comment delimiters in strings. +Returns point if comment found and nil otherwise." + (let ((eos (progn (end-of-line) (point))) + (data (match-data)) + found) + ;; Look for first comment delimiter not in a string + (beginning-of-line) + (setq found (search-forward comment-start eos 'lim)) + (while (and found (idlwave-in-quote)) + (setq found (search-forward comment-start eos 'lim))) + (store-match-data data) + (and found (not (idlwave-in-quote)) + (progn + (backward-char 1) + (point))))) + +(defun idlwave-show-matching-quote () + "Insert quote and show matching quote if this is end of a string." + (interactive) + (let ((bq (idlwave-in-quote)) + (inq last-command-char)) + (if (and bq (not (idlwave-in-comment))) + (let ((delim (char-after bq))) + (insert inq) + (if (eq inq delim) + (save-excursion + (goto-char bq) + (sit-for 1)))) + ;; Not the end of a string + (insert inq)))) + +(defun idlwave-show-begin-check () + "Ensure that the previous word was a token before `idlwave-show-begin'. +An END token must be preceded by whitespace." + (if + (save-excursion + (backward-word 1) + (backward-char 1) + (looking-at "[ \t\n\f]")) + (idlwave-show-begin))) + +(defun idlwave-show-begin () + "Finds the start of current block and blinks to it for a second. +Also checks if the correct end statement has been used." + ;; All end statements are reserved words + (let* ((pos (point)) + end end1) + (when (and (idlwave-check-abbrev 0 t) + idlwave-show-block) + (save-excursion + ;; Move inside current block + (setq end (buffer-substring + (save-excursion (skip-chars-backward "a-zA-Z") + (point)) + (point))) + (idlwave-beginning-of-statement) + (idlwave-block-jump-out -1 'nomark) + (when (setq end1 (cdr (idlwave-block-master))) + (cond + ((null end1)) ; no-opeartion + ((string= (downcase end) (downcase end1)) + (sit-for 1)) + ((string= (downcase end) "end") + ;; A generic end + (if idlwave-expand-generic-end + (save-excursion + (goto-char pos) + (backward-char 3) + (insert (if (string= end "END") (upcase end1) end1)) + (delete-char 3))) + (sit-for 1)) + (t + (beep) + (message "Warning: Shouldn't this be \"%s\" instead of \"%s\"?" + end1 end) + (sit-for 1)))))))) + +(defun idlwave-block-master () + (let ((case-fold-search t)) + (save-excursion + (cond + ((looking-at "pro\\|case\\|function\\>") + (assoc (downcase (match-string 0)) idlwave-block-matches)) + ((looking-at "begin\\>") + (let ((limit (save-excursion + (idlwave-beginning-of-statement) + (point)))) + (cond + ((re-search-backward idlwave-block-match-regexp limit t) + (assoc (downcase (match-string 1)) + idlwave-block-matches)) + ;;((re-search-backward ":[ \t]*\\=" limit t) + ;; ;; seems to be a case thing + ;; '("begin" . "end")) + (t + ;; Just a nromal block + '("begin" . "end"))))) + (t nil))))) + +(defun idlwave-close-block () + "Terminate the current block with the correct END statement." + (interactive) + + ;; Start new line if we are not in a new line + (unless (save-excursion + (skip-chars-backward " \t") + (bolp)) + (let ((idlwave-show-block nil)) + (newline-and-indent))) + + ;; Check which end is needed and insert it. + (let ((case-fold-search t) end) + (save-excursion + (idlwave-beginning-of-statement) + (idlwave-block-jump-out -1 'nomark) + (if (setq end (idlwave-block-master)) + (setq end (cdr end)) + (error "Cannot close block"))) + (insert end) + (idlwave-newline))) + +(defun idlwave-surround (&optional before after escape-chars) + "Surround the character before point with blanks. +Optional arguments BEFORE and AFTER affect the behavior before and +after the previous character. See description of `idlwave-make-space'. + +The function does nothing if any of the following conditions is true: +- `idlwave-surround-by-blank' is nil +- the character before point is inside a string or comment + +When the character 2 positions before point is a member of +ESCAPE-CHARS, BEFORE is forced to nil." + + (if (and idlwave-surround-by-blank + (not (idlwave-quoted))) + (progn + (if (memq (char-after (- (point) 2)) escape-chars) + (setq before nil)) + (backward-char 1) + (save-restriction + (let ((here (point))) + (skip-chars-backward " \t") + (if (bolp) + ;; avoid clobbering indent + (progn + (move-to-column (idlwave-calculate-indent)) + (if (<= (point) here) + (narrow-to-region (point) here)) + (goto-char here))) + (idlwave-make-space before)) + (skip-chars-forward " \t")) + (forward-char 1) + (idlwave-make-space after) + ;; Check to see if the line should auto wrap + (if (and (equal (char-after (1- (point))) ? ) + (> (current-column) fill-column)) + (funcall auto-fill-function))))) + +(defun idlwave-make-space (n) + "Make space at point. +The space affected is all the spaces and tabs around point. +If n is non-nil then point is left abs(n) spaces from the beginning of +the contiguous space. +The amount of space at point is determined by N. +If the value of N is: +nil - do nothing. +c > 0 - exactly c spaces. +c < 0 - a minimum of -c spaces, i.e., do not change if there are + already -c spaces. +0 - no spaces." + (if (integerp n) + (let + ((start-col (progn (skip-chars-backward " \t") (current-column))) + (left (point)) + (end-col (progn (skip-chars-forward " \t") (current-column)))) + (delete-horizontal-space) + (cond + ((> n 0) + (idlwave-indent-to (+ start-col n)) + (goto-char (+ left n))) + ((< n 0) + (idlwave-indent-to end-col (- n)) + (goto-char (- left n))) + ;; n = 0, done + )))) + +(defun idlwave-newline () + "Inserts a newline and indents the current and previous line." + (interactive) + ;; + ;; Handle unterminated single and double quotes + ;; If not in a comment and in a string then insertion of a newline + ;; will mean unbalanced quotes. + ;; + (if (and (not (idlwave-in-comment)) (idlwave-in-quote)) + (progn (beep) + (message "Warning: unbalanced quotes?"))) + (newline) + ;; + ;; The current line is being split, the cursor should be at the + ;; beginning of the new line skipping the leading indentation. + ;; + ;; The reason we insert the new line before indenting is that the + ;; indenting could be confused by keywords (e.g. END) on the line + ;; after the split point. This prevents us from just using + ;; `indent-for-tab-command' followed by `newline-and-indent'. + ;; + (beginning-of-line 0) + (idlwave-indent-line) + (forward-line) + (idlwave-indent-line)) + +;; +;; Use global variable 'comment-column' to set parallel comment +;; +;; Modeled on lisp.el +;; Emacs Lisp and IDL (Wave CL) have identical comment syntax +(defun idlwave-comment-hook () + "Compute indent for the beginning of the IDL comment delimiter." + (if (or (looking-at idlwave-no-change-comment) + (if idlwave-begin-line-comment + (looking-at idlwave-begin-line-comment) + (looking-at "^;"))) + (current-column) + (if (looking-at idlwave-code-comment) + (if (save-excursion (skip-chars-backward " \t") (bolp)) + ;; On line by itself, indent as code + (let ((tem (idlwave-calculate-indent))) + (if (listp tem) (car tem) tem)) + ;; after code - do not change + (current-column)) + (skip-chars-backward " \t") + (max (if (bolp) 0 (1+ (current-column))) + comment-column)))) + +(defun idlwave-split-line () + "Continue line by breaking line at point and indent the lines. +For a code line insert continuation marker. If the line is a line comment +then the new line will contain a comment with the same indentation. +Splits strings with the IDL operator `+' if `idlwave-split-line-string' is +non-nil." + (interactive) + (let (beg) + (if (not (idlwave-in-comment)) + ;; For code line add continuation. + ;; Check if splitting a string. + (progn + (if (setq beg (idlwave-in-quote)) + (if idlwave-split-line-string + ;; Split the string. + (progn (insert (setq beg (char-after beg)) " + " + idlwave-continuation-char beg) + (backward-char 1)) + ;; Do not split the string. + (beep) + (message "Warning: continuation inside string!!") + (insert " " idlwave-continuation-char)) + ;; Not splitting a string. + (insert " " idlwave-continuation-char)) + (newline-and-indent)) + (indent-new-comment-line)) + ;; Indent previous line + (setq beg (- (point-max) (point))) + (forward-line -1) + (idlwave-indent-line) + (goto-char (- (point-max) beg)) + ;; Reindent new line + (idlwave-indent-line))) + +(defun idlwave-beginning-of-subprogram () + "Moves point to the beginning of the current program unit." + (interactive) + (idlwave-find-key idlwave-begin-unit-reg -1)) + +(defun idlwave-end-of-subprogram () + "Moves point to the start of the next program unit." + (interactive) + (idlwave-end-of-statement) + (idlwave-find-key idlwave-end-unit-reg 1)) + +(defun idlwave-mark-statement () + "Mark current IDL statement." + (interactive) + (idlwave-end-of-statement) + (let ((end (point))) + (idlwave-beginning-of-statement) + (idlwave-push-mark end nil t))) + +(defun idlwave-mark-block () + "Mark containing block." + (interactive) + (idlwave-end-of-statement) + (idlwave-backward-up-block -1) + (idlwave-end-of-statement) + (let ((end (point))) + (idlwave-backward-block) + (idlwave-beginning-of-statement) + (idlwave-push-mark end nil t))) + + +(defun idlwave-mark-subprogram () + "Put mark at beginning of program, point at end. +The marks are pushed." + (interactive) + (idlwave-end-of-statement) + (idlwave-beginning-of-subprogram) + (let ((beg (point))) + (idlwave-forward-block) + (idlwave-push-mark beg nil t)) + (exchange-point-and-mark)) + +(defun idlwave-backward-up-block (&optional arg) + "Move to beginning of enclosing block if prefix ARG >= 0. +If prefix ARG < 0 then move forward to enclosing block end." + (interactive "p") + (idlwave-block-jump-out (- arg) 'nomark)) + +(defun idlwave-beginning-of-block () + "Go to the beginning of the current block." + (interactive) + (idlwave-block-jump-out -1 'nomark) + (forward-word 1)) + +(defun idlwave-end-of-block () + "Go to the beginning of the current block." + (interactive) + (idlwave-block-jump-out 1 'nomark) + (backward-word 1)) + +(defun idlwave-forward-block () + "Move across next nested block." + (interactive) + (if (idlwave-down-block 1) + (idlwave-block-jump-out 1 'nomark))) + +(defun idlwave-backward-block () + "Move backward across previous nested block." + (interactive) + (if (idlwave-down-block -1) + (idlwave-block-jump-out -1 'nomark))) + +(defun idlwave-down-block (&optional arg) + "Go down a block. +With ARG: ARG >= 0 go forwards, ARG < 0 go backwards. +Returns non-nil if successfull." + (interactive "p") + (let (status) + (if (< arg 0) + ;; Backward + (let ((eos (save-excursion + (idlwave-block-jump-out -1 'nomark) + (point)))) + (if (setq status (idlwave-find-key + idlwave-end-block-reg -1 'nomark eos)) + (idlwave-beginning-of-statement) + (message "No nested block before beginning of containing block."))) + ;; Forward + (let ((eos (save-excursion + (idlwave-block-jump-out 1 'nomark) + (point)))) + (if (setq status (idlwave-find-key + idlwave-begin-block-reg 1 'nomark eos)) + (idlwave-end-of-statement) + (message "No nested block before end of containing block.")))) + status)) + +(defun idlwave-mark-doclib () + "Put point at beginning of doc library header, mark at end. +The marks are pushed." + (interactive) + (let (beg + (here (point))) + (goto-char (point-max)) + (if (re-search-backward idlwave-doclib-start nil t) + (progn + (setq beg (progn (beginning-of-line) (point))) + (if (re-search-forward idlwave-doclib-end nil t) + (progn + (forward-line 1) + (idlwave-push-mark beg nil t) + (message "Could not find end of doc library header."))) + (message "Could not find doc library header start.") + (goto-char here))))) + +(defvar idlwave-shell-prompt-pattern) +(defun idlwave-beginning-of-statement () + "Move to beginning of the current statement. +Skips back past statement continuations. +Point is placed at the beginning of the line whether or not this is an +actual statement." + (interactive) + (cond + ((eq major-mode 'idlwave-shell-mode) + (if (re-search-backward idlwave-shell-prompt-pattern nil t) + (goto-char (match-end 0)))) + (t + (if (save-excursion (forward-line -1) (idlwave-is-continuation-line)) + (idlwave-previous-statement) + (beginning-of-line))))) + +(defun idlwave-previous-statement () + "Moves point to beginning of the previous statement. +Returns t if the current line before moving is the beginning of +the first non-comment statement in the file, and nil otherwise." + (interactive) + (let (first-statement) + (if (not (= (forward-line -1) 0)) + ;; first line in file + t + ;; skip blank lines, label lines, include lines and line comments + (while (and + ;; The current statement is the first statement until we + ;; reach another statement. + (setq first-statement + (or + (looking-at idlwave-comment-line-start-skip) + (looking-at "[ \t]*$") + (looking-at (concat "[ \t]*" idlwave-label "[ \t]*$")) + (looking-at "^@"))) + (= (forward-line -1) 0))) + ;; skip continuation lines + (while (and + (save-excursion + (forward-line -1) + (idlwave-is-continuation-line)) + (= (forward-line -1) 0))) + first-statement))) + +;; FIXME: end-of-statement does not work correctly when comment lines +;; are inside the statement. It does work correctly for line-end +;; comments, though. +(defun idlwave-end-of-statement () + "Moves point to the end of the current IDL statement. +If not in a statement just moves to end of line. Returns position." + (interactive) + (while (and (idlwave-is-continuation-line) + (= (forward-line 1) 0))) + (end-of-line) + (point)) + +(defun idlwave-next-statement () + "Moves point to beginning of the next IDL statement. + Returns t if that statement is the last + non-comment IDL statement in the file, and nil otherwise." + (interactive) + (let (last-statement) + (idlwave-end-of-statement) + ;; skip blank lines, label lines, include lines and line comments + (while (and (= (forward-line 1) 0) + ;; The current statement is the last statement until + ;; we reach a new statement. + (setq last-statement + (or + (looking-at idlwave-comment-line-start-skip) + (looking-at "[ \t]*$") + (looking-at (concat "[ \t]*" idlwave-label "[ \t]*$")) + (looking-at "^@"))))) + last-statement)) + +(defun idlwave-skip-label () + "Skip label or case statement element. +Returns position after label. +If there is no label point is not moved and nil is returned." + ;; Just look for the first non quoted colon and check to see if it + ;; is inside a sexp. If is not in a sexp it must be part of a label + ;; or case statement element. + (let ((start (point)) + (end (idlwave-find-key ":" 1 'nomark + (save-excursion + (idlwave-end-of-statement) (point))))) + (if (and end + (= (nth 0 (parse-partial-sexp start end)) 0)) + (progn + (forward-char) + (point)) + (goto-char start) + nil))) + +(defun idlwave-start-of-substatement (&optional pre) + "Move to start of next IDL substatement after point. +Uses the type of the current IDL statement to determine if the next +statement is on a new line or is a subpart of the current statement. +Returns point at start of substatement modulo whitespace. +If optional argument is non-nil move to beginning of current +substatement. " + (let ((orig (point)) + (eos (idlwave-end-of-statement)) + (ifnest 0) + st nst last) + (idlwave-beginning-of-statement) + (idlwave-skip-label) + (setq last (point)) + ;; Continue looking for substatements until we are past orig + (while (and (<= (point) orig) (not (eobp))) + (setq last (point)) + (setq nst (nth 1 (cdr (setq st (car (idlwave-statement-type)))))) + (if (equal (car st) 'if) (setq ifnest (1+ ifnest))) + (cond ((and nst + (idlwave-find-key nst 1 'nomark eos)) + (goto-char (match-end 0))) + ((and (> ifnest 0) (idlwave-find-key "\\<else\\>" 1 'nomark eos)) + (setq ifnest (1- ifnest)) + (goto-char (match-end 0))) + (t (setq ifnest 0) + (idlwave-next-statement)))) + (if pre (goto-char last)) + (point))) + +(defun idlwave-statement-type () + "Return the type of the current IDL statement. +Uses `idlwave-statement-match' to return a cons of (type . point) with +point the ending position where the type was determined. Type is the +association from `idlwave-statement-match', i.e. the cons cell from the +list not just the type symbol. Returns nil if not an identifiable +statement." + (save-excursion + ;; Skip whitespace within a statement which is spaces, tabs, continuations + (while (looking-at "[ \t]*\\<\\$") + (forward-line 1)) + (skip-chars-forward " \t") + (let ((st idlwave-statement-match) + (case-fold-search t)) + (while (and (not (looking-at (nth 0 (cdr (car st))))) + (setq st (cdr st)))) + (if st + (append st (match-end 0)))))) + +(defun idlwave-expand-equal (&optional before after) + "Pad '=' with spaces. +Two cases: Assignment statement, and keyword assignment. +The case is determined using `idlwave-start-of-substatement' and +`idlwave-statement-type'. +The equal sign will be surrounded by BEFORE and AFTER blanks. +If `idlwave-pad-keyword' is non-nil then keyword +assignment is treated just like assignment statements. Otherwise, +spaces are removed for keyword assignment. +Limits in for loops are treated as keyword assignment. +See `idlwave-surround'. " + ;; Even though idlwave-surround checks `idlwave-surround-by-blank' this + ;; check saves the time of finding the statement type. + (if idlwave-surround-by-blank + (let ((st (save-excursion + (idlwave-start-of-substatement t) + (idlwave-statement-type)))) + (if (or + (and (equal (car (car st)) 'assign) + (equal (cdr st) (point))) + idlwave-pad-keyword) + ;; An assignment statement + (idlwave-surround before after) + (idlwave-surround 0 0))))) + +(defun idlwave-indent-and-action () + "Call `idlwave-indent-line' and do expand actions." + (interactive) + (idlwave-indent-line t) + ) + +(defun idlwave-indent-line (&optional expand) + "Indents current IDL line as code or as a comment. +The actions in `idlwave-indent-action-table' are performed. +If the optional argument EXPAND is non-nil then the actions in +`idlwave-indent-expand-table' are performed." + (interactive) + ;; Move point out of left margin. + (if (save-excursion + (skip-chars-backward " \t") + (bolp)) + (skip-chars-forward " \t")) + (let ((mloc (point-marker))) + (save-excursion + (beginning-of-line) + (if (looking-at idlwave-comment-line-start-skip) + ;; Indentation for a line comment + (progn + (skip-chars-forward " \t") + (idlwave-indent-left-margin (idlwave-comment-hook))) + ;; + ;; Code Line + ;; + ;; Before indenting, run action routines. + ;; + (if (and expand idlwave-do-actions) + (mapcar 'idlwave-do-action idlwave-indent-expand-table)) + ;; + (if idlwave-do-actions + (mapcar 'idlwave-do-action idlwave-indent-action-table)) + ;; + ;; No longer expand abbrevs on the line. The user can do this + ;; manually using expand-region-abbrevs. + ;; + ;; Indent for code line + ;; + (beginning-of-line) + (if (or + ;; a label line + (looking-at (concat "^" idlwave-label "[ \t]*$")) + ;; a batch command + (looking-at "^[ \t]*@")) + ;; leave flush left + nil + ;; indent the line + (idlwave-indent-left-margin (idlwave-calculate-indent))) + ;; Adjust parallel comment + (end-of-line) + (if (idlwave-in-comment) + (indent-for-comment)))) + (goto-char mloc) + ;; Get rid of marker + (set-marker mloc nil) + )) + +(defun idlwave-do-action (action) + "Perform an action repeatedly on a line. +ACTION is a list (REG . FUNC). REG is a regular expression. FUNC is +either a function name to be called with `funcall' or a list to be +evaluated with `eval'. The action performed by FUNC should leave point +after the match for REG - otherwise an infinite loop may be entered." + (let ((action-key (car action)) + (action-routine (cdr action))) + (beginning-of-line) + (while (idlwave-look-at action-key) + (if (listp action-routine) + (eval action-routine) + (funcall action-routine))))) + +(defun idlwave-indent-to (col &optional min) + "Indent from point with spaces until column COL. +Inserts space before markers at point." + (if (not min) (setq min 0)) + (insert-before-markers + (make-string (max min (- col (current-column))) ? ))) + +(defun idlwave-indent-left-margin (col) + "Indent the current line to column COL. +Indents such that first non-whitespace character is at column COL +Inserts spaces before markers at point." + (save-excursion + (beginning-of-line) + (delete-horizontal-space) + (idlwave-indent-to col))) + +(defun idlwave-indent-subprogram () + "Indents program unit which contains point." + (interactive) + (save-excursion + (idlwave-end-of-statement) + (idlwave-beginning-of-subprogram) + (let ((beg (point))) + (idlwave-forward-block) + (message "Indenting subprogram...") + (indent-region beg (point) nil)) + (message "Indenting subprogram...done."))) + +(defun idlwave-calculate-indent () + "Return appropriate indentation for current line as IDL code." + (save-excursion + (beginning-of-line) + (cond + ;; Check for beginning of unit - main (beginning of buffer), pro, or + ;; function + ((idlwave-look-at idlwave-begin-unit-reg) + 0) + ;; Check for continuation line + ((save-excursion + (and (= (forward-line -1) 0) + (idlwave-is-continuation-line))) + (idlwave-calculate-cont-indent)) + ;; calculate indent based on previous and current statements + (t (let ((the-indent + ;; calculate indent based on previous statement + (save-excursion + (cond + ((idlwave-previous-statement) + 0) + ;; Main block + ((idlwave-look-at idlwave-begin-unit-reg t) + (+ (idlwave-current-statement-indent) + idlwave-main-block-indent)) + ;; Begin block + ((idlwave-look-at idlwave-begin-block-reg t) + (+ (idlwave-current-statement-indent) + idlwave-block-indent)) + ((idlwave-look-at idlwave-end-block-reg t) + (- (idlwave-current-statement-indent) + idlwave-end-offset + idlwave-block-indent)) + ((idlwave-current-statement-indent)))))) + ;; adjust the indentation based on the current statement + (cond + ;; End block + ((idlwave-look-at idlwave-end-block-reg t) + (+ the-indent idlwave-end-offset)) + (the-indent))))))) + +;; +;; Parenthesses balacing/indent +;; + +(defun idlwave-calculate-cont-indent () + "Calculates the IDL continuation indent column from the previous statement. +Note that here previous statement means the beginning of the current +statement if this statement is a continuation of the previous line. +Intervening comments or comments within the previous statement can +screw things up if the comments contain parentheses characters." + (save-excursion + (let* (open + (case-fold-search t) + (end-reg (progn (beginning-of-line) (point))) + (close-exp (progn (skip-chars-forward " \t") (looking-at "\\s)"))) + (beg-reg (progn (idlwave-previous-statement) (point)))) + ;; + ;; If PRO or FUNCTION declaration indent after name, and first comma. + ;; + (if (idlwave-look-at "\\<\\(pro\\|function\\)\\>") + (progn + (forward-sexp 1) + (if (looking-at "[ \t]*,[ \t]*") + (goto-char (match-end 0))) + (current-column)) + ;; + ;; Not a PRO or FUNCTION + ;; + ;; Look for innermost unmatched open paren + ;; + (if (setq open (car (cdr (parse-partial-sexp beg-reg end-reg)))) + ;; Found innermost open paren. + (progn + (goto-char open) + ;; Line up with next word unless this is a closing paren. + (cond + ;; This is a closed paren - line up under open paren. + (close-exp + (current-column)) + ;; Empty - just add regular indent. Take into account + ;; the forward-char + ((progn + ;; Skip paren + (forward-char 1) + (looking-at "[ \t$]*$")) + (+ (current-column) idlwave-continuation-indent -1)) + ;; Line up with first word + ((progn + (skip-chars-forward " \t") + (current-column))))) + ;; No unmatched open paren. Just a simple continuation. + (goto-char beg-reg) + (+ (idlwave-current-indent) + ;; Make adjustments based on current line + (cond + ;; Else statement + ((progn + (goto-char end-reg) + (skip-chars-forward " \t") + (looking-at "else")) + 0) + ;; Ordinary continuation + (idlwave-continuation-indent)))))))) + +(defun idlwave-find-key (key-reg &optional dir nomark limit) + "Move in direction of the optional second argument DIR to the +next keyword not contained in a comment or string and occurring before +optional fourth argument LIMIT. DIR defaults to forward direction. If +DIR is negative the search is backwards, otherwise, it is +forward. LIMIT defaults to the beginning or end of the buffer +according to the direction of the search. The keyword is given by the +regular expression argument KEY-REG. The search is case insensitive. +Returns position if successful and nil otherwise. If found +`push-mark' is executed unless the optional third argument NOMARK is +non-nil. If found, the point is left at the keyword beginning." + (or dir (setq dir 0)) + (or limit (setq limit (cond ((>= dir 0) (point-max)) ((point-min))))) + (let (found + (old-syntax-table (syntax-table)) + (case-fold-search t)) + (unwind-protect + (save-excursion + (set-syntax-table idlwave-find-symbol-syntax-table) + (if (>= dir 0) + (while (and (setq found (and + (re-search-forward key-reg limit t) + (match-beginning 0))) + (idlwave-quoted) + (not (eobp)))) + (while (and (setq found (and + (re-search-backward key-reg limit t) + (match-beginning 0))) + (idlwave-quoted) + (not (bobp)))))) + (set-syntax-table old-syntax-table)) + (if found (progn + (if (not nomark) (push-mark)) + (goto-char found))))) + +(defun idlwave-block-jump-out (&optional dir nomark) + "When optional argument DIR is non-negative, move forward to end of +current block using the `idlwave-begin-block-reg' and `idlwave-end-block-reg' +regular expressions. When DIR is negative, move backwards to block beginning. +Recursively calls itself to skip over nested blocks. DIR defaults to +forward. Calls `push-mark' unless the optional argument NOMARK is +non-nil. Movement is limited by the start of program units because of +possibility of unbalanced blocks." + (interactive "P") + (or dir (setq dir 0)) + (let* ((here (point)) + (case-fold-search t) + (limit (if (>= dir 0) (point-max) (point-min))) + (block-limit (if (>= dir 0) + idlwave-begin-block-reg + idlwave-end-block-reg)) + found + (block-reg (concat idlwave-begin-block-reg "\\|" + idlwave-end-block-reg)) + (unit-limit (or (save-excursion + (if (< dir 0) + (idlwave-find-key + idlwave-begin-unit-reg dir t limit) + (end-of-line) + (idlwave-find-key + idlwave-end-unit-reg dir t limit))) + limit))) + (if (>= dir 0) (end-of-line)) ;Make sure we are in current block + (if (setq found (idlwave-find-key block-reg dir t unit-limit)) + (while (and found (looking-at block-limit)) + (if (>= dir 0) (forward-word 1)) + (idlwave-block-jump-out dir t) + (setq found (idlwave-find-key block-reg dir t unit-limit)))) + (if (not nomark) (push-mark here)) + (if (not found) (goto-char unit-limit) + (if (>= dir 0) (forward-word 1))))) + +(defun idlwave-current-statement-indent () + "Return indentation of the current statement. +If in a statement, moves to beginning of statement before finding indent." + (idlwave-beginning-of-statement) + (idlwave-current-indent)) + +(defun idlwave-current-indent () + "Return the column of the indentation of the current line. +Skips any whitespace. Returns 0 if the end-of-line follows the whitespace." + (save-excursion + (beginning-of-line) + (skip-chars-forward " \t") + ;; if we are at the end of blank line return 0 + (cond ((eolp) 0) + ((current-column))))) + +(defun idlwave-is-continuation-line () + "Tests if current line is continuation line." + (save-excursion + (idlwave-look-at "\\<\\$"))) + +(defun idlwave-is-comment-line () + (save-excursion + (beginning-of-line 1) + (looking-at "[ \t]*;"))) + +(defun idlwave-look-at (regexp &optional cont beg) + "Searches current line from current point for the regular expression +REGEXP. If optional argument CONT is non-nil, searches to the end of +the current statement. If optional arg BEG is non-nil, search starts +from the beginning of the current statement. Ignores matches that end +in a comment or inside a string expression. Returns point if +successful, nil otherwise. This function produces unexpected results +if REGEXP contains quotes or a comment delimiter. The search is case +insensitive. If successful leaves point after the match, otherwise, +does not move point." + (let ((here (point)) + (old-syntax-table (syntax-table)) + (case-fold-search t) + eos + found) + (unwind-protect + (progn + (set-syntax-table idlwave-find-symbol-syntax-table) + (setq eos + (if cont + (save-excursion (idlwave-end-of-statement) (point)) + (save-excursion (end-of-line) (point)))) + (if beg (idlwave-beginning-of-statement)) + (while (and (setq found (re-search-forward regexp eos t)) + (idlwave-quoted)))) + (set-syntax-table old-syntax-table)) + (if (not found) (goto-char here)) + found)) + +(defun idlwave-fill-paragraph (&optional nohang) + "Fills paragraphs in comments. +A paragraph is made up of all contiguous lines having the same comment +leader (the leading whitespace before the comment delimiter and the +comment delimiter). In addition, paragraphs are separated by blank +line comments. The indentation is given by the hanging indent of the +first line, otherwise by the minimum indentation of the lines after +the first line. The indentation of the first line does not change. +Does not effect code lines. Does not fill comments on the same line +with code. The hanging indent is given by the end of the first match +matching `idlwave-hang-indent-regexp' on the paragraph's first line . If the +optional argument NOHANG is non-nil then the hanging indent is +ignored." + (interactive "P") + ;; check if this is a line comment + (if (save-excursion + (beginning-of-line) + (skip-chars-forward " \t") + (looking-at comment-start)) + (let + ((indent 999) + pre here diff fill-prefix-reg bcl first-indent + hang start end) + ;; Change tabs to spaces in the surrounding paragraph. + ;; The surrounding paragraph will be the largest containing block of + ;; contiguous line comments. Thus, we may be changing tabs in + ;; a much larger area than is needed, but this is the easiest + ;; brute force way to do it. + ;; + ;; This has the undesirable side effect of replacing the tabs + ;; permanently without the user's request or knowledge. + (save-excursion + (backward-paragraph) + (setq start (point))) + (save-excursion + (forward-paragraph) + (setq end (point))) + (untabify start end) + ;; + (setq here (point)) + (beginning-of-line) + (setq bcl (point)) + (re-search-forward + (concat "^[ \t]*" comment-start "+") + (save-excursion (end-of-line) (point)) + t) + ;; Get the comment leader on the line and its length + (setq pre (current-column)) + ;; the comment leader is the indentation plus exactly the + ;; number of consecutive ";". + (setq fill-prefix-reg + (concat + (setq fill-prefix + (regexp-quote + (buffer-substring (save-excursion + (beginning-of-line) (point)) + (point)))) + "[^;]")) + + ;; Mark the beginning and end of the paragraph + (goto-char bcl) + (while (and (looking-at fill-prefix-reg) + (not (looking-at paragraph-separate)) + (not (bobp))) + (forward-line -1)) + ;; Move to first line of paragraph + (if (/= (point) bcl) + (forward-line 1)) + (setq start (point)) + (goto-char bcl) + (while (and (looking-at fill-prefix-reg) + (not (looking-at paragraph-separate)) + (not (eobp))) + (forward-line 1)) + (beginning-of-line) + (if (or (not (looking-at fill-prefix-reg)) + (looking-at paragraph-separate)) + (forward-line -1)) + (end-of-line) + ;; if at end of buffer add a newline (need this because + ;; fill-region needs END to be at the beginning of line after + ;; the paragraph or it will add a line). + (if (eobp) + (progn (insert ?\n) (backward-char 1))) + ;; Set END to the beginning of line after the paragraph + ;; END is calculated as distance from end of buffer + (setq end (- (point-max) (point) 1)) + ;; + ;; Calculate the indentation for the paragraph. + ;; + ;; In the following while statements, after one iteration + ;; point will be at the beginning of a line in which case + ;; the while will not be executed for the + ;; the first paragraph line and thus will not affect the + ;; indentation. + ;; + ;; First check to see if indentation is based on hanging indent. + (if (and (not nohang) idlwave-hanging-indent + (setq hang + (save-excursion + (goto-char start) + (idlwave-calc-hanging-indent)))) + ;; Adjust lines of paragraph by inserting spaces so that + ;; each line's indent is at least as great as the hanging + ;; indent. This is needed for fill-paragraph to work with + ;; a fill-prefix. + (progn + (setq indent hang) + (beginning-of-line) + (while (> (point) start) + (re-search-forward comment-start-skip + (save-excursion (end-of-line) (point)) + t) + (if (> (setq diff (- indent (current-column))) 0) + (progn + (if (>= here (point)) + ;; adjust the original location for the + ;; inserted text. + (setq here (+ here diff))) + (insert (make-string diff ? )))) + (forward-line -1)) + ) + + ;; No hang. Instead find minimum indentation of paragraph + ;; after first line. + ;; For the following while statement, since START is at the + ;; beginning of line and END is at the the end of line + ;; point is greater than START at least once (which would + ;; be the case for a single line paragraph). + (while (> (point) start) + (beginning-of-line) + (setq indent + (min indent + (progn + (re-search-forward + comment-start-skip + (save-excursion (end-of-line) (point)) + t) + (current-column)))) + (forward-line -1)) + ) + (setq fill-prefix (concat fill-prefix + (make-string (- indent pre) + ? ))) + ;; first-line indent + (setq first-indent + (max + (progn + (re-search-forward + comment-start-skip + (save-excursion (end-of-line) (point)) + t) + (current-column)) + indent)) + + ;; try to keep point at its original place + (goto-char here) + + ;; In place of the more modern fill-region-as-paragraph, a hack + ;; to keep whitespace untouched on the first line within the + ;; indent length and to preserve any indent on the first line + ;; (first indent). + (save-excursion + (setq diff + (buffer-substring start (+ start first-indent -1))) + (subst-char-in-region start (+ start first-indent -1) ? ?~ nil) + (fill-region-as-paragraph + start + (- (point-max) end) + (current-justification) + nil) + (delete-region start (+ start first-indent -1)) + (goto-char start) + (insert diff)) + ;; When we want the point at the beginning of the comment + ;; body fill-region will put it at the beginning of the line. + (if (bolp) (skip-chars-forward (concat " \t" comment-start))) + (setq fill-prefix nil)))) + +(defun idlwave-calc-hanging-indent () + "Calculate the position of the hanging indent for the comment +paragraph. The hanging indent position is given by the first match +with the `idlwave-hang-indent-regexp'. If `idlwave-use-last-hang-indent' is +non-nil then use last occurrence matching `idlwave-hang-indent-regexp' on +the line. +If not found returns nil." + (if idlwave-use-last-hang-indent + (save-excursion + (end-of-line) + (if (re-search-backward + idlwave-hang-indent-regexp + (save-excursion (beginning-of-line) (point)) + t) + (+ (current-column) (length idlwave-hang-indent-regexp)))) + (save-excursion + (beginning-of-line) + (if (re-search-forward + idlwave-hang-indent-regexp + (save-excursion (end-of-line) (point)) + t) + (current-column))))) + +(defun idlwave-auto-fill () + "Called to break lines in auto fill mode. +Only fills comment lines if `idlwave-fill-comment-line-only' is non-nil. +Places a continuation character at the end of the line +if not in a comment. Splits strings with IDL concatenation operator +`+' if `idlwave-auto-fill-split-string is non-nil." + (if (<= (current-column) fill-column) + nil ; do not to fill + (if (or (not idlwave-fill-comment-line-only) + (save-excursion + ;; Check for comment line + (beginning-of-line) + (looking-at idlwave-comment-line-start-skip))) + (let (beg) + (idlwave-indent-line) + ;; Prevent actions do-auto-fill which calls indent-line-function. + (let (idlwave-do-actions + (paragraph-start ".") + (paragraph-separate ".")) + (do-auto-fill)) + (save-excursion + (end-of-line 0) + ;; Indent the split line + (idlwave-indent-line) + ) + (if (save-excursion + (beginning-of-line) + (looking-at idlwave-comment-line-start-skip)) + ;; A continued line comment + ;; We treat continued line comments as part of a comment + ;; paragraph. So we check for a hanging indent. + (if idlwave-hanging-indent + (let ((here (- (point-max) (point))) + (indent + (save-excursion + (forward-line -1) + (idlwave-calc-hanging-indent)))) + (if indent + (progn + ;; Remove whitespace between comment delimiter and + ;; text, insert spaces for appropriate indentation. + (beginning-of-line) + (re-search-forward + comment-start-skip + (save-excursion (end-of-line) (point)) t) + (delete-horizontal-space) + (idlwave-indent-to indent) + (goto-char (- (point-max) here))) + ))) + ;; Split code or comment? + (if (save-excursion + (end-of-line 0) + (idlwave-in-comment)) + ;; Splitting a non-line comment. + ;; Insert the comment delimiter from split line + (progn + (save-excursion + (beginning-of-line) + (skip-chars-forward " \t") + ;; Insert blank to keep off beginning of line + (insert " " + (save-excursion + (forward-line -1) + (buffer-substring (idlwave-goto-comment) + (progn + (skip-chars-forward "; ") + (point)))))) + (idlwave-indent-line)) + ;; Split code line - add continuation character + (save-excursion + (end-of-line 0) + ;; Check to see if we split a string + (if (and (setq beg (idlwave-in-quote)) + idlwave-auto-fill-split-string) + ;; Split the string and concatenate. + ;; The first extra space is for the space + ;; the line was split. That space was removed. + (insert " " (char-after beg) " +")) + (insert " $")) + (if beg + (if idlwave-auto-fill-split-string + ;; Make the second part of continued string + (save-excursion + (beginning-of-line) + (skip-chars-forward " \t") + (insert (char-after beg))) + ;; Warning + (beep) + (message "Warning: continuation inside a string."))) + ;; Although do-auto-fill (via indent-new-comment-line) calls + ;; idlwave-indent-line for the new line, re-indent again + ;; because of the addition of the continuation character. + (idlwave-indent-line)) + ))))) + +(defun idlwave-auto-fill-mode (arg) + "Toggle auto-fill mode for IDL mode. +With arg, turn auto-fill mode on if arg is positive. +In auto-fill mode, inserting a space at a column beyond `fill-column' +automatically breaks the line at a previous space." + (interactive "P") + (prog1 (set idlwave-fill-function + (if (if (null arg) + (not (symbol-value idlwave-fill-function)) + (> (prefix-numeric-value arg) 0)) + 'idlwave-auto-fill + nil)) + ;; update mode-line + (set-buffer-modified-p (buffer-modified-p)))) + +(defun idlwave-doc-header (&optional nomark ) + "Insert a documentation header at the beginning of the unit. +Inserts the value of the variable idlwave-file-header. Sets mark before +moving to do insertion unless the optional prefix argument NOMARK +is non-nil." + (interactive "P") + (or nomark (push-mark)) + ;; make sure we catch the current line if it begins the unit + (end-of-line) + (idlwave-beginning-of-subprogram) + (beginning-of-line) + ;; skip function or procedure line + (if (idlwave-look-at "\\<\\(pro\\|function\\)\\>") + (progn + (idlwave-end-of-statement) + (if (> (forward-line 1) 0) (insert "\n")))) + (if idlwave-file-header + (cond ((car idlwave-file-header) + (insert-file (car idlwave-file-header))) + ((stringp (car (cdr idlwave-file-header))) + (insert (car (cdr idlwave-file-header))))))) + + +(defun idlwave-default-insert-timestamp () + "Default timestamp insertion function" + (insert (current-time-string)) + (insert ", " (user-full-name)) + (insert " <" (user-login-name) "@" (system-name) ">") + ;; Remove extra spaces from line + (idlwave-fill-paragraph) + ;; Insert a blank line comment to separate from the date entry - + ;; will keep the entry from flowing onto date line if re-filled. + (insert "\n;\n;\t\t")) + +(defun idlwave-doc-modification () + "Insert a brief modification log at the beginning of the current program. +Looks for an occurrence of the value of user variable +`idlwave-doc-modifications-keyword' if non-nil. Inserts time and user name +and places the point for the user to add a log. Before moving, saves +location on mark ring so that the user can return to previous point." + (interactive) + (push-mark) + ;; make sure we catch the current line if it begins the unit + (end-of-line) + (idlwave-beginning-of-subprogram) + (let ((pro (idlwave-look-at "\\<\\(function\\|pro\\)\\>")) + (case-fold-search nil)) + (if (re-search-forward + (concat idlwave-doc-modifications-keyword ":") + ;; set search limit at next unit beginning + (save-excursion (idlwave-end-of-subprogram) (point)) + t) + (end-of-line) + ;; keyword not present, insert keyword + (if pro (idlwave-next-statement)) ; skip past pro or function statement + (beginning-of-line) + (insert "\n" comment-start "\n") + (forward-line -2) + (insert comment-start " " idlwave-doc-modifications-keyword ":"))) + (idlwave-newline) + (beginning-of-line) + (insert ";\n;\t") + (run-hooks 'idlwave-timestamp-hook)) + +;;; CJC 3/16/93 +;;; Interface to expand-region-abbrevs which did not work when the +;;; abbrev hook associated with an abbrev moves point backwards +;;; after abbrev expansion, e.g., as with the abbrev '.n'. +;;; The original would enter an infinite loop in attempting to expand +;;; .n (it would continually expand and unexpand the abbrev without expanding +;;; because the point would keep going back to the beginning of the +;;; abbrev instead of to the end of the abbrev). We now keep the +;;; abbrev hook from moving backwards. +;;; +(defun idlwave-expand-region-abbrevs (start end) + "Expand each abbrev occurrence in the region. +Calling from a program, arguments are START END." + (interactive "r") + (save-excursion + (goto-char (min start end)) + (let ((idlwave-show-block nil) ;Do not blink + (idlwave-abbrev-move nil)) ;Do not move + (expand-region-abbrevs start end 'noquery)))) + +(defun idlwave-quoted () + "Returns t if point is in a comment or quoted string. +nil otherwise." + (or (idlwave-in-comment) (idlwave-in-quote))) + +(defun idlwave-in-quote () + "Returns location of the opening quote +if point is in a IDL string constant, nil otherwise. +Ignores comment delimiters on the current line. +Properly handles nested quotation marks and octal +constants - a double quote followed by an octal digit." +;;; Treat an octal inside an apostrophe to be a normal string. Treat a +;;; double quote followed by an octal digit to be an octal constant +;;; rather than a string. Therefore, there is no terminating double +;;; quote. + (save-excursion + ;; Because single and double quotes can quote each other we must + ;; search for the string start from the beginning of line. + (let* ((start (point)) + (eol (progn (end-of-line) (point))) + (bq (progn (beginning-of-line) (point))) + (endq (point)) + (data (match-data)) + delim + found) + (while (< endq start) + ;; Find string start + ;; Don't find an octal constant beginning with a double quote + (if (re-search-forward "\"[^0-7]\\|'\\|\"$" eol 'lim) + ;; Find the string end. + ;; In IDL, two consecutive delimiters after the start of a + ;; string act as an + ;; escape for the delimiter in the string. + ;; Two consecutive delimiters alone (i.e., not after the + ;; start of a string) is the the null string. + (progn + ;; Move to position after quote + (goto-char (1+ (match-beginning 0))) + (setq bq (1- (point))) + ;; Get the string delimiter + (setq delim (char-to-string (preceding-char))) + ;; Check for null string + (if (looking-at delim) + (progn (setq endq (point)) (forward-char 1)) + ;; Look for next unpaired delimiter + (setq found (search-forward delim eol 'lim)) + (while (looking-at delim) + (forward-char 1) + (setq found (search-forward delim eol 'lim))) + (if found + (setq endq (- (point) 1)) + (setq endq (point))) + )) + (progn (setq bq (point)) (setq endq (point))))) + (store-match-data data) + ;; return string beginning position or nil + (if (> start bq) bq)))) + +;; Statement templates + +;; Replace these with a general template function, something like +;; expand.el (I think there was also something with a name similar to +;; dmacro.el) + +(defun idlwave-template (s1 s2 &optional prompt noindent) + "Build a template with optional prompt expression. + +Opens a line if point is not followed by a newline modulo intervening +whitespace. S1 and S2 are strings. S1 is inserted at point followed +by S2. Point is inserted between S1 and S2. If optional argument +PROMPT is a string then it is displayed as a message in the +minibuffer. The PROMPT serves as a reminder to the user of an +expression to enter. + +The lines containing S1 and S2 are reindented using `indent-region' +unless the optional second argument NOINDENT is non-nil." + (let ((beg (save-excursion (beginning-of-line) (point))) + end) + (if (not (looking-at "\\s-*\n")) + (open-line 1)) + (insert s1) + (save-excursion + (insert s2) + (setq end (point))) + (if (not noindent) + (indent-region beg end nil)) + (if (stringp prompt) + (message prompt)))) + +(defun idlwave-elif () + "Build skeleton IDL if-else block." + (interactive) + (idlwave-template "if" + " then begin\n\nendif else begin\n\nendelse" + "Condition expression")) + +(defun idlwave-case () + "Build skeleton IDL case statement." + (interactive) + (idlwave-template "case" " of\n\nendcase" "Selector expression")) + +(defun idlwave-for () + "Build skeleton for loop statment." + (interactive) + (idlwave-template "for" " do begin\n\nendfor" "Loop expression")) + +(defun idlwave-if () + "Build skeleton for loop statment." + (interactive) + (idlwave-template "if" " then begin\n\nendif" "Scalar logical expression")) + +(defun idlwave-procedure () + (interactive) + (idlwave-template "pro" "\n\nreturn\nend" "Procedure name")) + +(defun idlwave-function () + (interactive) + (idlwave-template "function" "\n\nreturn\nend" "Function name")) + +(defun idlwave-repeat () + (interactive) + (idlwave-template "repeat begin\n\nendrep until" "" "Exit condition")) + +(defun idlwave-while () + (interactive) + (idlwave-template "while" " do begin\n\nendwhile" "Entry condition")) + +(defun idlwave-split-string (string &optional pattern) + "Return a list of substrings of STRING which are separated by PATTERN. +If PATTERN is omitted, it defaults to \"[ \\f\\t\\n\\r\\v]+\"." + (or pattern + (setq pattern "[ \f\t\n\r\v]+")) + (let (parts (start 0)) + (while (string-match pattern string start) + (setq parts (cons (substring string start (match-beginning 0)) parts) + start (match-end 0))) + (nreverse (cons (substring string start) parts)))) + +(defun idlwave-replace-string (string replace_string replace_with) + (let* ((start 0) + (last (length string)) + (ret_string "") + end) + (while (setq end (string-match replace_string string start)) + (setq ret_string + (concat ret_string (substring string start end) replace_with)) + (setq start (match-end 0))) + (setq ret_string (concat ret_string (substring string start last))))) + +(defun idlwave-get-buffer-visiting (file) + ;; Return the buffer currently visiting FILE + (cond + ((boundp 'find-file-compare-truenames) ; XEmacs + (let ((find-file-compare-truenames t)) + (get-file-buffer file))) + ((fboundp 'find-buffer-visiting) ; Emacs + (find-buffer-visiting file)) + (t (error "This should not happen (idlwave-get-buffer-visiting)")))) + +(defun idlwave-find-file-noselect (file) + ;; Return a buffer visiting file. + (or (idlwave-get-buffer-visiting file) + (find-file-noselect file))) + +(defvar idlwave-scanned-lib-directories) +(defun idlwave-find-lib-file-noselet (file) + ;; Find FILE on the scanned lib path and return a buffer visiting it + (let* ((dirs idlwave-scanned-lib-directories) + dir efile) + (catch 'exit + (while (setq dir (pop dirs)) + (if (file-regular-p + (setq efile (expand-file-name file dir))) + (throw 'exit (idlwave-find-file-noselect efile))))))) + +(defun idlwave-make-tags () + "Creates the IDL tags file IDLTAGS in the current directory from +the list of directories specified in the minibuffer. Directories may be +for example: . /usr/local/rsi/idl/lib. All the subdirectories of the +specified top directories are searched if the directory name is prefixed +by @. Specify @ directories with care, it may take a long, long time if +you specify /." + (interactive) + (let (directory directories cmd append status numdirs dir getsubdirs + buffer save_buffer files numfiles item errbuf) + + ;; + ;; Read list of directories + (setq directory (read-string "Tag Directories: " ".")) + (setq directories (idlwave-split-string directory "[ \t]+")) + ;; + ;; Set etags command, vars + (setq cmd "etags --output=IDLTAGS --language=none --regex='/[ +\\t]*[pP][Rr][Oo][ \\t]+\\([^ \\t,]+\\)/' --regex='/[ +\\t]*[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn][ \\t]+\\([^ \\t,]+\\)/' ") + (setq append " ") + (setq status 0) + ;; + ;; For each directory + (setq numdirs 0) + (setq dir (nth numdirs directories)) + (while (and dir) + ;; + ;; Find the subdirectories + (if (string-match "^[@]\\(.+\\)$" dir) + (setq getsubdirs t) (setq getsubdirs nil)) + (if (and getsubdirs) (setq dir (substring dir 1 (length dir)))) + (setq dir (expand-file-name dir)) + (if (file-directory-p dir) + (progn + (if (and getsubdirs) + (progn + (setq buffer (get-buffer-create "*idltags*")) + (call-process "sh" nil buffer nil "-c" + (concat "find " dir " -type d -print")) + (setq save_buffer (current-buffer)) + (set-buffer buffer) + (setq files (idlwave-split-string + (idlwave-replace-string + (buffer-substring 1 (point-max)) + "\n" "/*.pro ") + "[ \t]+")) + (set-buffer save_buffer) + (kill-buffer buffer)) + (setq files (list (concat dir "/*.pro")))) + ;; + ;; For each subdirectory + (setq numfiles 0) + (setq item (nth numfiles files)) + (while (and item) + ;; + ;; Call etags + (if (not (string-match "^[ \\t]*$" item)) + (progn + (message (concat "Tagging " item "...")) + (setq errbuf (get-buffer-create "*idltags-error*")) + (setq status (+ status + (call-process "sh" nil errbuf nil "-c" + (concat cmd append item)))) + ;; + ;; Append additional tags + (setq append " --append ") + (setq numfiles (1+ numfiles)) + (setq item (nth numfiles files))) + (progn + (setq numfiles (1+ numfiles)) + (setq item (nth numfiles files)) + ))) + + (setq numdirs (1+ numdirs)) + (setq dir (nth numdirs directories))) + (progn + (setq numdirs (1+ numdirs)) + (setq dir (nth numdirs directories))))) + + (setq errbuf (get-buffer-create "*idltags-error*")) + (if (= status 0) + (kill-buffer errbuf)) + (message "") + )) + +(defun idlwave-toggle-comment-region (beg end &optional n) + "Comment the lines in the region if the first non-blank line is +commented, and conversely, uncomment region. If optional prefix arg +N is non-nil, then for N positive, add N comment delimiters or for N +negative, remove N comment delimiters. +Uses `comment-region' which does not place comment delimiters on +blank lines." + (interactive "r\nP") + (if n + (comment-region beg end (prefix-numeric-value n)) + (save-excursion + (goto-char beg) + (beginning-of-line) + ;; skip blank lines + (skip-chars-forward " \t\n") + (if (looking-at (concat "[ \t]*\\(" comment-start "+\\)")) + (comment-region beg end + (- (length (buffer-substring + (match-beginning 1) + (match-end 1))))) + (comment-region beg end))))) + + +;; ---------------------------------------------------------------------------- +;; ---------------------------------------------------------------------------- +;; ---------------------------------------------------------------------------- +;; ---------------------------------------------------------------------------- +;; +;; Completion and Routine Info +;; + +;; String "intern" functions + +;; For the completion and routine info function, we want to normalize +;; the case of procedure names etc. We do this by "interning" these +;; string is a hand-crafted way. Hashes are used to map the downcase +;; version of the strings to the cased versions. Since these cased +;; versions are really lisp objects, we can use `eq' to search, which +;; is a large performance boost. +;; All new strings need to be "sinterned". We do this as early as +;; possible after getting these strings from completion or buffer +;; substrings. So most of the code can simply assume to deal with +;; "sinterned" strings. The only exception is that the functions +;; which scan whole buffers for routine information do not intern the +;; grabbed strings. This is only done afterwards. Therefore in these +;; functions it is *not* save to assume the strings can be compared +;; with `eq' and be fed into the routine assq functions. + +;; Here we define the hashing functions. + +;; The variables which hold the hashes. +(defvar idlwave-sint-routines '(nil)) +(defvar idlwave-sint-keywords '(nil)) +(defvar idlwave-sint-methods '(nil)) +(defvar idlwave-sint-classes '(nil)) +(defvar idlwave-sint-files '(nil)) + +(defun idlwave-reset-sintern (&optional what) + "Reset all sintern hashes." + ;; Make sure the hash functions are accessible. + (if (or (not (fboundp 'gethash)) + (not (fboundp 'puthash))) + (progn + (require 'cl) + (or (fboundp 'puthash) + (defalias 'puthash 'cl-puthash)))) + (let ((entries '((idlwave-sint-routines 1000 10) + (idlwave-sint-keywords 1000 10) + (idlwave-sint-methods 100 10) + (idlwave-sint-classes 10 10)))) + + ;; Make sure these are lists + (loop for entry in entries + for var = (car entry) + do (if (not (consp (symbol-value var))) (set var (list nil)))) + + (when (or (eq what t) (eq what 'syslib) + (null (cdr idlwave-sint-routines))) + ;; Reset the system & library hash + (loop for entry in entries + for var = (car entry) for size = (nth 1 entry) + do (setcdr (symbol-value var) + (make-hash-table ':size size ':test 'equal))) + (setq idlwave-sint-files nil)) + + (when (or (eq what t) (eq what 'bufsh) + (null (car idlwave-sint-routines))) + ;; Reset the buffer & shell hash + (loop for entry in entries + for var = (car entry) for size = (nth 1 entry) + do (setcar (symbol-value var) + (make-hash-table ':size size ':test 'equal)))))) + +(defun idlwave-sintern-routine-or-method (name &optional class set) + (if class + (idlwave-sintern-method name set) + (idlwave-sintern-routine name set))) + +(defun idlwave-sintern (stype &rest args) + (apply (intern (concat "idlwave-sintern-" (symbol-name stype))) args)) + +;;(defmacro idlwave-sintern (type var) +;; `(cond ((not (stringp name)) name) +;; ((gethash (downcase name) (cdr ,var))) +;; ((gethash (downcase name) (car ,var))) +;; (set (idlwave-sintern-set name ,type ,var set)) +;; (name))) + +(defun idlwave-sintern-routine (name &optional set) + (cond ((not (stringp name)) name) + ((gethash (downcase name) (cdr idlwave-sint-routines))) + ((gethash (downcase name) (car idlwave-sint-routines))) + (set (idlwave-sintern-set name 'routine idlwave-sint-routines set)) + (name))) +(defun idlwave-sintern-keyword (name &optional set) + (cond ((not (stringp name)) name) + ((gethash (downcase name) (cdr idlwave-sint-keywords))) + ((gethash (downcase name) (car idlwave-sint-keywords))) + (set (idlwave-sintern-set name 'keyword idlwave-sint-keywords set)) + (name))) +(defun idlwave-sintern-method (name &optional set) + (cond ((not (stringp name)) name) + ((gethash (downcase name) (cdr idlwave-sint-methods))) + ((gethash (downcase name) (car idlwave-sint-methods))) + (set (idlwave-sintern-set name 'method idlwave-sint-methods set)) + (name))) +(defun idlwave-sintern-class (name &optional set) + (cond ((not (stringp name)) name) + ((gethash (downcase name) (cdr idlwave-sint-classes))) + ((gethash (downcase name) (car idlwave-sint-classes))) + (set (idlwave-sintern-set name 'class idlwave-sint-classes set)) + (name))) + +(defun idlwave-sintern-file (name &optional set) + (car (or (member name idlwave-sint-files) + (setq idlwave-sint-files (cons name idlwave-sint-files))))) + +(defun idlwave-sintern-set (name type tables set) + (let* ((func (or (cdr (assq type idlwave-completion-case)) + 'identity)) + (iname (funcall (if (eq func 'preserve) 'identity func) name)) + (table (if (eq set 'sys) (cdr tables) (car tables)))) + (puthash (downcase name) iname table) + iname)) + +(defun idlwave-sintern-rinfo-list (list &optional set) + "Sintern all strings in the rinfo LIST. With optional parameter SET: +also set new patterns. Probably this will always have to be t." + (let (entry name type class kwds res source call olh new) + (while list + (setq entry (car list) + list (cdr list) + name (car entry) + type (nth 1 entry) + class (nth 2 entry) + source (nth 3 entry) + call (nth 4 entry) + kwds (nth 5 entry) + olh (nth 6 entry)) + (setq kwds (mapcar (lambda (x) + (list (idlwave-sintern-keyword (car x) set))) + kwds)) + (if class + (progn + (if (symbolp class) (setq class (symbol-name class))) + (setq class (idlwave-sintern-class class set)) + (setq name (idlwave-sintern-method name set))) + (setq name (idlwave-sintern-routine name set))) + (if (stringp (cdr source)) + (setcdr source (idlwave-sintern-file (cdr source) t))) + (setq new (if olh + (list name type class source call kwds olh) + (list name type class source call kwds))) + (setq res (cons new res))) + (nreverse res))) + +;;--------------------------------------------------------------------------- + + +;; The variables which hold the information +(defvar idlwave-builtin-routines nil + "Holds the routine-info obtained by scanning buffers.") +(defvar idlwave-buffer-routines nil + "Holds the routine-info obtained by scanning buffers.") +(defvar idlwave-compiled-routines nil + "Holds the procedure routine-info obtained by asking the shell.") +(defvar idlwave-library-routines nil + "Holds the procedure routine-info from the library scan.") +(defvar idlwave-scanned-lib-directories nil + "The directories scanned to get libinfo.") +(defvar idlwave-routines nil + "Holds the combinded procedure routine-info.") +(defvar idlwave-class-alist nil + "Holds the class names known to IDLWAVE.") +(defvar idlwave-class-history nil + "The history of classes selected with the minibuffer.") +(defvar idlwave-force-class-query nil) +(defvar idlwave-before-completion-wconf nil + "The window configuration just before the completion buffer was displayed.") + +;; +;; The code to get routine info from different sources. + +(defvar idlwave-builtin-routines) +(defun idlwave-routines () + "Provide a list of IDL routines. +This routine loads the builtin routines on the first call. Later it +only returns the value of the variable." + (or idlwave-routines + (progn + (idlwave-update-routine-info) + ;; return the current value + idlwave-routines))) + +(defun idlwave-update-routine-info (&optional arg) + "Update the internal routine-info lists. +These lists are used by `idlwave-routine-info' (\\[idlwave-routine-info]) +and by `idlwave-complete' (\\[idlwave-complete]) to provide information +about individual routines. + +The information can come from 4 sources: +1. IDL programs in the current editing session +2. Compiled modules in an IDL shell running as Emacs subprocess +3. A list which covers the IDL system routines. +4. A list which covers the prescanned library files. + +Scans all IDLWAVE-mode buffers of the current editing session (see +`idlwave-scan-all-buffers-for-routine-info'). +When an IDL shell is running, this command also queries the IDL program +for currently compiled routines. + +With prefix ARG, also reload the system and library lists. +With two prefix ARG's, also rescans the library tree." + (interactive "P") + (if (equal arg '(16)) + (idlwave-create-libinfo-file t) + (let* ((reload (or arg + idlwave-buffer-case-takes-precedence + (null idlwave-builtin-routines)))) + + (setq idlwave-buffer-routines nil + idlwave-compiled-routines nil) + ;; Reset the appropriate hashes + (idlwave-reset-sintern (cond (reload t) + ((null idlwave-builtin-routines) t) + (t 'bufsh))) + + (if idlwave-buffer-case-takes-precedence + ;; We can safely scan the buffer stuff first + (progn + (idlwave-update-buffer-routine-info) + (and reload (idlwave-load-system-rinfo))) + ;; We first do the system info, and then the buffers + (and reload (idlwave-load-system-rinfo)) + (idlwave-update-buffer-routine-info)) + + ;; Let's see if there is a shell + (let* ((shell-is-running (and (fboundp 'idlwave-shell-is-running) + (idlwave-shell-is-running))) + (ask-shell (and shell-is-running + idlwave-query-shell-for-routine-info))) + + (if (or (not ask-shell) + (not (interactive-p))) + ;; 1. If we are not going to ask the shell, we need to do the + ;; concatenation now. + ;; 2. When this function is called non-interactively, it means + ;; that someone needs routine info *now*. The shell update + ;; causes the concatenation *delayed*, so not in time for + ;; the current command. Therefore, we do a concatenation + ;; now, even though the shell might do it again. + (idlwave-concatenate-rinfo-lists)) + + (when ask-shell + ;; Ask the shell about the routines it knows. + (message "Querying the shell") + (idlwave-shell-update-routine-info)))))) + +(defun idlwave-load-system-rinfo () + ;; Load and case-treat the system and lib info files. + (load "idlwave-rinfo" t) + (message "Normalizing idlwave-builtin-routines...") + (setq idlwave-builtin-routines + (idlwave-sintern-rinfo-list idlwave-builtin-routines 'sys)) + (message "Normalizing idlwave-builtin-routines...done") + (setq idlwave-routines idlwave-builtin-routines) + (when (and (stringp idlwave-libinfo-file) + (file-regular-p idlwave-libinfo-file)) + (condition-case nil + (progn + (load-file idlwave-libinfo-file) + (message "Normalizing idlwave-library-routines...") + (setq idlwave-library-routines (idlwave-sintern-rinfo-list + idlwave-library-routines 'sys)) + (message "Normalizing idlwave-library-routines...done")) + (error nil)))) + +(defun idlwave-update-buffer-routine-info () + (let (res) + (if idlwave-scan-all-buffers-for-routine-info + (progn + ;; Scan all buffers, current buffer last + (message "Scanning all buffers...") + (setq res (idlwave-get-routine-info-from-buffers + (reverse (buffer-list))))) + ;; Just scan this buffer + (if (eq major-mode 'idlwave-mode) + (progn + (message "Scanning current buffer...") + (setq res (idlwave-get-routine-info-from-buffers + (list (current-buffer))))))) + ;; Put the result into the correct variable + (setq idlwave-buffer-routines + (idlwave-sintern-rinfo-list res t)))) + +(defun idlwave-concatenate-rinfo-lists () + "Put the different sources for routine information together." + ;; The sequence here is important because earlier definitions shadow + ;; later ones. We assume that if things in the buffers are newer + ;; then in the shell of the system, it is meant to be different. + ;; FIXME: should the builtin stuff be before the library? + ;; This is how IDL searches, the user may also have + ;; functions overloading system stuff, and then the lib + ;; should be first. Difficult to find a general solution. + ;; FIXME: can't we use nconc here in some way, to save memory? + ;; This is possible for buffer abd shell stuff, but these are + ;; small anyway, and so it is not so critical. + (setq idlwave-routines (append idlwave-buffer-routines + idlwave-compiled-routines + idlwave-library-routines + idlwave-builtin-routines)) + (setq idlwave-class-alist nil) + (let (class) + (loop for x in idlwave-routines do + (when (and (setq class (nth 2 x)) + (not (assq class idlwave-class-alist))) + (push (list class) idlwave-class-alist)))) + ;; Give a message with information about the number of routines we have. + (message + "Routine info updated: buffer(%d) compiled(%d) library(%d) system(%d)" + (length idlwave-buffer-routines) + (length idlwave-compiled-routines) + (length idlwave-library-routines) + (length idlwave-builtin-routines))) + +;;----- Scanning buffers ------------------- + +(defun idlwave-get-routine-info-from-buffers (buffers) + "Call `idlwave-get-buffer-routine-info' on idlwave-mode buffers in BUFFERS." + (let (buf routine-lists res) + (save-excursion + (while (setq buf (pop buffers)) + (set-buffer buf) + (if (eq major-mode 'idlwave-mode) + ;; yes, this buffer has the right mode. + (progn (setq res (condition-case nil + (idlwave-get-buffer-routine-info) + (error nil))) + (push res routine-lists))))) + ;; Concatenate the individual lists and return the result + (apply 'nconc routine-lists))) + +(defun idlwave-get-buffer-routine-info () + "Scan the current buffer for routine info. Return (PRO-LIST FUNC-LIST)." + (let* ((case-fold-search t) + routine-list string entry) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (while (re-search-forward + "^[ \t]*\\<\\(pro\\|function\\)\\>" nil t) + (setq string (buffer-substring + (match-beginning 0) + (progn + (idlwave-end-of-statement) + (point)))) + (setq entry (idlwave-parse-definition string)) + (push entry routine-list)))) + routine-list)) + +(defun idlwave-parse-definition (string) + "Parse a module definition." + (let ((case-fold-search t) + start name args type keywords class) + ;; Remove comments + (while (string-match ";.*" string) + (setq string (replace-match "" t t string))) + ;; Remove the continuation line stuff + (while (string-match "\\([^a-zA-Z0-9$_]\\)\\$[ \t]*\n" string) + (setq string (replace-match "\\1 " t nil string))) + ;; Match the name and type. + (when (string-match + "\\<\\(pro\\|function\\)\\>\\s-+\\(\\([a-zA-Z0-9$_]+\\)::\\)?\\([a-zA-Z0-9$_]+\\)" string) + (setq start (match-end 0)) + (setq type (downcase (match-string 1 string))) + (if (match-beginning 3) + (setq class (match-string 3 string))) + (setq name (match-string 4 string))) + ;; Match normal args and keyword args + (while (string-match + ",\\s-*\\([a-zA-Z][a-zA-Z0-9$_]*\\|_extra\\)\\s-*\\(=\\)?" + string start) + (setq start (match-end 0)) + (if (match-beginning 2) + (push (match-string 1 string) keywords) + (push (match-string 1 string) args))) + ;; Normalize and sort. + (setq args (nreverse args)) + (setq keywords (sort keywords (lambda (a b) + (string< (downcase a) (downcase b))))) + ;; Make and return the entry + ;; We don't know which argument are optional, so this information + ;; will not be contained in the calling sequence. + (list name + (if (equal type "pro") 'pro 'fun) + class + (cond ((not (boundp 'idlwave-scanning-lib)) + (cons 'buffer (buffer-file-name))) + ((string= (downcase + (file-name-sans-extension + (file-name-nondirectory (buffer-file-name)))) + (downcase name)) + (list 'lib)) + (t (cons 'lib (file-name-nondirectory (buffer-file-name))))) + (concat + (if (string= type "function") "Result = " "") + (if class "Obj ->[%s::]" "") + "%s" + (if args + (concat + (if (string= type "function") "(" ", ") + (mapconcat 'identity args ", ") + (if (string= type "function") ")" "")))) + (if keywords + (mapcar 'list keywords) + nil)))) + +;;----- Scanning the library ------------------- + +(defun idlwave-create-libinfo-file (&optional arg) + "Scan all files on selected dirs of IDL search path for routine information. +A widget checklist will allow you to choose the directories. +Write the result as a file `idlwave-libinfo-file'. When this file exists, +will be automatically loaded to give routine information about library +routines. +With ARG, just rescan the same directories as last time - so no widget +will pop up." + (interactive "P") + ;; Make sure the file is loaded if it exists. + (if (and (stringp idlwave-libinfo-file) + (file-regular-p idlwave-libinfo-file)) + (condition-case nil + (load-file idlwave-libinfo-file) + (error nil))) + ;; Make sure the file name makes sense + (unless (and (stringp idlwave-libinfo-file) + (file-accessible-directory-p + (file-name-directory idlwave-libinfo-file)) + (not (string= "" (file-name-nondirectory + idlwave-libinfo-file)))) + (error "`idlwave-libinfo-file' does not point to file in accessible directory.")) + + (cond + ((and arg idlwave-scanned-lib-directories) + ;; Rescan the known directories + (idlwave-scan-lib-files idlwave-scanned-lib-directories)) + (idlwave-library-path + ;; Get the directories from that variable + (idlwave-display-libinfo-widget + (idlwave-expand-path idlwave-library-path) + idlwave-scanned-lib-directories)) + (t + ;; Ask the shell for the path and run the widget + (message "Asking the shell for IDL path...") + (idlwave-shell-send-command + "__pa=expand_path(!path,/array)&for i=0,n_elements(__pa)-1 do print,'PATH:',__pa[i]" + '(idlwave-libinfo-command-hook nil) + 'hide)))) + +(defun idlwave-libinfo-command-hook (&optional arg) + ;; Command hook used by `idlwave-create-libinfo-file'. + (if arg + ;; Scan immediately + (idlwave-scan-lib-files idlwave-scanned-lib-directories) + ;; Display the widget + (idlwave-display-libinfo-widget (idlwave-shell-path-filter) + idlwave-scanned-lib-directories))) + +(defvar idlwave-shell-command-output) +(defun idlwave-shell-path-filter () + ;; Convert the output of the path query into a list of directories + (let ((path-string idlwave-shell-command-output) + (case-fold-search t) + (start 0) + dirs) + (while (string-match "^PATH:[ \t]*\\(.*\\)\n" path-string start) + (push (match-string 1 path-string) dirs) + (setq start (match-end 0))) + (nreverse dirs))) + +(defconst idlwave-libinfo-widget-help-string + "This is the front-end to the creation of IDLWAVE library routine info. +Please select below the directories on IDL's search path from which you +would like to extract routine information, which will be stored in the file + + %s + +If this is not the correct file, first set variable `idlwave-libinfo-file'. +Then call this command again. +After selecting the directories, choose [Scan & Save] to scan the library +directories and save the routine info. +\n") + +(defvar idlwave-widget) +(defvar widget-keymap) +(defun idlwave-display-libinfo-widget (dirs selected-dirs) + "Create the widget to select IDL search path directories for scanning." + (interactive) + (require 'widget) + (require 'wid-edit) + (unless dirs + (error "Don't know IDL's search path")) + + ;; Allow only those directories to be selected which are in the path. + (setq selected-dirs (delq nil (mapcar (lambda (x) + (if (member x dirs) x nil)) + selected-dirs))) + (kill-buffer (get-buffer-create "*IDLWAVE Widget*")) + (switch-to-buffer (get-buffer-create "*IDLWAVE Widget*")) + (kill-all-local-variables) + (make-local-variable 'idlwave-widget) + (widget-insert (format idlwave-libinfo-widget-help-string + idlwave-libinfo-file)) + + (widget-create 'push-button + :notify 'idlwave-widget-scan-lib-files + :help-echo "testing" + "Scan & Save") + (widget-insert " ") + (widget-create 'push-button + :notify (lambda (&rest ignore) + (kill-buffer (current-buffer))) + "Quit") + (widget-insert " ") + (widget-create 'push-button + :notify 'idlwave-delete-libinfo-file + "Delete File") + (widget-insert " ") + (widget-create 'push-button + :notify '(lambda (&rest ignore) + (idlwave-display-libinfo-widget + (widget-get idlwave-widget :path-dirs) + (widget-get idlwave-widget :path-dirs))) + "Select All") + (widget-insert " ") + (widget-create 'push-button + :notify '(lambda (&rest ignore) + (idlwave-display-libinfo-widget + (widget-get idlwave-widget :path-dirs) + nil)) + "Deselect All") + (widget-insert "\n\n") + + (widget-insert "Select Directories\n") + + (setq idlwave-widget + (apply 'widget-create + 'checklist + :value selected-dirs + :greedy t + :tag "List of directories" + (mapcar (lambda (x) (list 'item x)) dirs))) + (widget-put idlwave-widget :path-dirs dirs) + (widget-insert "\n") + (use-local-map widget-keymap) + (widget-setup) + (goto-char (point-min)) + (delete-other-windows)) + +(defun idlwave-delete-libinfo-file (&rest ignore) + (if (yes-or-no-p + (format "Delete file %s " idlwave-libinfo-file)) + (progn + (delete-file idlwave-libinfo-file) + (message "%s has been deleted" idlwave-libinfo-file)))) + +(defun idlwave-widget-scan-lib-files (&rest ignore) + ;; Call `idlwave-scan-lib-files' with data taken from the widget. + (let* ((widget idlwave-widget) + (selected-dirs (widget-value widget))) + (idlwave-scan-lib-files selected-dirs))) + +(defvar font-lock-mode) +(defun idlwave-scan-lib-files (selected-dirs) + ;; Scan the files in SELECTED-DIRS and store the info in a file + (let* ((idlwave-scanning-lib t) + (idlwave-completion-case nil) + dirs dir files file) + (setq idlwave-library-routines nil) + (setq idlwave-scanned-lib-directories selected-dirs) + (save-excursion + (set-buffer (get-buffer-create "*idlwave-scan.pro*")) + (idlwave-mode) + (setq dirs (reverse selected-dirs)) + (while (setq dir (pop dirs)) + (when (file-directory-p dir) + (setq files (directory-files dir 'full "\\.[pP][rR][oO]\\'")) + (while (setq file (pop files)) + (when (file-regular-p file) + (if (not (file-readable-p file)) + (message "Skipping %s (no read permission)" file) + (message "Scanning %s..." file) + (erase-buffer) + (insert-file-contents file 'visit) + (setq idlwave-library-routines + (append (idlwave-get-routine-info-from-buffers + (list (current-buffer))) + idlwave-library-routines))) + ))))) + (kill-buffer "*idlwave-scan.pro*") + (kill-buffer (get-buffer-create "*IDLWAVE Widget*")) + (let ((font-lock-maximum-size 0)) + (find-file idlwave-libinfo-file)) + (if (and (boundp 'font-lock-mode) + font-lock-mode) + (font-lock-mode 0)) + (erase-buffer) + (insert ";; IDLWAVE libinfo file\n") + (insert (format ";; Created %s\n\n" (current-time-string))) + + ;; Define the variable which contains a list of all scanned directories + (insert "\n(setq idlwave-scanned-lib-directories\n '(") + (mapcar (lambda (x) + (insert (format "\n \"%s\"" x))) + selected-dirs) + (insert "))\n") + ;; Define the routine info list + (insert "\n(setq idlwave-library-routines\n '(") + (mapcar (lambda (x) + (insert "\n ") + (insert (with-output-to-string (prin1 x)))) + idlwave-library-routines) + (insert (format "))\n\n;;; %s ends here\n" + (file-name-nondirectory idlwave-libinfo-file))) + (goto-char (point-min)) + ;; Save the buffer + (save-buffer 0) + (kill-buffer (current-buffer))) + (message "Info for %d routines saved in %s" + (length idlwave-library-routines) + idlwave-libinfo-file) + (sit-for 2) + (idlwave-update-routine-info t)) + +(defun idlwave-expand-path (path &optional default-dir) + ;; Expand parts of path starting with '+' recursively into directory list. + ;; Relative recursive path elements are expanded relative to DEFAULT-DIR. + (message "Expanding path...") + (let (path1 dir recursive) + (while (setq dir (pop path)) + (if (setq recursive (string= (substring dir 0 1) "+")) + (setq dir (substring dir 1))) + (if (and recursive + (not (file-name-absolute-p dir))) + (setq dir (expand-file-name dir default-dir))) + (if recursive + ;; Expand recursively + (setq path1 (append (idlwave-recursive-directory-list dir) path1)) + ;; Keep unchanged + (push dir path1))) + (message "Expanding path...done") + (nreverse path1))) + +(defun idlwave-recursive-directory-list (dir) + ;; Return a list of all directories below DIR, including DIR itself + (let ((path (list dir)) path1 file files) + (while (setq dir (pop path)) + (when (file-directory-p dir) + (setq files (nreverse (directory-files dir t "[^.]"))) + (while (setq file (pop files)) + (if (file-directory-p file) + (push (file-name-as-directory file) path))) + (push dir path1))) + path1)) + +;;----- Asking the shell ------------------- + +;; First, here is the idl program which can be used to query IDL for +;; defined routines. +(defconst idlwave-routine-info.pro + " +function idlwave_make_info_entry,name,func=func,separator=sep + ;; See if it's an object method + func = keyword_set(func) + methsep = strpos(name,'::') + meth = methsep ne -1 + + ;; Get routine info + pars = routine_info(name,/parameters,functions=func) + source = routine_info(name,/source,functions=func) + nargs = pars.num_args + nkw = pars.num_kw_args + if nargs gt 0 then args = pars.args + if nkw gt 0 then kwargs = pars.kw_args + + ;; Trim the class, and make the name + if meth then begin + class = strmid(name,0,methsep) + name = strmid(name,methsep+2,strlen(name)-1) + if nargs gt 0 then begin + ;; remove the self argument + wh = where(args ne 'SELF',nargs) + if nargs gt 0 then args = args(wh) + endif + endif else begin + ;; No class, just a normal routine. + class = \"\" + endelse + + ;; Calling sequence + cs = \"\" + if func then cs = 'Result = ' + if meth then cs = cs + 'Obj -> [' + '%s' + '::]' + cs = cs + '%s' + if func then cs = cs + '(' else if nargs gt 0 then cs = cs + ', ' + if nargs gt 0 then begin + for j=0,nargs-1 do begin + cs = cs + args(j) + if j lt nargs-1 then cs = cs + ', ' + endfor + end + if func then cs = cs + ')' + ;; Keyword arguments + kwstring = '' + if nkw gt 0 then begin + for j=0,nkw-1 do begin + kwstring = kwstring + ' ' + kwargs(j) + endfor + endif + + ret=(['IDLWAVE-PRO','IDLWAVE-FUN', $ + 'IDLWAVE-PRO','IDLWAVE-FUN'])(func+2*meth) + + return,ret + ': ' + name + sep + class + sep + source(0).path $ + + sep + cs + sep + kwstring +end + +pro idlwave_routine_info + sep = '<@>' + print,'>>>BEGIN OF IDLWAVE ROUTINE INFO (\"' + sep + '\" IS THE SEPARATOR)' + all = routine_info() + for i=0,n_elements(all)-1 do $ + print,idlwave_make_info_entry(all(i),separator=sep) + all = routine_info(/functions) + for i=0,n_elements(all)-1 do $ + print,idlwave_make_info_entry(all(i),/func,separator=sep) + print,'>>>END OF IDLWAVE ROUTINE INFO' +end +" + "The idl program to get the routine info stuff. +The output of this program is parsed by `idlwave-shell-routine-info-filter'.") + +(defun idlwave-shell-routine-info-filter () + "Function which parses the special output from idlwave_routine_info.pro." + (let ((text idlwave-shell-command-output) + (start 0) + sep sep-re file type spec specs name cs key keys class) + ;; Initialize variables + (setq idlwave-compiled-routines nil) + ;; Cut out the correct part of the output. + (if (string-match + "^>>>BEGIN OF IDLWAVE ROUTINE INFO (\"\\(.+\\)\" IS THE SEPARATOR.*" + text) + (setq sep (match-string 1 text) + sep-re (concat (regexp-quote sep) " *") + text (substring text (match-end 0))) + (error "Routine Info error: No match for BEGIN line")) + (if (string-match "^>>>END OF IDLWAVE ROUTINE INFO.*" text) + (setq text (substring text 0 (match-beginning 0))) + (error "Routine Info error: No match for END line")) + ;; Match the output lines + (while (string-match "^IDLWAVE-\\(PRO\\|FUN\\): \\(.*\\)" text start) + (setq start (match-end 0)) + (setq type (match-string 1 text) + spec (match-string 2 text) + specs (idlwave-split-string spec sep-re) + name (nth 0 specs) + class (if (equal (nth 1 specs) "") nil (nth 1 specs)) + file (nth 2 specs) + cs (nth 3 specs) + key (nth 4 specs) + keys (if (and (stringp key) + (not (string-match "\\` *\\'" key))) + (mapcar 'list + (delete "" (idlwave-split-string key " +"))))) + (setq name (idlwave-sintern-routine-or-method name class t) + class (idlwave-sintern-class class t) + keys (mapcar (lambda (x) + (list (idlwave-sintern-keyword (car x) t))) keys)) + ;; Make sure we use the same string object for the same file + (setq file (idlwave-sintern-file file t)) + ;; FIXME: What should I do with routines from the temp file??? + ;; Maybe just leave it in - there is a chance that the + ;; routine is still in there. + ;; (if (equal file idlwave-shell-temp-pro-file) + ;; (setq file nil)) + + ;; In the following ignore routines already defined in buffers, + ;; assuming that if the buffer stuff differs, it is a "new" + ;; version. + ;; We could do the same for the library to avoid duplicates - + ;; but I think frequently a user might have several versions of + ;; the same function in different programs, and in this case the + ;; compiled one will be the best guess of all version. + ;; Therefore, we leave duplicates of library routines in. + + (cond ((string= name "$MAIN$")) ; ignore this one + ((and (string= type "PRO") + ;; FIXME: is it OK to make the buffer routines dominate? + (not (idlwave-rinfo-assq name 'pro class + idlwave-buffer-routines)) + ;; FIXME: is it OK to make the library routines dominate? + ;;(not (idlwave-rinfo-assq name 'pro class + ;; idlwave-library-routines)) + ) + (push (list name 'pro class (cons 'compiled file) cs keys) + idlwave-compiled-routines)) + ((and (string= type "FUN") + ;; FIXME: is it OK to make the buffer routines dominate? + (not (idlwave-rinfo-assq name 'fun class + idlwave-buffer-routines)) + ;; FIXME: is it OK to make the library routines dominate? + ;; (not (idlwave-rinfo-assq name 'fun class + ;; idlwave-library-routines)) + ) + (push (list name 'fun class (cons 'compiled file) cs keys) + idlwave-compiled-routines))))) + ;; Reverse the definitions so that they are alphabetically sorted. + (setq idlwave-compiled-routines + (nreverse idlwave-compiled-routines))) + +(defvar idlwave-shell-temp-pro-file) +(defun idlwave-shell-update-routine-info () + "Query the shell for routine_info of compiled modules and update the lists." + ;; Save and compile the procedure + (save-excursion + (set-buffer (idlwave-find-file-noselect + idlwave-shell-temp-pro-file)) + (erase-buffer) + (insert idlwave-routine-info.pro) + (save-buffer 0)) + (idlwave-shell-send-command (concat ".run " idlwave-shell-temp-pro-file) + nil 'hide) + + ;; Execute the procedure and analyze the output + (idlwave-shell-send-command "idlwave_routine_info" + '(progn + (idlwave-shell-routine-info-filter) + (idlwave-concatenate-rinfo-lists)) + 'hide)) + +;; --------------------------------------------------------------------------- +;; +;; Completion and displaying routine calling sequences + +(defun idlwave-complete (&optional arg module class) + "Complete a function, procedure or keyword name at point. +This function is smart and figures out what can be legally completed +at this point. +- At the beginning of a statement it completes procedure names. +- In the middle of a statement it completes function names. +- after a `(' or `,' in the argument list of a function or procedure, + it completes a keyword of the relevant function or procedure. +- In the first arg of `OBJ_NEW', it completes a class name. + +When several completions are possible, a list will be displayed in the +*Completions* buffer. If this list is too long to fit into the +window, scrolling can be achieved by repeatedly pressing \\[idlwave-complete]. + +The function also knows about object methods. When it needs a class +name, the action depends upon `idlwave-query-class', which see. You +can force IDLWAVE to ask you for a class name with a \\[universal-argument] prefix +argument to this command. + +See also the variables `idlwave-keyword-completion-adds-equal' and +`idlwave-function-completion-adds-paren'. + +The optional ARG can be used to specify the completion type in order +to override IDLWAVE's idea of what should be completed at point. +Possible values are: + +0 <=> query for the completion type +1 <=> 'procedure +2 <=> 'procedure-keyword +3 <=> 'function +4 <=> 'function-keyword +5 <=> 'procedure-method +6 <=> 'procedure-method-keyword +7 <=> 'function-method +8 <=> 'function-method-keyword +9 <=> 'class + +For Lisp programmers only: +When we force a keyword, optional argument MODULE can contain the module name. +When we force a method or a method keyword, CLASS can specify the class." + (interactive "P") + (idlwave-routines) + (let* ((where-list + (if (and arg + (or (integerp arg) + (symbolp arg))) + (idlwave-make-force-complete-where-list arg module class) + (idlwave-where))) + (what (nth 2 where-list)) + (idlwave-force-class-query (equal arg '(4))) + cwin) + + (if (and module (string-match "::" module)) + (setq class (substring module 0 (match-beginning 0)) + module (substring module (match-end 0)))) + + (cond + + ((and (null arg) + (eq (car-safe last-command) 'idlwave-display-completion-list) + (setq cwin (get-buffer-window "*Completions*"))) + (setq this-command last-command) + (idlwave-scroll-completions)) + + ((null what) + (error "Nothing to complete here")) + + ((eq what 'class) + (idlwave-complete-class)) + + ((eq what 'procedure) + ;; Complete a procedure name + (let* ((class-selector (idlwave-determine-class (nth 3 where-list) 'pro)) + (isa (concat "procedure" (if class-selector "-method" ""))) + (type-selector 'pro)) + (idlwave-complete-in-buffer + 'procedure (if class-selector 'method 'routine) + (idlwave-routines) 'idlwave-selector + (format "Select a %s name%s" + isa + (if class-selector + (format " (class is %s)" class-selector) + "")) + isa + 'idlwave-attach-method-classes))) + + ((eq what 'function) + ;; Complete a function name + (let* ((class-selector (idlwave-determine-class (nth 3 where-list) 'fun)) + (isa (concat "function" (if class-selector "-method" ""))) + (type-selector 'fun)) + (idlwave-complete-in-buffer + 'function (if class-selector 'method 'routine) + (idlwave-routines) 'idlwave-selector + (format "Select a %s name%s" + isa + (if class-selector + (format " (class is %s)" class-selector) + "")) + isa + 'idlwave-attach-method-classes))) + + ((eq what 'procedure-keyword) + ;; Complete a procedure keyword + (let* ((where (nth 3 where-list)) + (name (car where)) + (method-selector name) + (type-selector 'pro) + (class (idlwave-determine-class where 'pro)) + (class-selector class) + (isa (format "procedure%s-keyword" (if class "-method" ""))) + (entry (idlwave-rinfo-assq + name 'pro class (idlwave-routines))) + (list (nth 5 entry))) + (unless (or entry (eq class t)) + (error "Nothing known about procedure %s" + (idlwave-make-full-name class name))) + (setq list (idlwave-fix-keywords name 'pro class list)) + (unless list (error (format "No keywords available for procedure %s" + (idlwave-make-full-name class name)))) + (idlwave-complete-in-buffer + 'keyword 'keyword list nil + (format "Select keyword for procedure %s%s" + (idlwave-make-full-name class name) + (if (member '("_EXTRA") list) " (note _EXTRA)" "")) + isa + 'idlwave-attach-keyword-classes))) + + ((eq what 'function-keyword) + ;; Complete a function keyword + (let* ((where (nth 3 where-list)) + (name (car where)) + (method-selector name) + (type-selector 'fun) + (class (idlwave-determine-class where 'fun)) + (class-selector class) + (isa (format "function%s-keyword" (if class "-method" ""))) + (entry (idlwave-rinfo-assq + name 'fun class (idlwave-routines))) + (list (nth 5 entry))) + (unless (or entry (eq class t)) + (error "Nothing known about function %s" + (idlwave-make-full-name class name))) + (setq list (idlwave-fix-keywords name 'fun class list)) + (unless list (error (format "No keywords available for function %s" + (idlwave-make-full-name class name)))) + (idlwave-complete-in-buffer + 'keyword 'keyword list nil + (format "Select keyword for function %s%s" + (idlwave-make-full-name class name) + (if (member '("_EXTRA") list) " (note _EXTRA)" "")) + isa + 'idlwave-attach-keyword-classes))) + + (t (error "This should not happen (idlwave-complete)"))))) + +(defun idlwave-make-force-complete-where-list (what &optional module class) + ;; Return an artificial WHERE specification to force the completion + ;; routine to complete a specific item independent of context. + ;; WHAT is the prefix arg of `idlwave-complete', see there for details. + ;; MODULE and CLASS can be used to specify the routine name and class. + ;; The class name will also be found in MODULE if that is like "class::mod". + (let* ((what-list '(("procedure") ("procedure-keyword") + ("function") ("function-keyword") + ("procedure-method") ("procedure-method-keyword") + ("function-method") ("function-method-keyword") + ("class"))) + (module (idlwave-sintern-routine-or-method module class)) + (class (idlwave-sintern-class class)) + (what (cond + ((equal what 0) + (setq what + (intern (completing-read + "Complete what? " what-list nil t)))) + ((integerp what) + (setq what (intern (car (nth (1- what) what-list))))) + ((and what + (symbolp what) + (assoc (symbol-name what) what-list)) + what) + (t (error "Illegal WHAT")))) + (nil-list '(nil nil nil nil)) + (class-list (list nil nil (or class t) nil))) + + (cond + + ((eq what 'procedure) + (list nil-list nil-list 'procedure nil-list nil)) + + ((eq what 'procedure-keyword) + (let* ((class-selector nil) + (type-selector 'pro) + (pro (or module + (idlwave-completing-read + "Procedure: " (idlwave-routines) 'idlwave-selector)))) + (setq pro (idlwave-sintern-routine pro)) + (list nil-list nil-list 'procedure-keyword + (list pro nil nil nil) nil))) + + ((eq what 'function) + (list nil-list nil-list 'function nil-list nil)) + + ((eq what 'function-keyword) + (let* ((class-selector nil) + (type-selector 'fun) + (func (or module + (idlwave-completing-read + "Function: " (idlwave-routines) 'idlwave-selector)))) + (setq func (idlwave-sintern-routine func)) + (list nil-list nil-list 'function-keyword + (list func nil nil nil) nil))) + + ((eq what 'procedure-method) + (list nil-list nil-list 'procedure class-list nil)) + + ((eq what 'procedure-method-keyword) + (let* ((class (idlwave-determine-class class-list 'pro)) + (class-selector class) + (type-selector 'pro) + (pro (or module + (idlwave-completing-read + (format "Procedure in %s class: " class-selector) + (idlwave-routines) 'idlwave-selector)))) + (setq pro (idlwave-sintern-method pro)) + (list nil-list nil-list 'procedure-keyword + (list pro nil class nil) nil))) + + ((eq what 'function-method) + (list nil-list nil-list 'function class-list nil)) + + ((eq what 'function-method-keyword) + (let* ((class (idlwave-determine-class class-list 'fun)) + (class-selector class) + (type-selector 'fun) + (func (or module + (idlwave-completing-read + (format "Function in %s class: " class-selector) + (idlwave-routines) 'idlwave-selector)))) + (setq func (idlwave-sintern-method func)) + (list nil-list nil-list 'function-keyword + (list func nil class nil) nil))) + + ((eq what 'class) + (list nil-list nil-list 'class nil-list nil)) + + (t (error "Illegal value for WHAT"))))) + +(defun idlwave-completing-read (&rest args) + ;; Completing read, case insensitive + (let ((old-value (default-value 'completion-ignore-case))) + (unwind-protect + (progn + (setq-default completion-ignore-case t) + (apply 'completing-read args)) + (setq-default completion-ignore-case old-value)))) + +(defun idlwave-make-full-name (class name) + ;; Make a fully qualified module name including the class name + (concat (if class (format "%s::" class) "") name)) + +(defun idlwave-rinfo-assq (name type class list) + ;; Works like assq, but also checks type and class + (catch 'exit + (let (match) + (while (setq match (assq name list)) + (and (or (eq type t) + (eq (nth 1 match) type)) + (eq (nth 2 match) class) + (throw 'exit match)) + (setq list (cdr (memq match list))))))) + +(defun idlwave-all-assq (key list) + "Return a list of all associations of Key in LIST." + (let (rtn elt) + (while (setq elt (assq key list)) + (push elt rtn) + (setq list (cdr (memq elt list)))) + (nreverse rtn))) + +(defun idlwave-all-method-classes (method &optional type) + "Return all classes which have a method METHOD. TYPE is 'fun or 'pro. +When TYPE is not specified, both procedures and functions will be considered." + (if (null method) + (mapcar 'car idlwave-class-alist) + (let (rtn) + (mapcar (lambda (x) + (and (nth 2 x) + (or (not type) + (eq type (nth 1 x))) + (push (nth 2 x) rtn))) + (idlwave-all-assq method (idlwave-routines))) + (idlwave-uniquify rtn)))) + +(defun idlwave-all-method-keyword-classes (method keyword &optional type) + "Return all classes which have a method METHOD with keyword KEYWORD. +TYPE is 'fun or 'pro. +When TYPE is not specified, both procedures and functions will be considered." + (if (or (null method) + (null keyword)) + nil + (let (rtn) + (mapcar (lambda (x) + (and (nth 2 x) + (or (not type) + (eq type (nth 1 x))) + (assoc keyword (nth 5 x)) + (push (nth 2 x) rtn))) + (idlwave-all-assq method (idlwave-routines))) + (idlwave-uniquify rtn)))) + +(defun idlwave-determine-class (info type) + ;; Determine the class of a routine call. INFO is the structure returned + ;; `idlwave-what-function' or `idlwave-what-procedure'. + ;; The third element in this structure is the class. When nil, we return nil. + ;; When t, try to get the class from text properties at the arrow, + ;; otherwise prompt the user for a class name. Also stores the selected + ;; class as a text property at the arrow. + ;; TYPE is 'fun or 'pro. + (let* ((class (nth 2 info)) + (apos (nth 3 info)) + (nassoc (assoc (if (stringp (car info)) + (upcase (car info)) + (car info)) + idlwave-query-class)) + (dassoc (assq (if (car info) 'keyword-default 'method-default) + idlwave-query-class)) + (query (cond (nassoc (cdr nassoc)) + (dassoc (cdr dassoc)) + (t t))) + (arrow (and apos (string= (buffer-substring apos (+ 2 apos)) "->"))) + (force-query idlwave-force-class-query) + store class-alist) + (cond + ((null class) nil) + ((eq t class) + ;; There is an object which would like to know its class + (if (and arrow (get-text-property apos 'idlwave-class) + idlwave-store-inquired-class + (not force-query)) + (setq class (get-text-property apos 'idlwave-class) + class (idlwave-sintern-class class))) + (when (and (eq class t) + (or force-query query)) + (setq class-alist + (mapcar 'list (idlwave-all-method-classes (car info) type))) + (setq class + (idlwave-sintern-class + (cond + ((and (= (length class-alist) 0) (not force-query)) + (error "No classes available with method %s" (car info))) + ((and (= (length class-alist) 1) (not force-query)) + (car (car class-alist))) + (t + (setq store idlwave-store-inquired-class) + (idlwave-completing-read + (format "Class%s: " (if (stringp (car info)) + (format " for %s method %s" + type (car info)) + "")) + class-alist nil nil nil 'idlwave-class-history)))))) + (when (and class (not (eq t class))) + ;; We have a real class here + (when (and store arrow) + (put-text-property apos (+ apos 2) 'idlwave-class class) + (put-text-property apos (+ apos 2) 'face idlwave-class-arrow-face)) + (setf (nth 2 info) class)) + ;; Return the class + class) + ;; Default as fallback + (t class)))) + +(defvar type-selector) +(defvar class-selector) +(defvar method-selector) +(defun idlwave-selector (a) + (and (eq (nth 1 a) type-selector) + (or (and (nth 2 a) (eq class-selector t)) + (eq (nth 2 a) class-selector)))) + +(defun idlwave-where () + "Find out where we are. +The return value is a list with the following stuff: +(PRO-LIST FUNC-LIST COMPLETE-WHAT CW-LIST LAST-CHAR) + +PRO-LIST (PRO POINT CLASS ARROW) +FUNC-LIST (FUNC POINT CLASS ARROW) +COMPLETE-WHAT a symbol indicating what kind of completion makes sense here +CW-LIST Like PRO-LIST, for what can be copmpleted here. +LAST-CHAR last relevant character before point (non-white non-comment, + not part of current identifier or leading slash). + +In the lists, we have these meanings: +PRO: Procedure name +FUNC: Function name +POINT: Where is this +CLASS: What class has the routine (nil=no, t=is method, but class unknown) +ARROW: Where is the arrow?" + (idlwave-routines) + (let* ((bos (save-excursion (idlwave-beginning-of-statement) (point))) + (func-entry (idlwave-what-function bos)) + (func (car func-entry)) + (func-class (nth 1 func-entry)) + (func-arrow (nth 2 func-entry)) + (func-point (or (nth 3 func-entry) 0)) + (func-level (or (nth 4 func-entry) 0)) + (pro-entry (idlwave-what-procedure bos)) + (pro (car pro-entry)) + (pro-class (nth 1 pro-entry)) + (pro-arrow (nth 2 pro-entry)) + (pro-point (or (nth 3 pro-entry) 0)) + (last-char (idlwave-last-valid-char)) + (case-fold-search t) + cw cw-mod cw-arrow cw-class cw-point) + (if (< func-point pro-point) (setq func nil)) + (cond + ((string-match + "\\`[ \t]*\\([a-zA-Z][a-zA-Z0-9$_]*\\)?\\'" + (buffer-substring (if (> pro-point 0) pro-point bos) (point))) + (setq cw 'procedure cw-class pro-class cw-point pro-point + cw-arrow pro-arrow)) + ((string-match "\\`[ \t]*\\(pro\\|function\\)\\>" + (buffer-substring bos (point))) + nil) + ((string-match "OBJ_NEW([ \t]*'\\([a-zA-Z][a-zA-Z0-9$_]*\\)?\\'" + (buffer-substring bos (point))) + (setq cw 'class)) + ((and func + (> func-point pro-point) + (= func-level 1) + (memq last-char '(?\( ?,))) + (setq cw 'function-keyword cw-mod func cw-point func-point + cw-class func-class cw-arrow func-arrow)) + ((and pro (eq last-char ?,)) + (setq cw 'procedure-keyword cw-mod pro cw-point pro-point + cw-class pro-class cw-arrow pro-arrow)) +; ((member last-char '(?\' ?\) ?\] ?!)) +; ;; after these chars, a function makes no sense +; ;; FIXME: I am sure there can be more in this list +; ;; FIXME: Do we want to do this at all? +; nil) + ;; Everywhere else we try a function. + (t + (setq cw 'function) + (save-excursion + (if (re-search-backward "->[ \t]*\\(\\([$a-zA-Z0-9_]+\\)::\\)?[$a-zA-Z0-9_]*\\=" bos t) + (setq cw-arrow (match-beginning 0) + cw-class (if (match-end 2) + (idlwave-sintern-class (match-string 2)) + t)))))) + (list (list pro pro-point pro-class pro-arrow) + (list func func-point func-class func-arrow) + cw + (list cw-mod cw-point cw-class cw-arrow) + last-char))) + +(defun idlwave-this-word (&optional class) + ;; Grab the word around point. CLASS is for the `skip-chars=...' functions + (setq class (or class "a-zA-Z0-9$_")) + (save-excursion + (buffer-substring-no-properties + (progn (skip-chars-backward class) (point)) + (progn (skip-chars-forward class) (point))))) + +(defvar idlwave-find-symbol-syntax-table) +(defun idlwave-what-function (&optional bound) + ;; Find out if point is within the argument list of a function. + ;; The return value is ("function-name" (point) level). + ;; Level is 1 on the to level parenthesis, higher further down. + + ;; If the optional BOUND is an integer, bound backwards directed + ;; searches to this point. + + (catch 'exit + (let (pos + func-point + (old-syntax (syntax-table)) + (cnt 0) + func arrow-start class) + (unwind-protect + (save-restriction + (save-excursion + (set-syntax-table idlwave-find-symbol-syntax-table) + (narrow-to-region (max 1 (or bound 0)) (point-max)) + ;; move back out of the current parenthesis + (while (condition-case nil + (progn (up-list -1) t) + (error nil)) + (setq pos (point)) + (incf cnt) + (when (and (= (following-char) ?\() + (re-search-backward + "\\(::\\|\\<\\)\\([a-zA-Z][a-zA-Z0-9$_]*\\)\\=" + bound t)) + (setq func (match-string 2) + func-point (goto-char (match-beginning 2)) + pos func-point) + (if (re-search-backward + "->[ \t]*\\(\\([a-zA-Z][a-zA-Z0-9$_]*\\)::\\)?\\=" bound t) + (setq arrow-start (match-beginning 0) + class (or (match-string 2) t))) + (throw + 'exit + (list + (idlwave-sintern-routine-or-method func class) + (idlwave-sintern-class class) + arrow-start func-point cnt))) + (goto-char pos)) + (throw 'exit nil))) + (set-syntax-table old-syntax))))) + +(defun idlwave-what-procedure (&optional bound) + ;; Find out if point is within the argument list of a procedure. + ;; The return value is ("procedure-name" class arrow-pos (point)). + + ;; If the optional BOUND is an integer, bound backwards directed + ;; searches to this point. + (let ((pos (point)) pro-point + pro class arrow-start string) + (save-excursion + (idlwave-beginning-of-statement) + (setq string (buffer-substring (point) pos)) + (if (string-match + "\\`[ \t]*\\([a-zA-Z][a-zA-Z0-9$_]*\\)[ \t]*\\(,\\|\\'\\)" string) + (setq pro (match-string 1 string) + pro-point (+ (point) (match-beginning 1))) + (if (and (idlwave-skip-object) + (setq string (buffer-substring (point) pos)) + (string-match + "\\`[ \t]*\\(->\\)[ \t]*\\(\\([a-zA-Z][a-zA-Z0-9$_]*\\)::\\)?\\([a-zA-Z][a-zA-Z0-9$_]*\\)?[ \t]*\\(,\\|\\'\\)" string)) + (setq pro (if (match-beginning 4) + (match-string 4 string)) + pro-point (if (match-beginning 4) + (+ (point) (match-beginning 4)) + pos) + arrow-start (+ (point) (match-beginning 1)) + class (or (match-string 3 string) t))))) + (list (idlwave-sintern-routine-or-method pro class) + (idlwave-sintern-class class) + arrow-start + pro-point))) + +(defun idlwave-skip-object () + ;; If there is an object at point, move over it and return t. + (let ((pos (point))) + (if (catch 'exit + (save-excursion + (skip-chars-forward " ") ; white space + (skip-chars-forward "*") ; de-reference + (cond + ((looking-at idlwave-identifier) + (goto-char (match-end 0))) + ((eq (following-char) ?\() + nil) + (t (throw 'exit nil))) + (catch 'endwhile + (while t + (cond ((eq (following-char) ?.) + (forward-char 1) + (if (not (looking-at idlwave-identifier)) + (throw 'exit nil)) + (goto-char (match-end 0))) + ((memq (following-char) '(?\( ?\[)) + (condition-case nil + (forward-list 1) + (error (throw 'exit nil)))) + (t (throw 'endwhile t))))) + (if (looking-at "[ \t]*->") + (throw 'exit (setq pos (match-beginning 0))) + (throw 'exit nil)))) + (goto-char pos) + nil))) + + +(defun idlwave-last-valid-char () + "Return the last character before point which is not white or a comment +and also not part of the current identifier. Since we do this in +order to identify places where keywords are, we consider the initial +`/' of a keyword as part of the identifier. +This function is not general, can only be used for completion stuff." + (catch 'exit + (save-excursion + ;; skip the current identifier + (skip-chars-backward "a-zA-Z0-9_$") + ;; also skip a leading slash which might be belong to the keyword + (if (eq (preceding-char) ?/) + (backward-char 1)) + ;; FIXME: does not check if this is a valid identifier + (while t + (skip-chars-backward " \t") + (cond + ((memq (preceding-char) '(?\; ?\$)) (throw 'exit nil)) + ((eq (preceding-char) ?\n) + (beginning-of-line 0) + (if (looking-at "\\([^;]\\)*\\$[ \t]*\\(;.*\\)?\n") + ;; continuation line + (goto-char (match-end 1)) + (throw 'exit nil))) + (t (throw 'exit (preceding-char)))))))) + +(defvar idlwave-complete-after-success-form nil + "A form to evaluate after successful completion.") +(defvar idlwave-complete-after-success-form-force nil + "A form to evaluate after completion selection in *Completions* buffer.") +(defconst idlwave-completion-mark (make-marker) + "A mark pointing to the beginning of the completion string.") + +(defun idlwave-complete-in-buffer (type stype list selector prompt isa + &optional prepare-display-function) + "Perform TYPE completion of word before point against LIST. +SELECTOR is the PREDICATE argument for the completion function. +Show PROMPT in echo area. TYPE is one of 'function, 'procedure or 'keyword." + (let* ((completion-ignore-case t) + beg (end (point)) slash part spart completion all-completions + dpart dcompletion) + + (unless list + (error (concat prompt ": No completions available"))) + + ;; What is already in the buffer? + (save-excursion + (skip-chars-backward "a-zA-Z0-9_$") + (setq slash (eq (preceding-char) ?/) + beg (point) + idlwave-complete-after-success-form + (list 'idlwave-after-successful-completion + (list 'quote type) slash beg) + idlwave-complete-after-success-form-force + (list 'idlwave-after-successful-completion + (list 'quote type) slash (list 'quote 'force)))) + + ;; Try a completion + (setq part (buffer-substring beg end) + dpart (downcase part) + spart (idlwave-sintern stype part) + completion (try-completion part list selector) + dcompletion (if (stringp completion) (downcase completion))) + (cond + ((null completion) + ;; nothing available. + (error "Can't find %s completion for \"%s\"" isa part)) + ((and (not (equal dpart dcompletion)) + (not (eq t completion))) + ;; We can add something + (delete-region beg end) + (if (and (string= part dpart) + (or (not (string= part "")) + idlwave-complete-empty-string-as-lower-case) + (not idlwave-completion-force-default-case)) + (insert dcompletion) + (insert completion)) + (if (eq t (try-completion completion list selector)) + ;; Now this is a unique match + (idlwave-after-successful-completion type slash beg)) + t) + ((or (eq completion t) + (and (equal dpart dcompletion) + (= 1 (length (setq all-completions + (idlwave-uniquify + (all-completions part list selector))))))) + ;; This is already complete + (idlwave-after-successful-completion type slash beg) + (message "%s is already the complete %s" part isa) + nil) + (t + ;; We cannot add something - offer a list. + (message "Making completion list...") + (let* ((list all-completions) + (complete (memq spart all-completions)) + (completion-highlight-first-word-only t) ; XEmacs + (completion-fixup-function ; Emacs + (lambda () (and (eq (preceding-char) ?>) + (re-search-backward " <" beg t))))) + (setq list (sort list (lambda (a b) + (string< (downcase a) (downcase b))))) + (if prepare-display-function + (setq list (funcall prepare-display-function list))) + (if (and (string= part dpart) + (or (not (string= part "")) + idlwave-complete-empty-string-as-lower-case) + (not idlwave-completion-force-default-case)) + (setq list (mapcar (lambda (x) + (if (listp x) + (setcar x (downcase (car x))) + (setq x (downcase x))) + x) + list))) + (idlwave-display-completion-list list prompt beg complete)) + t)))) + +(defun idlwave-complete-class () + "Complete a class at point." + (interactive) + ;; Call `idlwave-routines' to make sure the class list will be available + (idlwave-routines) + ;; Now do the completion + (idlwave-complete-in-buffer 'class 'class idlwave-class-alist nil + "Select a class" "class")) + + +(defun idlwave-attach-classes (list is-kwd show-classes) + ;; attach the proper class list to a LIST of completion items. + ;; IS-KWD, when non-nil, shows its keywords - otherwise its methods + ;; SHOW-CLASSES is the value of `idlwave-completion-show-classes'. + (catch 'exit + (if (or (null show-classes) ; don't wnat to see classes + (null class-selector) ; not a method call + (stringp class-selector)) ; the class is already known + ;; In these cases, we do not have to do anything + (throw 'exit list)) + + ;; The property and dots stuff currently only make sense with XEmacs + ;; because Emacs drops text properties when filling the *Completions* + ;; buffer. + (let* ((do-prop (and (featurep 'xemacs) (>= show-classes 0))) + (do-buf (not (= show-classes 0))) + (do-dots (featurep 'xemacs)) + (max (abs show-classes)) + (lmax (if do-dots (apply 'max (mapcar 'length list)))) + classes nclasses class-info space) + (mapcar + (lambda (x) + ;; get the classes + (setq classes + (if is-kwd + (idlwave-all-method-keyword-classes + method-selector x type-selector) + (idlwave-all-method-classes x type-selector))) + (setq nclasses (length classes)) + ;; Make the separator between item and class-info + (if do-dots + (setq space (concat " " (make-string (- lmax (length x)) ?.))) + (setq space " ")) + (if do-buf + ;; We do want info in the buffer + (if (<= nclasses max) + (setq class-info (concat + space + "<" (mapconcat 'identity classes ",") ">")) + (setq class-info (format "%s<%d classes>" space nclasses))) + (setq class-info nil)) + (when do-prop + ;; We do want properties + (setq x (copy-sequence x)) + (put-text-property 0 (length x) + 'help-echo (mapconcat 'identity classes " ") + x)) + (if class-info + (list x class-info) + x)) + list)))) + +(defun idlwave-attach-method-classes (list) + ;; Call idlwave-attach-classes with method parameters + (idlwave-attach-classes list nil idlwave-completion-show-classes)) +(defun idlwave-attach-keyword-classes (list) + ;; Call idlwave-attach-classes with keyword parameters + (idlwave-attach-classes list t idlwave-completion-show-classes)) + +;;---------------------------------------------------------------------- +;;---------------------------------------------------------------------- +;;---------------------------------------------------------------------- +;;---------------------------------------------------------------------- +;;---------------------------------------------------------------------- + +(defun idlwave-scroll-completions (&optional message) + "Scroll the completion window on this frame." + (let ((cwin (get-buffer-window "*Completions*" 'visible)) + (win (selected-window))) + (unwind-protect + (progn + (select-window cwin) + (condition-case nil + (scroll-up) + (error (if (and (listp last-command) + (nth 2 last-command)) + (progn + (select-window win) + (eval idlwave-complete-after-success-form)) + (set-window-start cwin (point-min))))) + (and message (message message))) + (select-window win)))) + +(defun idlwave-display-completion-list (list &optional message beg complete) + "Display the completions in LIST in the completions buffer and echo MESSAGE." + (unless (and (get-buffer-window "*Completions*") + (idlwave-local-value 'idlwave-completion-p "*Completions*")) + (move-marker idlwave-completion-mark beg) + (setq idlwave-before-completion-wconf (current-window-configuration))) + + (if (featurep 'xemacs) + (idlwave-display-completion-list-xemacs list) + (idlwave-display-completion-list-emacs list)) + + ;; Store a special value in `this-command'. When `idlwave-complete' + ;; finds this in `last-command', it will scroll the *Completions* buffer. + (setq this-command (list 'idlwave-display-completion-list message complete)) + + ;; Mark the completions buffer as created by cib + (idlwave-set-local 'idlwave-completion-p t "*Completions*") + + ;; Fontify the classes + (if (and idlwave-completion-fontify-classes + (consp (car list))) + (idlwave-completion-fontify-classes)) + + ;; Display the message + (message (or message "Making completion list...done"))) + +(defun idlwave-choose (function &rest args) + "Call FUNCTION as a completion chooser and pass ARGS to it." + (let ((completion-ignore-case t)) ; install correct value + (apply function args)) + (eval idlwave-complete-after-success-form-force)) + +(defun idlwave-restore-wconf-after-completion () + "Restore the old (before completion) window configuration." + (and idlwave-completion-restore-window-configuration + idlwave-before-completion-wconf + (set-window-configuration idlwave-before-completion-wconf))) + +(defun idlwave-set-local (var value &optional buffer) + "Set the buffer-local value of VAR in BUFFER to VALUE." + (save-excursion + (set-buffer (or buffer (current-buffer))) + (set (make-local-variable var) value))) + +(defun idlwave-local-value (var &optional buffer) + "Return the value of VAR in BUFFER, but only if VAR is local to BUFFER." + (save-excursion + (set-buffer (or buffer (current-buffer))) + (and (local-variable-p var (current-buffer)) + (symbol-value var)))) + +;; In XEmacs, we can use :activate-callback directly + +(defun idlwave-display-completion-list-xemacs (list) + (with-output-to-temp-buffer "*Completions*" + (display-completion-list list :activate-callback + 'idlwave-default-choose-completion))) + +(defun idlwave-default-choose-completion (&rest args) + "Execute `default-choose-completion' and then restore the win-conf." + (apply 'idlwave-choose 'default-choose-completion args)) + +;; In Emacs we have to replace the keymap in the *Completions* buffer +;; in order to install our wrappers. + +(defvar idlwave-completion-map nil + "Keymap for completion-list-mode with idlwave-complete.") + +(defun idlwave-display-completion-list-emacs (list) + "Display completion list and install the choose wrappers." + (with-output-to-temp-buffer "*Completions*" + (display-completion-list list)) + (save-excursion + (set-buffer "*Completions*") + (use-local-map + (or idlwave-completion-map + (setq idlwave-completion-map + (idlwave-make-modified-completion-map (current-local-map))))))) + +(defun idlwave-make-modified-completion-map (old-map) + "Replace `choose-completion' and `mouse-choose-completion' in OLD-MAP." + (let ((new-map (copy-keymap old-map))) + (substitute-key-definition + 'choose-completion 'idlwave-choose-completion new-map) + (substitute-key-definition + 'mouse-choose-completion 'idlwave-mouse-choose-completion new-map) + new-map)) + +(defun idlwave-choose-completion (&rest args) + "Choose the completion that point is in or next to." + (interactive) + (apply 'idlwave-choose 'choose-completion args)) + +(defun idlwave-mouse-choose-completion (&rest args) + "Click on an alternative in the `*Completions*' buffer to choose it." + (interactive "e") + (apply 'idlwave-choose 'mouse-choose-completion args)) + +;;---------------------------------------------------------------------- +;;---------------------------------------------------------------------- + +(defun idlwave-completion-fontify-classes () + "Goto the *Completions* buffer and fontify the class info." + (when (featurep 'font-lock) + (save-excursion + (set-buffer "*Completions*") + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "\\.*<[^>]+>" nil t) + (put-text-property (match-beginning 0) (match-end 0) + 'face 'font-lock-string-face)))))) + +(defun idlwave-uniquify (list) + (let (nlist) + (loop for x in list do + (add-to-list 'nlist x)) + nlist)) + +(defun idlwave-after-successful-completion (type slash &optional verify) + "Add `=' or `(' after successful completion of keyword and function. +Restore the pre-completion window configuration if possible." + (cond + ((eq type 'procedure) + nil) + ((eq type 'function) + (cond + ((equal idlwave-function-completion-adds-paren nil) nil) + ((or (equal idlwave-function-completion-adds-paren t) + (equal idlwave-function-completion-adds-paren 1)) + (insert "(")) + ((equal idlwave-function-completion-adds-paren 2) + (insert "()") + (backward-char 1)) + (t nil))) + ((eq type 'keyword) + (if (and idlwave-keyword-completion-adds-equal + (not slash)) + (progn (insert "=") t) + nil))) + + ;; Restore the pre-completion window configuration if this is safe. + + (if (or (eq verify 'force) ; force + (and + (get-buffer-window "*Completions*") ; visible + (idlwave-local-value 'idlwave-completion-p + "*Completions*") ; cib-buffer + (eq (marker-buffer idlwave-completion-mark) + (current-buffer)) ; buffer OK + (equal (marker-position idlwave-completion-mark) + verify))) ; pos OK + (idlwave-restore-wconf-after-completion)) + (move-marker idlwave-completion-mark nil) + (setq idlwave-before-completion-wconf nil)) + +(defun idlwave-routine-info-from-idlhelp (&optional arg) + "Make IDLHELP display the online documentation about the routine at point. +Sends the command `? MODULE' to the IDLWAVE-Shell. Shell must be running, +it does not autostart for this task." + (interactive "P") + (idlwave-routine-info arg 'external)) + +(defun idlwave-routine-info (&optional arg external) + "Display a routines calling sequence and list of keywords. +When point is on the name a function or procedure, or in the argument +list of a function or procedure, this command displays a help buffer +with the information. When called with prefix arg, enforce class +query. + +When point is on an object operator `->', display the class stored in +this arrow, if any (see `idlwave-store-inquired-class'). With a +prefix arg, the class property is cleared out." + + (interactive "P") + (idlwave-routines) + (if (string-match "->" (buffer-substring + (max (point-min) (1- (point))) + (min (+ 2 (point)) (point-max)))) + ;; Cursor is on an arrow + (if (get-text-property (point) 'idlwave-class) + ;; arrow has class property + (if arg + ;; Remove property + (save-excursion + (backward-char 1) + (when (looking-at ".?\\(->\\)") + (remove-text-properties (match-beginning 1) (match-end 1) + '(idlwave-class nil face nil)) + (message "Class property removed from arrow"))) + ;; Echo class property + (message "Arrow has text property identifying object to be class %s" + (get-text-property (point) 'idlwave-class))) + ;; No property found + (message "Arrow has no class text property")) + + ;; Not on an arrow... + (let* ((idlwave-query-class nil) + (idlwave-force-class-query (equal arg '(4))) + (module (idlwave-what-module))) + (cond ((car module) + (if external + (apply 'idlwave-search-online-help module) + (apply 'idlwave-display-calling-sequence module))) + (t + (error "Don't know which calling sequence to show.")))))) + +(defun idlwave-search-online-help (name &optional type class olh) + "Tell IDL to lookup CLASS::NAME with type TYPE in the online help. +If TYPE and CLASS are both nil, just look up NAME in the default help file." + ;; If only the IDLHELP application was better designed, so that + ;; we could make it open the right thing right away. As things are, + ;; we need to pipe the stuff through the help search engine, and we + ;; cannot enter a space. + (let* (extra book full string cmd) + + ;; Try to find a clue for the right help book + (if (and type (not olh)) + (setq olh (or (nth 6 (idlwave-rinfo-assq + name type class idlwave-builtin-routines)) + (nth 6 (idlwave-rinfo-assq + name type class idlwave-routines))))) + + ;; Sometimes the book is given as a symbol - make it a string + (if (and olh (symbolp olh)) (setq olh (symbol-name olh))) + (setq book (or olh "idl")) ; We need a default + ;; Add the FULL_PATH keyword if appropriate + (if (and (file-name-absolute-p book) + (file-exists-p book)) + (setq full ",/FULL_PATH") + (setq full "")) + + ;; We would like to add "Method" or so, but stupid IDL online help + ;; command treats a space as a separator and interprets the next thing as + ;; the book name. + ;; (setq extra (cond ((eq type 'kwd) " keyword") + ;; (class " method") + ;; ((eq type 'pro) " procedure") + ;; ((eq type 'fun) " function") + ;; (t ""))) + (setq extra "") + + ;; Methods are subitems of classes, the separator is a single `:' + (if (and name class (not (eq type 'kwd))) + (setq name (concat class ":" name))) + ;; FIXME: We used to use book, but in idl5.3, all help is in idl.hlp + (setq string (concat name extra) + cmd (format "ONLINE_HELP,'%s',BOOK='%s'%s" string "idl" full)) +; cmd (format "ONLINE_HELP,'%s',BOOK='%s'%s" string book full)) + (message "Sending to IDL: %s" cmd) (sit-for 2) + (idlwave-shell-send-command cmd))) + +(defun idlwave-resolve (&optional arg) + "Call RESOLVE on the module name at point. +Like `idlwave-routine-info', this looks for a routine call at point. +After confirmation in the minibuffer, it will use the shell to issue +a RESOLVE call for this routine, to attempt to make it defined and its +routine info available for IDLWAVE. If the routine is a method call, +both `class__method' and `class__define' will be tried. +With ARG, enforce query for the class of object methods." + (interactive "P") + (let* ((idlwave-query-class nil) + (idlwave-force-class-query (equal arg '(4))) + (module (idlwave-what-module)) + (name (idlwave-make-full-name (nth 2 module) (car module))) + (type (if (eq (nth 1 module) 'pro) "pro" "function")) + (resolve (read-string "Resolve: " (format "%s %s" type name))) + (kwd "") + class) + (if (string-match "\\(pro\\|function\\)[ \t]+\\(\\(.*\\)::\\)?\\(.*\\)" + resolve) + (setq type (match-string 1 resolve) + class (if (match-beginning 2) + (match-string 3 resolve) + nil) + name (match-string 4 resolve))) + (if (string= (downcase type) "function") + (setq kwd ",/is_function")) + + (cond + ((null class) + (idlwave-shell-send-command + (format "resolve_routine,'%s'%s" (downcase name) kwd) + 'idlwave-update-routine-info + nil t)) + (t + (idlwave-shell-send-command + (format "resolve_routine,'%s__define'%s" (downcase class) kwd) + (list 'idlwave-shell-send-command + (format "resolve_routine,'%s__%s'%s" + (downcase class) (downcase name) kwd) + '(idlwave-update-routine-info) + nil t)))))) + +(defun idlwave-find-module (&optional arg) + "Find the source code of an IDL module. +Works for modules for which IDLWAVE has routine info available. +The function offers as default the module name `idlwave-routine-info' would +use. With ARG force class query for object methods." + (interactive "P") + (let* ((idlwave-query-class nil) + (idlwave-force-class-query (equal arg '(4))) + (module (idlwave-what-module)) + (default (concat (idlwave-make-full-name (nth 2 module) (car module)) + (if (eq (nth 1 module) 'pro) "<p>" "<f>"))) + (list + (delq nil + (mapcar (lambda (x) + (if (eq 'system (car-safe (nth 3 x))) + ;; Take out system routines with no source. + nil + (cons + (concat (idlwave-make-full-name (nth 2 x) (car x)) + (if (eq (nth 1 x) 'pro) "<p>" "<f>")) + (cdr x)))) + (idlwave-routines)))) + (name (idlwave-completing-read + (format "Module (Default %s): " + (if default default "none")) + list)) + type class) + (if (string-match "\\`\\s-*\\'" name) + ;; Nothing, use the default. + (setq name default)) + (if (string-match "<[fp]>" name) + (setq type (substring name -2 -1) + name (substring name 0 -3))) + (if (string-match "\\(.*\\)::\\(.*\\)" name) + (setq class (match-string 1 name) + name (match-string 2 name))) + (setq name (idlwave-sintern-routine-or-method name class) + class (idlwave-sintern-class class) + type (cond ((equal type "f") 'fun) + ((equal type "p") 'pro) + (t t))) + (idlwave-do-find-module name type class))) + +(defun idlwave-do-find-module (name type class) + (let ((name1 (idlwave-make-full-name class name)) + source buf1 entry + (buf (current-buffer)) + (pos (point))) + (setq entry (idlwave-rinfo-assq name type class (idlwave-routines)) + source (nth 3 entry)) + (cond + ((or (null name) (equal name "")) + (error "Abort")) + ((null entry) + (error "Nothing known about a module %s" name1)) + ((eq (car source) 'system) + (error "Source code for system routine %s is not available." + name1)) + ((equal (cdr source) "") + (error "Source code for routine %s is not available." + name1)) + ((memq (car source) '(buffer lib compiled)) + (setq buf1 + (if (eq (car source) 'lib) + (idlwave-find-lib-file-noselet + (or (cdr source) + (format "%s.pro" (downcase name)))) + (idlwave-find-file-noselect (cdr source)))) + (pop-to-buffer buf1) + (goto-char 1) + (let ((case-fold-search t)) + (if (re-search-forward + (concat "^[ \t]*\\<" + (cond ((equal type "f") "function") + ((equal type "p") "pro") + (t "\\(pro\\|function\\)")) + "\\>[ \t]+" + (regexp-quote (downcase name1)) + "[^a-zA-Z0-9_$]") + nil t) + (goto-char (match-beginning 0)) + (pop-to-buffer buf) + (goto-char pos) + (error "Could not find routine %s" name1))))))) + +(defun idlwave-what-module () + "Return a default module for stuff near point. +Used by `idlwave-routine-info' and `idlwave-find-module'." + (idlwave-routines) + (let* ((where (idlwave-where)) + (cw (nth 2 where)) + (pro (car (nth 0 where))) + (func (car (nth 1 where))) + (this-word (idlwave-this-word "a-zA-Z0-9$_")) + (next-char (save-excursion (skip-chars-forward "a-zA-Z0-9$_") + (following-char))) + ) + (cond + ((and (eq cw 'procedure) + (not (equal this-word ""))) + (setq this-word (idlwave-sintern-routine-or-method + this-word (nth 2 (nth 3 where)))) + (list this-word 'pro + (idlwave-determine-class + (cons this-word (cdr (nth 3 where))) + 'pro))) + ((and (eq cw 'function) + (not (equal this-word "")) + (eq next-char ?\()) ; exclude arrays, vars. + (setq this-word (idlwave-sintern-routine-or-method + this-word (nth 2 (nth 3 where)))) + (list this-word 'fun + (idlwave-determine-class + (cons this-word (cdr (nth 3 where))) + 'fun))) + (func + (list func 'fun (idlwave-determine-class (nth 1 where) 'fun))) + (pro + (list pro 'pro (idlwave-determine-class (nth 0 where) 'pro))) + (t nil)))) + +(defun idlwave-fix-keywords (name type class keywords) + ;; This fixes the list of keywords. + (let ((case-fold-search t) + name1 type1) + + ;; If this is the OBJ_NEW function, try to figure out the class and use + ;; the keywords from the corresponding INIT method. + (if (and (equal name "OBJ_NEW") + (eq major-mode 'idlwave-mode)) + (let* ((bos (save-excursion (idlwave-beginning-of-statement) (point))) + (string (buffer-substring bos (point))) + (case-fold-search t) + class) + (and (string-match "obj_new([^'\"]*['\"]\\([a-zA-Z0-9_]+\\)" + string) + (setq class (idlwave-sintern-class (match-string 1 string))) + (setq keywords + (append keywords + (nth 5 (idlwave-rinfo-assq + (idlwave-sintern-method "INIT") + 'fun + class + (idlwave-routines)))))))) + + ;; If the class is `t', combine all keywords of all methods NAME + (when (eq class t) + (loop for x in (idlwave-routines) do + (and (nth 2 x) ; non-nil class + (or (and (eq (nth 1 x) type) ; default type + (eq (car x) name)) ; default name + (and (eq (nth 1 x) type1) ; backup type + (eq (car x) name1))) ; backup name + (mapcar (lambda (k) (add-to-list 'keywords k)) + (nth 5 x)))) + (setq keywords (idlwave-uniquify keywords))) + ;; Return the final list + keywords)) + +(defvar idlwave-rinfo-map (make-sparse-keymap)) +(define-key idlwave-rinfo-map + (if (featurep 'xemacs) [button2] [mouse-2]) + 'idlwave-mouse-active-rinfo) +(define-key idlwave-rinfo-map + (if (featurep 'xemacs) [button3] [mouse-3]) + 'idlwave-mouse-active-rinfo-right) +(defvar idlwave-popup-source) + +(defun idlwave-display-calling-sequence (name type class) + ;; Display the calling sequence of module NAME, type TYPE in class CLASS. + (let* ((entry (idlwave-rinfo-assq + name type class (idlwave-routines))) + (name (or (car entry) name)) + (class (or (nth 2 entry) class)) + (source (nth 3 entry)) + ;;(system (eq (car source) 'system)) + (calling-seq (nth 4 entry)) + (keywords (nth 5 entry)) + (olh (nth 6 entry)) + (help-echo3 " Button3: IDL Online Help") + (help-echo23 "Button2: Pop to source and back. Button3: IDL Online Help") + (col 0) + (data (list name type class (current-buffer) olh)) + (km-prop (if (featurep 'xemacs) 'keymap 'local-map)) + beg props win) + (setq keywords (idlwave-fix-keywords name type class keywords)) + (cond + ((null entry) + (error "No %s %s known" type name)) + ((or (null name) (equal name "")) + (error "No function or procedure call at point.")) + ((null calling-seq) + (error "Calling sequence of %s %s is not available" type name)) + (t + (save-excursion + (set-buffer (get-buffer-create "*Help*")) + (setq buffer-read-only nil) + (erase-buffer) + (set (make-local-variable 'idlwave-popup-source) nil) + (setq props (list 'mouse-face 'highlight + km-prop idlwave-rinfo-map + 'help-echo help-echo23 + 'data (cons 'usage data))) + (insert "Usage: ") + (setq beg (point)) + (insert (if class + (format calling-seq class name) + (format calling-seq name)) + "\n") + (add-text-properties beg (point) props) + + (insert "Keywords:") + (if (null keywords) + (insert " No keywords accepted.") + (setq col 9) + (mapcar + (lambda (x) + (if (>= (+ col 1 (length (car x))) + (window-width)) + (progn + (insert "\n ") + (setq col 9))) + (insert " ") + (setq beg (point) + props (list 'mouse-face 'highlight + km-prop idlwave-rinfo-map + 'data (cons 'keyword data) + 'help-echo help-echo3 + 'keyword (car x))) + (insert (car x)) + (add-text-properties beg (point) props) + (setq col (+ col 1 (length (car x))))) + keywords)) + (insert "\n") + + (insert "Origin: ") + (setq beg (point) + props (list 'mouse-face 'highlight + km-prop idlwave-rinfo-map + 'help-echo help-echo23 + 'data (cons 'origin data))) + (cond + ((eq (car source) 'system) + (insert "system routine")) + ((equal source '(lib)) + (insert (format "library file %s.pro" (downcase name)))) + ((eq (car source) 'lib) + (insert "library file ") + (insert (cdr source))) + ((eq (car source) 'buffer) + (insert "buffer visiting ") + (insert (abbreviate-file-name (cdr source)))) + ((eq (car source) 'compiled) + (insert "compiled from ") + (insert (cdr source)))) + (add-text-properties beg (point) props) + (setq buffer-read-only t)) + (display-buffer "*Help*") + (if (and (setq win (get-buffer-window "*Help*")) + idlwave-resize-routine-help-window) + (progn + (let ((ww (selected-window))) + (unwind-protect + (progn + (select-window win) + (enlarge-window (- (/ (frame-height) 2) + (window-height))) + (shrink-window-if-larger-than-buffer)) + (select-window ww))))))))) + +(defun idlwave-mouse-active-rinfo-right (ev) + (interactive "e") + (idlwave-mouse-active-rinfo ev 'right)) + +(defun idlwave-mouse-active-rinfo (ev &optional right) + (interactive "e") + (mouse-set-point ev) + (let (data id name type class buf keyword olh bufwin) + (setq data (get-text-property (point) 'data) + keyword (get-text-property (point) 'keyword) + id (car data) + name (nth 1 data) + type (nth 2 data) + class (nth 3 data) + buf (nth 4 data) + olh (nth 5 data) + bufwin (get-buffer-window buf t)) + (cond ((or (eq id 'usage) (eq id 'origin)) + (if right + (idlwave-search-online-help name type class) + (setq idlwave-popup-source (not idlwave-popup-source)) + (if idlwave-popup-source + (condition-case err + (idlwave-do-find-module name type class) + (error + (setq idlwave-popup-source nil) + (if (window-live-p bufwin) (select-window bufwin)) + (error (nth 1 err)))) + (if bufwin + (select-window bufwin) + (pop-to-buffer buf))))) + ((eq id 'keyword) + (if right + (idlwave-search-online-help keyword 'kwd class olh) + (error "Button2 not active for keywords")))))) + +;; ---------------------------------------------------------------------------- +;; +;; Additions for use with imenu.el and func-menu.el +;; (pop-up a list of IDL units in the current file). +;; + +(defun idlwave-prev-index-position () + "Search for the previous procedure or function. +Return nil if not found. For use with imenu.el." + (save-match-data + (cond + ((idlwave-find-key "\\<\\(pro\\|function\\)\\>" -1 'nomark)) + ;; ((idlwave-find-key idlwave-begin-unit-reg 1 'nomark) + (t nil)))) + +(defun idlwave-unit-name () + "Return the unit name. +Assumes that point is at the beginning of the unit as found by +`idlwave-prev-index-position'." + (forward-sexp 2) + (forward-sexp -1) + (let ((begin (point))) + (re-search-forward "[a-zA-Z][a-zA-Z0-9$_]+\\(::[a-zA-Z][a-zA-Z0-9$_]+\\)?") + (if (fboundp 'buffer-substring-no-properties) + (buffer-substring-no-properties begin (point)) + (buffer-substring begin (point))))) + +(defun idlwave-function-menu () + "Use `imenu' or `function-menu' to jump to a procedure or function." + (interactive) + (if (string-match "XEmacs" emacs-version) + (progn + (require 'func-menu) + (function-menu)) + (require 'imenu) + (imenu (imenu-choose-buffer-index)))) + +;; Here we kack func-menu.el in order to support this new mode. +;; The latest versions of func-menu.el already have this stuff in, so +;; we hack only if it is not already there. +(when (fboundp 'eval-after-load) + (eval-after-load "func-menu" + '(progn + (or (assq 'idlwave-mode fume-function-name-regexp-alist) + (not (boundp 'fume-function-name-regexp-idl)) ; avoid problems + (setq fume-function-name-regexp-alist + (cons '(idlwave-mode . fume-function-name-regexp-idl) + fume-function-name-regexp-alist))) + (or (assq 'idlwave-mode fume-find-function-name-method-alist) + (not (fboundp 'fume-find-next-idl-function-name)) ; avoid problems + (setq fume-find-function-name-method-alist + (cons '(idlwave-mode . fume-find-next-idl-function-name) + fume-find-function-name-method-alist)))))) + +(defun idlwave-edit-in-idlde () + "Edit the current file in IDL Development environment." + (interactive) + (start-process "idldeclient" nil + idlwave-shell-explicit-file-name "-c" "-e" + (buffer-file-name) "&")) + +(defun idlwave-launch-idlhelp () + "Start the IDLhelp application." + (interactive) + (start-process "idlhelp" nil idlwave-help-application)) + +;; Menus - using easymenu.el +(defvar idlwave-mode-menu-def + `("IDLWAVE" + ["PRO/FUNC menu" idlwave-function-menu t] + ("Motion" + ["Subprogram Start" idlwave-beginning-of-subprogram t] + ["Subprogram End" idlwave-end-of-subprogram t] + ["Block Start" idlwave-beginning-of-block t] + ["Block End" idlwave-end-of-block t] + ["Up Block" idlwave-backward-up-block t] + ["Down Block" idlwave-down-block t] + ["Skip Block Backward" idlwave-backward-block t] + ["Skip Block Forward" idlwave-forward-block t]) + ("Mark" + ["Subprogram" idlwave-mark-subprogram t] + ["Block" idlwave-mark-block t] + ["Header" idlwave-mark-doclib t]) + ("Format" + ["Indent Subprogram" idlwave-indent-subprogram t] + ["(Un)Comment Region" idlwave-toggle-comment-region "C-c ;"] + ["Continue/Split line" idlwave-split-line t] + "--" + ["Toggle Auto Fill" idlwave-auto-fill-mode :style toggle + :selected (symbol-value idlwave-fill-function)]) + ("Templates" + ["Procedure" idlwave-procedure t] + ["Function" idlwave-function t] + ["Doc Header" idlwave-doc-header t] + ["Log" idlwave-doc-modification t] + "--" + ["Case" idlwave-case t] + ["For" idlwave-for t] + ["Repeat" idlwave-repeat t] + ["While" idlwave-while t] + "--" + ["Close Block" idlwave-close-block t]) + ("Completion / RInfo" + ["Complete" idlwave-complete t] + ("Complete Special" + ["1 Procedure Name" (idlwave-complete 'procedure) t] + ["2 Procedure Keyword" (idlwave-complete 'procedure-keyword) t] + "--" + ["3 Function Name" (idlwave-complete 'function) t] + ["4 Function Keyword" (idlwave-complete 'function-keyword) t] + "--" + ["5 Procedure Method Name" (idlwave-complete 'procedure-method) t] + ["6 Procedure Method Keyword" (idlwave-complete 'procedure-method-keyword) t] + "--" + ["7 Function Method Name" (idlwave-complete 'function-method) t] + ["8 Function Method Keyword" (idlwave-complete 'function-method-keyword) t] + "--" + ["9 Class Name" idlwave-complete-class t]) + "--" + ["Show Routine Info" idlwave-routine-info t] + ["Show Routine Doc with IDLHELP" idlwave-routine-info-from-idlhelp t] + "--" + ["Find Routine Source" idlwave-find-module t] + "--" + ["Update Routine Info" idlwave-update-routine-info t] + "--" + "IDL Library Routine Info" + ["Select Library Directories" idlwave-create-libinfo-file t] + ["Scan Directories" (idlwave-update-routine-info '(16)) idlwave-scanned-lib-directories]) + "--" + ("External" + ["Generate IDL tags" idlwave-make-tags t] + ["Start IDL shell" idlwave-shell t] + ["Edit file in IDLDE" idlwave-edit-in-idlde t] + ["Launch IDL Help" idlwave-launch-idlhelp t]) + "--" + ("Customize" + ["Browse IDLWAVE Group" idlwave-customize t] + "--" + ["Build Full Customize Menu" idlwave-create-customize-menu + (fboundp 'customize-menu-create)]) + ("Documentation" + ["Describe Mode" describe-mode t] + ["Abbreviation List" idlwave-list-abbrevs t] + "--" + ["Commentary in idlwave.el" idlwave-show-commentary t] + ["Commentary in idlwave-shell.el" idlwave-shell-show-commentary t] + "--" + ["Info" idlwave-info t] + "--" + ["Launch IDL Help" idlwave-launch-idlhelp t]))) + +(defvar idlwave-mode-debug-menu-def + '("Debug" + ["Start IDL shell" idlwave-shell t] + ["Save and .RUN buffer" idlwave-shell-save-and-run + (and (boundp 'idlwave-shell-automatic-start) + idlwave-shell-automatic-start)])) + +(if (or (featurep 'easymenu) (load "easymenu" t)) + (progn + (easy-menu-define idlwave-mode-menu idlwave-mode-map + "IDL and WAVE CL editing menu" + idlwave-mode-menu-def) + (easy-menu-define idlwave-mode-debug-menu idlwave-mode-map + "IDL and WAVE CL editing menu" + idlwave-mode-debug-menu-def))) + +(defun idlwave-customize () + "Call the customize function with idlwave as argument." + (interactive) + ;; Try to load the code for the shell, so that we can customize it + ;; as well. + (or (featurep 'idlwave-shell) + (load "idlwave-shell" t)) + (customize-browse 'idlwave)) + +(defun idlwave-create-customize-menu () + "Create a full customization menu for IDLWAVE, insert it into the menu." + (interactive) + (if (fboundp 'customize-menu-create) + (progn + ;; Try to load the code for the shell, so that we can customize it + ;; as well. + (or (featurep 'idlwave-shell) + (load "idlwave-shell" t)) + (easy-menu-change + '("IDLWAVE") "Customize" + `(["Browse IDLWAVE group" idlwave-customize t] + "--" + ,(customize-menu-create 'idlwave) + ["Set" Custom-set t] + ["Save" Custom-save t] + ["Reset to Current" Custom-reset-current t] + ["Reset to Saved" Custom-reset-saved t] + ["Reset to Standard Settings" Custom-reset-standard t])) + (message "\"IDLWAVE\"-menu now contains full customization menu")) + (error "Cannot expand menu (outdated version of cus-edit.el)"))) + +(defun idlwave-show-commentary () + "Use the finder to view the file documentation from `idlwave.el'." + (interactive) + (require 'finder) + (finder-commentary "idlwave.el")) + +(defun idlwave-shell-show-commentary () + "Use the finder to view the file documentation from `idlwave-shell.el'." + (interactive) + (require 'finder) + (finder-commentary "idlwave-shell.el")) + +(defun idlwave-info () + "Read documentation for IDLWAVE in the info system." + (interactive) + (require 'info) + (Info-goto-node "(idlwave)")) + +(defun idlwave-list-abbrevs (arg) + "Show the code abbreviations define in IDLWAVE mode. +This lists all abbrevs where the replacement text differs from the input text. +These are the ones the users want to learn to speed up their writing. + +The function does *not* list abbrevs which replace a word with itself +to call a hook. These hooks are used to change the case of words or +to blink the matching `begin', and the user does not need to know them. + +With arg, list all abbrevs with the corresponding hook. + +This function was written since `list-abbrevs' looks terrible for IDLWAVE mode." + + (interactive "P") + (let ((table (symbol-value 'idlwave-mode-abbrev-table)) + abbrevs + str rpl func fmt (len-str 0) (len-rpl 0)) + (mapatoms + (lambda (sym) + (if (symbol-value sym) + (progn + (setq str (symbol-name sym) + rpl (symbol-value sym) + func (symbol-function sym)) + (if arg + (setq func (prin1-to-string func)) + (if (and (listp func) (stringp (nth 2 func))) + (setq rpl (concat "EVAL: " (nth 2 func)) + func "") + (setq func ""))) + (if (or arg (not (string= rpl str))) + (progn + (setq len-str (max len-str (length str))) + (setq len-rpl (max len-rpl (length rpl))) + (setq abbrevs (cons (list str rpl func) abbrevs))))))) + table) + ;; sort the list + (setq abbrevs (sort abbrevs (lambda (a b) (string< (car a) (car b))))) + ;; Make the format + (setq fmt (format "%%-%ds %%-%ds %%s\n" len-str len-rpl)) + (with-output-to-temp-buffer "*Help*" + (if arg + (progn + (princ "Abbreviations and Actions in IDLWAVE-Mode\n") + (princ "=========================================\n\n") + (princ (format fmt "KEY" "REPLACE" "HOOK")) + (princ (format fmt "---" "-------" "----"))) + (princ "Code Abbreviations and Templates in IDLWAVE-Mode\n") + (princ "================================================\n\n") + (princ (format fmt "KEY" "ACTION" "")) + (princ (format fmt "---" "------" ""))) + (mapcar + (lambda (list) + (setq str (car list) + rpl (nth 1 list) + func (nth 2 list)) + (princ (format fmt str rpl func))) + abbrevs))) + ;; Make sure each abbreviation uses only one display line + (save-excursion + (set-buffer "*Help*") + (setq truncate-lines t))) + +(run-hooks 'idlwave-load-hook) + +(provide 'idlwave) + +;;; idlwave.el ends here +