Initial Commit
[packages] / xemacs-packages / oo-browser / br-python-ft.el
1 ;;!emacs
2 ;;
3 ;; FILE:         br-python-ft.el
4 ;; SUMMARY:      Python OO-Browser class and member functions.
5 ;; USAGE:        GNU Emacs Lisp Library
6 ;; KEYWORDS:     python, oop, tools
7 ;;
8 ;; AUTHOR:       Harri Pasanen / Bob Weiner
9 ;;               based on Smalltalk and C++ OO-Browsers 
10 ;; ORG:          BeOpen.com
11 ;;
12 ;; ORIG-DATE:    5-Apr-96
13 ;; LAST-MOD:     10-May-01 at 19:21:10 by Bob Weiner
14 ;;
15 ;; Copyright (C) 1996, 1997, 1998  BeOpen.com
16 ;; See the file BR-COPY for license information.
17 ;;
18 ;; This file is part of the OO-Browser.
19 ;;
20 ;; DESCRIPTION:  
21 ;;    There may still be traces of C++ origin in this file.
22 ;; DESCRIP-END.
23
24 ;;; ************************************************************************
25 ;;; Other required Elisp libraries
26 ;;; ************************************************************************
27
28 (require 'br-c-ft)
29 (require 'br-python)
30
31 ;;; ************************************************************************
32 ;;; Public variables
33 ;;; ************************************************************************
34
35 (defconst python-feature-entry-regexp
36   (concat br-feature-type-regexp " \\(.+\\.\\)?\\([^\t\n\r]*[^ \t\n\r]\\)")
37   "Regexp matching a Python feature entry string from a browser listing buffer.")
38
39 (defvar python-import-dirs
40   (if (stringp (getenv "PYTHONPATH"))
41       (mapcar 'file-name-as-directory (split-string (getenv "PYTHONPATH") ":"))
42     '("/usr/local/lib/python/"))
43   "Ordered list of module directories searched by the python interpreter.
44 Each directory must end with a directory separator.")
45
46 (defconst python-type-tag-separator "@"
47   "String that separates a tag's type from its normalized definition form.
48 This should be a single character which is unchanged when quoted for use as a
49 literal in a regular expression.")
50
51 (defconst python-tag-fields-regexp
52   ;; The \\\\? below is necessary because we sometimes use this expression to
53   ;; test against a string that has ben regexp-quoted and some of the
54   ;; characters in br-feature-type-regexp will then be preceded by \\.
55   (format "^\\([^%s \n]+\\)%s\\\\?\\(%s \\)\\([^%s\n]+\\)%s"
56           python-type-tag-separator python-type-tag-separator br-feature-type-regexp
57           python-type-tag-separator python-type-tag-separator)
58  "Regexp matching the fields of a Python feature tag line.
59 Group 1 is the class of the feature.  Group 2 is the prefix preceding the
60 feature when displayed within a listing buffer.  Group 3 is the feature name.
61 The feature definition signature begins at the end of the regexp match,
62 i.e. (match-end 0), and goes to the end of the string or line.")
63
64 ;;; ************************************************************************
65 ;;; Public functions
66 ;;; ************************************************************************
67
68 (defun python-add-default-classes ()
69   (if br-c-tags-flag
70       (c-add-default-classes)
71     ;; Add to categorize module functions
72     (br-add-default-classes '("[function]")))
73   (br-add-default-classes '("[global]" "[module]" "[package]")))
74
75 (defun python-feature-implementors (name)
76   "Return unsorted list of Python feature tags which implement feature NAME."
77   (nconc
78    (python-feature-matches (concat "^" (regexp-quote name) "$"))
79    (python-feature-matches (concat "\\." (regexp-quote name) "$"))))
80
81 (defun python-feature-signature-to-name (feature-sig-or-tag &optional with-class for-display)
82   "Extracts the feature name from FEATURE-SIG-OR-TAG.
83 The feature's class name is dropped from feature-sig-or-tag unless optional
84 WITH-CLASS is non-nil.  If optional FOR-DISPLAY is non-nil, a feature type
85 character is prepended to the name for display in a browser listing."
86   (if (br-feature-tag-p feature-sig-or-tag)
87       (br-feature-tag-name feature-sig-or-tag with-class for-display)
88     (let ((name))
89       (cond
90        ;; member
91        ((string-match python-tag-fields-regexp feature-sig-or-tag)
92         (setq name (substring feature-sig-or-tag
93                               (match-beginning (if for-display 2 3))
94                               (match-end 3)))
95         (if with-class
96             (setq name (concat
97                         (substring feature-sig-or-tag
98                                    (match-beginning 1) (match-end 1))
99                         "::" name)))
100         ;; Remove any trailing whitespace.
101         (br-delete-space name))
102        ;;
103        ;; unknown
104        (t;; Remove any trailing whitespace and add display prefix.
105         (setq name (br-delete-space feature-sig-or-tag))
106         (if for-display (concat "- " name) name))))))
107
108 (defun python-scan-features ()
109   "Return reverse ordered list of current module's global attributes and function definitions.
110 Assume point is at the beginning of a widened buffer."
111   (save-excursion
112     (let ((case-fold-search)
113           (features) globals class name feat start)
114
115       ;; Record globals
116       (while (re-search-forward python-global-def nil t)
117         (setq start (match-beginning 0))
118         (if (python-within-string-p)
119             ;; ignore any feature found and skip to the end of the string
120             (re-search-forward python-multi-line-string-delimiter nil t)
121           ;; otherwise record the globals found
122           (setq globals (nreverse (python-scan-globals))
123                 feat (br-buffer-substring start (point)))
124           (setq features
125                 (nconc
126                  (mapcar
127                   (function (lambda (global)
128                               (python-feature-normalize feat "[global]" global)))
129                   globals)
130                  features))))
131
132       ;; Record non-member functions
133       (goto-char (point-min))
134       (while (re-search-forward python-routine-def nil t)
135         (setq class "[function]"
136               name (br-buffer-substring
137                     (match-beginning python-feature-name-grpn)
138                     (match-end python-feature-name-grpn))
139               feat (python-feature-normalize
140                     (br-buffer-substring (match-beginning 0) (match-end 0))
141                     class name))
142         (if (python-within-string-p)
143             ;; ignore any feature found and skip to the end of the string
144             (re-search-forward python-multi-line-string-delimiter nil t)
145           ;; otherwise record the function found
146           (setq features (cons feat features))))
147       features)))
148
149 (defun python-to-definition (&optional other-win)
150   "If point is on an import statement, look for the module file.
151 With OTHER-WIN non-nil, show it in another window."
152   (interactive)
153   (cond
154    ((python-import-file other-win))
155    (t   (beep)
156         (message
157          "(OO-Browser):  Select an import statement to display its source.")
158         nil)))
159
160 (defun python-insert-entry-info ()
161   "Insert `python-docstring' into the current buffer at point."
162   (interactive)
163   (insert python-docstring))
164
165 (defalias 'python-insert-class-info 'python-insert-entry-info)
166
167 ;; Optional arg below is solely for call compatibility when called with
168 ;; an argument from br-store-class-info.  The argument is not used under
169 ;; Python.
170 (defun python-store-entry-info (&optional entry)
171   "Set `python-docstring' to the documentation for the listing entry at point and return it.
172 Return nil if no documentation is available."
173  (setq python-docstring (python-lookup-docstring)))
174
175 (defalias 'python-store-class-info 'python-store-entry-info)
176
177 ;;; ************************************************************************
178 ;;; Private functions
179 ;;; ************************************************************************
180
181 (defun python-lookup-docstring ()
182   "Look up and return a docstring for the browser listing entry at point or nil."
183   (let ((entry-name)
184         (entry-type)
185         (file-name)
186         (feature-tag)
187         (docstring)
188         (pydoc-interface-p (fboundp 'pydoc-commands)))
189
190     (cond ((br-at-feature-p)
191            (setq feature-tag (br-feature-get-tag)
192                  entry-name (br-feature-tag-name feature-tag)
193                  file-name (br-feature-tag-path feature-tag))
194            ;; Entry-type may be: [package], [module], [global], [function]
195            ;; or a [method]
196            (setq entry-type (br-feature-tag-class feature-tag))
197            (if (not (br-member entry-type '("[package]" "[module]" "[global]"
198                                             "[function]")))
199                (setq entry-type "[method]")))
200
201           ((and (setq entry-name (br-find-class-name))
202                 (br-class-in-table-p entry-name))
203            (setq file-name (br-class-path entry-name))
204            ;; entry type may be a class or interface; consider them the
205            ;; same for documentation extraction purposes
206            (setq entry-type "[class]"))
207
208           (t (error "(OO-Browser):  Entry referenced but not defined in the Environment.")))
209
210     (if (and file-name (not (string-equal entry-type "[global]")))
211         (progn
212           (if pydoc-interface-p
213               (condition-case ()
214                   (save-window-excursion
215                     (require 'pydoc)
216                     (cond ((string-equal entry-type "[package]")
217                            (if (or (not (fboundp 'pydoc-package-list))
218                                    (br-member entry-name (pydoc-package-list)))
219                                (progn (pydoc-packages entry-name)
220                                       (setq docstring (buffer-string)))))
221
222                           ((string-equal entry-type "[module]")
223                            (if (or (not (fboundp 'pydoc-module-list))
224                                    (br-member entry-name (pydoc-module-list)))
225                                (progn (pydoc-modules entry-name)
226                                       (setq docstring (buffer-string)))))
227
228                           ((br-member entry-type '("[function]" "[method]"))
229                            (pydoc-help entry-name)
230                            (setq docstring (buffer-string)))))
231                 (error
232                  (setq docstring nil))))
233           (if (null docstring)
234               (setq docstring
235                     (python-get-docstring-from-source
236                      entry-name entry-type feature-tag file-name)))))
237
238     docstring))
239
240 (defun python-get-docstring-from-source (entry-name entry-type feature-tag file-name)
241   "Scan source for ENTRY-NAME's docstring using ENTRY-TYPE, FEATURE-TAG and FILE-NAME.
242 ENTRY-TYPE must a string, one of [package], [module], [function], [method] or 
243 \[class]."
244   (let ((no-kill (get-file-buffer file-name))
245         (docstring))
246     (save-restriction
247       (save-excursion
248         (cond (no-kill
249                (set-buffer no-kill))
250               ((not (file-directory-p file-name))
251                (br-insert-file-contents file-name)))
252         (widen)
253         (goto-char (point-min))
254         (cond ((string-equal entry-type "[class]")
255                (if (re-search-forward (python-class-definition-regexp
256                                        entry-name) nil t)
257                    (progn
258                      ;; Skip over any superclass list
259                      (if (char-equal (preceding-char) ?\()
260                          (progn (backward-char 1)
261                                 (forward-list)))
262                      (setq docstring (python-extract-docstring)))))
263
264               ((br-member entry-type '("[function]" "[method]"))
265                (if (python-feature-locate-p feature-tag)
266                    ;; Skip past argument list
267                    (if (> (skip-chars-forward "^\(") 0)
268                        (progn
269                          (forward-list)
270                          (setq docstring (python-extract-docstring))))))
271
272               ((string-equal entry-type "[module]")
273                (setq docstring (python-extract-docstring)))
274
275               ((string-equal entry-type "[package]")
276                (setq file-name (expand-file-name "__init__.py" file-name)
277                      no-kill (get-file-buffer file-name))
278                (if no-kill
279                    (set-buffer no-kill)
280                  (br-insert-file-contents file-name))
281                (setq docstring (python-extract-docstring))))))
282
283     (if (not no-kill)
284         (kill-buffer *br-tmp-buffer*))
285     docstring))
286
287 (defun python-extract-docstring ()
288   "Return the documentation string after point, or nil if none."
289   (skip-chars-forward ": \t")
290   (if (looking-at
291        (concat python-empty-line "*[ \t]*" python-string-start))
292       (let ((start (match-end 0))
293             (end-quote (br-buffer-substring (match-beginning 4) (match-end 4))))
294         (goto-char start)
295         (if (search-forward end-quote nil t)
296             (br-buffer-substring start (match-beginning 0))))))
297
298 (defconst python-string-start
299   (concat 
300    "\\("
301    "'''"                ; triple single-quoted
302    "\\|"                ; or
303    "\"\"\""             ; triple double-quoted
304    "\\|"                ; or
305    "'"                  ; single-quoted, not empty
306    "\\|"                ; or
307    "\""                 ; double-quoted, not empty
308    "\\)")
309   "regexp matching python string literal starting quotes")
310
311 (defconst python-empty-line
312   "\\(\\([ \t]*[\n\r]+\\)\\|\\([ \t]*#.*$\\)\\)"
313   "Regexp matching an empty or python comment line.")
314
315 (defun python-within-comment-p ()
316   "Return non-nil if point is within a Python comment."
317   (save-excursion
318     (and (re-search-backward "#\\|\n" nil t)
319          (not (looking-at "\n")))))
320
321 (defun python-within-string-p ()
322   "Return non-nil if point is within a multi-line python string."
323   (save-excursion
324     (let ((count 0))
325       (while (re-search-forward python-multi-line-string-delimiter nil t)
326         (setq count (1+ count)))
327       (= (% count 2) 1))))
328
329 (defun python-feature-map-tags (function regexp)
330   "Apply FUNCTION to all current feature tags that match REGEXP and return a list of the results."
331   (let ((identifier-chars (concat "[" python-identifier-chars ".]*"))
332         (case-fold-search))
333     ;; Ensure handle "^" and "$" meta-chars.
334     (setq regexp
335         (concat (format "\\`%s " br-feature-type-regexp)
336                 (if (equal (substring regexp 0 1) "^")
337                     (progn (setq regexp (substring regexp 1)) nil)
338                   identifier-chars)
339                 (if (equal (substring regexp -1) "$")
340                     (substring regexp 0 -1)
341                   (concat regexp identifier-chars))
342                   "\\'"))
343     (br-feature-map-tags function regexp)))
344
345 (defun python-feature-matches (regexp)
346   "Return an unsorted list of feature tags whose names match in part or whole to REGEXP.
347 ^ and $ characters may be used to match to the beginning and end of a feature name,
348 respectively."
349   (python-feature-map-tags 'identity regexp))
350
351 (defun python-feature-normalize (feature class name)
352   "Return a feature tag based on FEATURE, CLASS and NAME."
353   (setq class (br-delete-space class))
354   (setq name (if (equal class "[global]")
355                  (concat "= " name)
356                (concat "- " name)))
357   (let* ((len (length feature))
358          (normal-feature (make-string len ?\ ))
359          (n 0) (i 0)
360          (space-regexp "[ \t\n\r]+")
361          (original-syntax-table (syntax-table))
362          chr)
363     (unwind-protect
364         (progn
365           (set-syntax-table text-mode-syntax-table)
366           (while (< i len)
367             (setq chr (aref feature i)) 
368             (cond
369              ;; Convert sequences of space characters to a single space.
370              ;; GNU Emacs doesn't support optional syntax-table arg to
371              ;; `char-syntax'.
372              ((eq (char-syntax chr) ?\ )
373               (if (string-match space-regexp feature i)
374                   (progn (setq i (match-end 0))
375                          (if (not (and (> n 0)
376                                        (eq (aref normal-feature (1- n)) ?\ )))
377                              (setq n (1+ n))))
378                 (setq i (1+ i)
379                       n (1+ n))))
380              ;;
381              ;; Remove # comments
382              ((eq chr ?#)
383               (setq i (1+ i))
384               (while (and (< i len) (not (eq (aref feature i) ?\n)))
385                 (setq i (1+ i))))
386              (t ;; Normal character
387               (aset normal-feature n chr)
388               (setq i (1+ i)
389                     n (1+ n)))))
390           (concat class python-type-tag-separator 
391                   name python-type-tag-separator 
392                   (br-delete-space (substring normal-feature 0 n))))
393       (set-syntax-table original-syntax-table))))
394
395 (defun python-files-with-source (class)
396   "Use CLASS to compute set of files that match to a Python source file regexp.
397 Return as a list."
398   (let ((file (if class (br-class-path class) buffer-file-name)))
399     (and file
400          (let* ((src-file-regexp (concat "^" (br-filename-head file)
401                                          python-code-file-regexp))
402                 (dir (file-name-directory file))
403                 (files (directory-files dir nil src-file-regexp)))
404            (mapcar (function (lambda (f) (expand-file-name f dir)))
405                    files)))))
406
407 (defun python-find-ancestors-feature (class-list ftr-pat &optional other-win)
408   "Scan ancestors of CLASS-LIST and show routine definition matching FTR-PAT."
409   ;; If no class, search for a non-member function.
410   (or class-list (setq class-list '(nil)))
411   (br-feature-display class-list ftr-pat other-win))
412
413 (defun python-find-class-name ()
414   "Return current word as a potential class name."
415   (save-excursion
416     (let* ((start)
417            (ignore "\]\[ \t\n\r\f\;,.\(\){}*&-")
418            (pat (concat "^" ignore)))
419       (forward-char 1)
420       (skip-chars-backward ignore)
421       (skip-chars-backward pat)
422       (setq start (point))
423       (skip-chars-forward (concat pat ":"))
424       (br-buffer-substring start (point)))))
425
426 (defun python-output-feature-tags (routine-file routine-tags-list)
427   "Write Python ROUTINE-FILE's ROUTINE-TAGS-LIST into `br-feature-tags-file'.
428 Assume `br-feature-tags-init' has been called."
429   (interactive)
430   (save-excursion
431     (br-feature-set-tags-buffer)
432     (goto-char 1)
433     ;; Delete any prior routine tags associated with routine-file
434     (if (search-forward routine-file nil 'end)
435         (progn (forward-line -1)
436                (let ((start (point)))
437                  (search-forward "\^L" nil 'end 2)
438                  (backward-char 1)
439                  (delete-region start (point)))))
440     (if routine-tags-list
441         (progn (insert "\^L\n")
442                ;; Quote pathname to avoid read errors on MS OSes.
443                (prin1 routine-file (current-buffer))
444                (insert "\n")
445                (mapcar (function (lambda (tag) (insert tag "\n")))
446                        routine-tags-list)))))
447
448 (defun python-find-module-name ()
449   "Return current word as a potential module name."
450   (save-excursion
451     (let ((start))
452       (forward-char 1)
453       (skip-chars-backward python-identifier-chars)
454       (setq start (point))
455       (skip-chars-forward python-identifier-chars)
456       (br-buffer-substring start (point)))))
457
458 (defun python-import-file (&optional other-win)
459   "If point is on an import module line, display the module, method or function name at point.
460 With optional OTHER-WIN non-nil, display it in the other window.
461
462 Return non-nil iff point is on an import file line, even if a matching entry
463 is not found.  When found return the full pathname to the import entry,
464 otherwise return t.
465
466 Look for import files within `python-import-dirs' and any Environment
467 directory."
468   (let ((opoint (point)))
469     (beginning-of-line)
470     (cond ((looking-at python-import-modules-regexp)
471            (if (< (match-end 0)
472                   (max opoint (match-beginning python-import-name-grpn)))
473                ;; Have to avoid selecting anything within a # comment here.
474                (goto-char (match-beginning python-import-name-grpn))
475              (goto-char
476               (max opoint (match-beginning python-import-name-grpn))))
477            (let* ((import-name (python-find-module-name))
478                   (path (python-import-pathname import-name)))
479              ;; If found, display file
480              (python-display-module import-name path other-win)
481              (or path t)))
482           ((looking-at python-import-functions-regexp)
483            (if (< (match-end 0)
484                   (max opoint (match-beginning python-import-name-grpn)))
485                ;; Have to avoid selecting anything within a # comment here.
486                (goto-char (match-beginning python-import-name-grpn))
487              (goto-char
488               (max opoint (match-beginning python-import-name-grpn))))
489            (setq opoint (point))
490            (let* ((end-module-name (match-end python-import-name-grpn))
491                   (module-name 
492                    (br-buffer-substring
493                     (match-beginning python-import-name-grpn)
494                     (match-end python-import-name-grpn)))
495                   (import-name (python-find-module-name))
496                   (path (python-import-pathname module-name)))
497              ;; If found, display file
498              (if (python-display-module module-name path other-win)
499                  (if (or (<= opoint end-module-name)
500                          (equal import-name "import")
501                          (equal import-name ""))
502                      nil
503                    (if (re-search-forward
504                         (concat "^[ \t]*\\(class\\|def\\|\\)"
505                                 "\\(" (regexp-quote import-name) "\\)"
506                                 "[^" python-identifier-chars "]")
507                         nil t)
508                        (goto-char (match-beginning 2))
509                      (beep)
510                      (message "(OO-Browser):  Found module `%s' but not member `%s'."
511                               module-name import-name))))
512              (or path t)))
513           (t (goto-char opoint)
514              nil))))
515
516 (defun python-display-module (module-name path other-win)
517   "Display file associated with MODULE-NAME and PATH in OTHER-WIN (if non-nil).
518 Return t if file is displayed, nil otherwise."
519   (if path
520       (if (file-readable-p path)
521           (progn
522             (funcall br-edit-file-function path other-win)
523             (widen)
524             (goto-char (point-min))
525             (if (not (fboundp 'br-lang-mode))
526                 (python-mode-setup))
527             (br-major-mode)
528             t)
529         (beep)
530         (message "(OO-Browser):  Module `%s' is unreadable." path)
531         nil)
532     (beep)
533     (message "(OO-Browser):  Cannot find module `%s'." module-name)
534     nil))
535
536 (defun python-import-pathname (import-name)
537   "Return the full pathname to a Python IMPORT-NAME or nil if none.
538 Look for import files within `python-import-dirs' and any Environment
539 directory."
540   (if (not (stringp import-name))
541       (error "(python-import-pathname): Invalid import name, `%s'" import-name))
542   (if (string-match "\\.py\\'" import-name)
543       (setq import-name (substring import-name 0 (match-beginning 0))))
544   ;; Convert import-name a.b.c to pathname form, a/b/c.
545   (setq import-name (hypb:replace-match-string
546                      "\\." import-name
547                      (file-name-as-directory "/")
548                      t))
549   (setq import-name (concat import-name ".py"))
550   (let ((dir-list (append python-lib-search-dirs python-sys-search-dirs 
551                           python-import-dirs))
552         (found)
553         path)
554     (if buffer-file-name
555         (setq dir-list (cons (file-name-directory buffer-file-name)
556                              dir-list)))
557     (while (and (not found) dir-list)
558       (setq path (expand-file-name import-name (car dir-list)))
559       (or (setq found (file-exists-p path))
560           (setq dir-list (cdr dir-list))))
561     ;;
562     ;; If not found in normal include dirs, check all Env paths also.
563     ;;
564     (if (not found)
565         (let ((paths (delq nil (hash-map 'cdr br-paths-htable))))
566           (while (and (not found) paths)
567             (setq path (car paths))
568             (if (string-equal (file-name-nondirectory path) import-name)
569                 (setq found t paths nil)
570               (setq paths (cdr paths))))))
571     (if found path)))
572
573 (defun python-scan-ancestors-feature (class-list ftr-pat &optional other-win)
574   "Display routine definition derived from CLASS-LIST, matching FTR-PAT.
575 Scan files with same base name as class file."
576   (let  ((classes class-list)
577          (found-ftr)
578          (code-def-files)
579          (file)
580          (ftr-sig-regexp)
581          (class))
582     (if (null class-list)
583         nil
584       (while (and (not found-ftr) classes)
585         (setq class (car classes)
586               code-def-files (python-files-with-source class)
587               ftr-sig-regexp (funcall ftr-pat class))
588         (while (and (setq file (car code-def-files))
589                     (not (setq found-ftr
590                                (br-feature-found-p file ftr-sig-regexp
591                                                    nil other-win t))))
592           (setq code-def-files (cdr code-def-files)))
593         (setq classes (if found-ftr nil (cdr classes))))
594       (if found-ftr
595           (or class t)
596         (python-scan-ancestors-feature
597          (apply 'append (mapcar (function (lambda (cl) (br-get-parents cl)))
598                                 class-list))
599          ftr-pat)))))
600
601 (defun python-scan-features-in-class (class start end)
602   "Return reverse ordered list of Python routine definitions within CLASS def.
603 START and END give buffer region to search."
604   (setq class (br-delete-space class))
605   (save-excursion
606     (save-restriction
607       (narrow-to-region start end)
608       (goto-char start)
609       (let ((routines) rout name)
610         ;;
611         ;; Get member definitions
612         ;;
613         (while (re-search-forward python-routine-def-in-class nil t)
614           (setq start (match-beginning 0)
615                 name (concat class "."
616                              (br-buffer-substring
617                               (match-beginning python-feature-name-grpn)
618                               (match-end python-feature-name-grpn)))
619                 rout (python-feature-normalize
620                       (br-buffer-substring (match-beginning 0) (match-end 0))
621                       class name)
622                 routines (cons rout routines)))
623         routines))))
624
625 (defun python-scan-globals ()
626   "Return list of globals names from a single global statement.
627 Point must be after the 'global' keyword which begins the list of comma
628 separated identifiers."
629   (let ((global-list) (again t)
630         global)
631     (while (and again (re-search-forward python-global-name nil t))
632       (setq again (eq ?, (following-char))
633             global (br-buffer-substring (match-beginning 1)
634                                         (match-end 1))
635             global-list (cons global global-list)))
636     (nreverse global-list)))
637
638 (defun python-feature-locate-p (feature-tag &optional regexp-flag)
639   "Leave point at the start of FEATURE-TAG's definition in the current buffer.
640 Assume caller has moved point to the beginning of the buffer or to the point
641 of desired search start.
642 Optional REGEXP-FLAG means FEATURE-TAG is a regular expression."
643   (let ((case-fold-search) (start)
644         (found t) feature-sig feature-regexp class)
645     (if (br-feature-tag-p feature-tag)
646         (setq feature-sig (br-feature-tag-signature feature-tag)
647               class (br-feature-tag-class feature-tag))
648       (setq feature-sig feature-tag
649             class nil))
650     (if regexp-flag
651         (if (stringp feature-tag)
652             (setq feature-regexp feature-tag)
653           (error "(python-feature-locate-p): Not a regexp, %s" feature-tag)))
654     ;;
655     ;; First move to the proper class implementation if feature-tag does not
656     ;; include a <class>:: part and this is not a [default-class], so that if
657     ;; two classes in the same file have the same feature signature, we still
658     ;; end up at the right one.
659     (cond (class
660            (if (or (string-match "\\`\\[" class)
661                    (and feature-sig (string-match "::" feature-sig)))
662                nil
663              (setq found (re-search-forward
664                           (python-class-definition-regexp class nil)
665                           nil t)
666                    start (match-beginning 0))))
667           ((string-match python-tag-fields-regexp feature-sig)
668            (setq class (substring feature-sig
669                                   (match-beginning 1) (match-end 1))
670                  feature-sig (substring feature-sig (match-end 0)))
671            (if (or (and regexp-flag
672                         (not (string-match "\\`\\\\\\[\\|::" feature-regexp)))
673                    (not (or regexp-flag
674                             (string-match "\\`\\[\\|::" feature-tag))))
675                (setq found (re-search-forward
676                             (python-class-definition-regexp class regexp-flag)
677                             nil t)
678                      start (match-beginning 0)))))
679     ;;
680     ;; If class was searched for and not found, then skip down to code display.
681     (if (not found)
682         nil
683       ;; Otherwise, look for feature expression.
684       (setq found nil)
685       (or regexp-flag (setq feature-regexp
686                             (python-feature-signature-to-regexp feature-sig)))
687       (while (and (re-search-forward feature-regexp nil t)
688                   (setq start (match-beginning 0))
689                   (not (setq found
690                              (cond ((eq major-mode 'python-mode)
691                                     (cond ((python-within-comment-p)
692                                            nil)
693                                           ((python-within-string-p)
694                                            (re-search-forward
695                                             python-multi-line-string-delimiter nil t)
696                                            nil)
697                                           (t)))
698                                    ;; must be a C/C++ file
699                                    ((c-within-comment-p)
700                                     (search-forward "*/" nil t)
701                                     nil)
702                                    (t)))))))
703     (if found (br-display-code start))))
704
705 (defun python-feature-name-to-regexp (name)
706   "Converts routine NAME into a regular expression matching the routine's name tag."
707   (setq name (python-feature-signature-to-regexp name))
708   (aset name (1- (length name)) ?\()  ;; Match only to functions
709   name)
710
711 (defun python-feature-signature-to-regexp (signature)
712   "Given a Python SIGNATURE, return regexp used to match to its definition."
713   (setq signature (regexp-quote signature))
714   (let ((prefix-info
715          (if (string-match python-tag-fields-regexp signature)
716              (prog1 (substring signature (match-beginning 0) (match-end 0))
717                (setq signature (substring signature (match-end 0)))))))
718     (let ((pat) (i 0) (c) (len (length signature)))
719       (while (< i len)
720         (setq c (aref signature i)
721               pat (cond ((eq c ? )
722                          ;; Allow for possible single line comment
723                          ;; following any whitespace, e.g. following
724                          ;; each routine argument.
725                          (concat pat "[ \t\n\r]*\\(#.*\\)?"))
726                         (t
727                          (concat pat (char-to-string c))))
728               i (1+ i)))
729       (setq pat (concat prefix-info pat)))))
730
731
732
733
734 ;;; ************************************************************************
735 ;;; Private variables
736 ;;; ************************************************************************
737
738 (defvar python-docstring ""
739   "Documentation string for python class, method or function.")
740
741 (defconst python-code-file-regexp "\\.py\\'"
742   "Regexp which matches a unique part of a Python source (non-header) file name and no others.")
743
744 (defconst python-import-modules-regexp
745   (concat "^[ \t]*import[ \t]+" python-identifier "[^#\n\r]+")
746   "Regexp which matches Python module import statements.
747 Grouping `python-import-name-grpn' matches the first import module name.")
748
749 (defconst python-import-functions-regexp
750   (concat "^[ \t]*from[ \t]+" python-identifier "[ \t\n\r]+import[ \t]+[^#\n\r]+")
751   "Regexp which matches Python function import statements.
752 Grouping `python-import-name-grpn' matches the import module name.")
753
754 (defconst python-global-def "^global[ \t]+"
755   "Regexp which matches a global definition statement.
756 After a match to this expression, point is left after the 'global' keyword.")
757
758 (defconst python-routine-def
759   (concat "^def[ \t]+" python-identifier "[ \t\n\r\\]*[^:]+")
760   "Regexp which matches a global python function definition.
761 Grouping `python-feature-name-grpn' matches the function name.
762 After a match to this expression, point is left before the colon
763 terminating the signature line.")
764
765 (defconst python-routine-def-in-class
766   (concat "^[ \t]+def[ \t]+" python-identifier "[ \t\n\r\\]*[^:]+")
767   "Regexp which matches a python class method definition.
768 Grouping `python-feature-name-grpn' matches the function name.
769 After a match to this expression, point is left before the colon
770 terminating the signature line.")
771
772 (defconst python-feature-name-grpn 1
773   "Feature name grouping from `python-routine-def' and `python-routine-def-in-class' matches.")
774
775 (defconst python-import-name-grpn 1
776   "Module name regexp grouping for import statements.")
777
778 (defconst python-multi-line-string-delimiter "'''\\|\"\"\""
779   "Regexp matching a Python multi-line string Start or end delimiter.")
780
781 (provide 'br-python-ft)