Mercurial > hg > xemacs-beta
diff lisp/utils/speedbar.el @ 167:85ec50267440 r20-3b10
Import from CVS: tag r20-3b10
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:45:46 +0200 |
parents | 4be1180a9e89 |
children | 6075d714658b |
line wrap: on
line diff
--- a/lisp/utils/speedbar.el Mon Aug 13 09:44:44 2007 +0200 +++ b/lisp/utils/speedbar.el Mon Aug 13 09:45:46 2007 +0200 @@ -1,200 +1,387 @@ -;;; speedbar - quick access to files and tags -;;; -;;; Copyright (C) 1996 Eric M. Ludlam -;;; -;;; Author: Eric M. Ludlam <zappo@gnu.ai.mit.edu> -;;; RCS: $Id: speedbar.el,v 1.1 1997/02/17 06:40:14 steve Exp $ -;;; Version: 0.3.1 -;;; Keywords: file, tags, tools -;;; -;;; This program is free software; you can redistribute it and/or modify -;;; it under the terms of the GNU General Public License as published by -;;; the Free Software Foundation; either version 2, or (at your option) -;;; any later version. -;;; -;;; This program 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 this program; if not, you can either send email to this -;;; program's author (see below) or write to: -;;; -;;; The Free Software Foundation, Inc. -;;; 675 Mass Ave. -;;; Cambridge, MA 02139, USA. -;;; -;;; Please send bug reports, etc. to zappo@gnu.ai.mit.edu. -;;; +;;; speedbar --- quick access to files and tags -*-byte-compile-warnings:nil;-*- + +;; Copyright (C) 1996, 1997 Eric M. Ludlam +;; +;; Author: Eric M. Ludlam <zappo@gnu.ai.mit.edu> +;; Version: 0.5 +;; Keywords: file, tags, tools +;; X-RCS: $Id: speedbar.el,v 1.2 1997/06/29 23:13:33 steve Exp $ +;; +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. +;; +;; This program 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 this program; if not, you can either send email to this +;; program's author (see below) or write to: +;; +;; The Free Software Foundation, Inc. +;; 675 Mass Ave. +;; Cambridge, MA 02139, USA. +;; +;; Please send bug reports, etc. to zappo@gnu.ai.mit.edu. +;; -;;; Commentary: -;;; -;;; The speedbar provides a frame in which files, and locations in -;;; files are displayed. These items can be clicked on with mouse-2 -;;; in order to make the last active frame display that file location. -;;; -;;; If you want to choose it from a menu or something, do this: -;;; -;;; (autoload 'speedbar-frame-mode "speedbar" "Popup a speedbar frame" t) -;;; (define-key-after (lookup-key global-map [menu-bar tools]) -;;; [speedbar] '("Speedbar" . speedbar-frame-mode) [calendar]) -;;; -;;; To activate speedbar without the menu, type: M-x speedbar-frame-mode RET -;;; -;;; Once a speedbar frame is active, it takes advantage of idle time -;;; to keep it's contents updated. The contents is usually a list of -;;; files in the directory of the currently active buffer. When -;;; applicable, tags in the active file can be expanded. -;;; -;;; Speedbar uses multiple methods for creating tags to jump to. -;;; When the variable `speedbar-use-imenu-package' is set, then -;;; speedbar will first try to use imenu to get tags. If the mode of -;;; the buffer doesn't support imenu, then etags is used. Using Imenu -;;; has the advantage that tags are cached, so opening and closing -;;; tags lists is faster. Speedbar-imenu will also load the file into -;;; a non-selected buffer so clicking the file later will be faster. -;;; -;;; To add new files types into the speedbar, modify -;;; `speedbar-file-regexp' to include the extension of the file type -;;; you wish to include. If speedbar complains that the file type is -;;; not supported, that means there is no built in support from imenu, -;;; and the etags part wasn't set up right. -;;; -;;; To add new file types to imenu, see the documentation in the -;;; file imenu.el that comes with emacs. To add new file types which -;;; etags supports, you need to modify the variable -;;; `speedbar-fetch-etags-parse-list'. This variable is an -;;; association list with each element of the form: (extension-regex -;;; . parse-one-line) The extension-regex would be something like -;;; "\\.c$" for a .c file, and the parse-one-line would be either a -;;; regular expression where match tag 1 is the element you wish -;;; displayed as a tag. If you need to do something more complex, -;;; then you can also write a function which parses one line, and put -;;; its symbol there instead. -;;; -;;; If the updates are going to slow for you, modify the variable -;;; `speedbar-update-speed' to a longer idle time before updates. -;;; -;;; If you navigate directories, you will probably notice that you -;;; will navigate to a directory which is eventually replaced after -;;; you go back to editing a file (unless you pull up a new file.) -;;; The delay time before this happens is in -;;; `speedbar-navigating-speed', and defaults to 20 seconds. -;;; -;;; XEmacs users may want to change the default timeouts for -;;; `speedbar-update-speed' to something longer as XEmacs doesn't have -;;; idle timers, the speedbar timer keeps going off arbitrarilly while -;;; you're typing. It's quite pesky. -;;; -;;; To get speedbar-configure-faces to work, you will need to -;;; download my eieio package from my ftp site. -;;; -;;; EIEIO is NOT required when using speedbar. It is ONLY needed -;;; if you want to use a fancy dialog face editor for speedbar. +;;; Commentary: +;; +;; The speedbar provides a frame in which files, and locations in +;; files are displayed. These items can be clicked on with mouse-2 +;; in order to make the last active frame display that file location. +;; +;; To use speedbar, add this to your .emacs file: +;; +;; (autoload 'speedbar-frame-mode "speedbar" "Popup a speedbar frame" t) +;; (autoload 'speedbar-get-focus "speedbar" "Jump to speedbar frame" t) +;; +;; If you want to choose it from a menu or something, do this: +;; +;; (define-key-after (lookup-key global-map [menu-bar tools]) +;; [speedbar] '("Speedbar" . speedbar-frame-mode) [calendar]) +;; +;; If you want to access speedbar using only the keyboard, do this: +;; +;; (define-key global-map [f4] 'speedbar-get-focus) +;; +;; This will let you hit f4 (or whatever key you choose) to jump +;; focus to the speedbar frame. Pressing RET or e to jump to a file +;; or tag will move you back to the attached frame. The command +;; `speedbar-get-fucus' will also create a speedbar frame if it does +;; not exist. +;; +;; Once a speedbar frame is active, it takes advantage of idle time +;; to keep it's contents updated. The contents is usually a list of +;; files in the directory of the currently active buffer. When +;; applicable, tags in the active file can be expanded. +;; +;; To add new supported files types into speedbar, use the function +;; `speedbar-add-supported-extension' If speedbar complains that the +;; file type is not supported, that means there is no built in +;; support from imenu, and the etags part wasn't set up correctly. You +;; may add elements to `speedbar-supported-extension-expressions' as long +;; as it is done before speedbar is loaded. +;; +;; To prevent speedbar from following you into certain directories +;; use the function `speedbar-add-ignored-path-regexp' too add a new +;; regular expression matching a type of path. You may add list +;; elements to `speedbar-ignored-path-expressions' as long as it is +;; done before speedbar is loaded. +;; +;; To add new file types to imenu, see the documentation in the +;; file imenu.el that comes with emacs. To add new file types which +;; etags supports, you need to modify the variable +;; `speedbar-fetch-etags-parse-list'. +;; +;; If the updates are going too slow for you, modify the variable +;; `speedbar-update-speed' to a longer idle time before updates. +;; +;; If you navigate directories, you will probably notice that you +;; will navigate to a directory which is eventually replaced after +;; you go back to editing a file (unless you pull up a new file.) +;; The delay time before this happens is in +;; `speedbar-navigating-speed', and defaults to 10 seconds. +;; +;; XEmacs users may want to change the default timeouts for +;; `speedbar-update-speed' to something longer as XEmacs doesn't have +;; idle timers, the speedbar timer keeps going off arbitrarilly while +;; you're typing. It's quite pesky. +;; +;; Users of emacs previous to to v 19.31 (when idle timers +;; where introduced) will not have speedbar updating automatically. +;; Use "r" to refresh the display after changing directories. +;; Remember, do not interrupt the stealthy updates or you display may +;; not be completely refreshed. +;; +;; See optional file `speedbspec.el' for additional configurations +;; which allow speedbar to create specialized lists for special modes +;; that are not file-related. +;; +;; See optional file `speedbcfg.el' for interactive buffers +;; allowing simple configuration of colors and features of speedbar. +;; +;; AUC-TEX users: The imenu tags for AUC-TEX mode don't work very +;; well. Use the imenu keywords from tex-mode.el for better results. +;; +;; This file requires the library package assoc (association lists) ;;; Speedbar updates can be found at: -;;; ftp://ftp.ultranet.com/pub/zappo/speedbar.*.el -;;; +;; ftp://ftp.ultranet.com/pub/zappo/speedbar*.tar.gz +;; -;;; HISTORY: -;;; 0.1 Initial Revision -;;; 0.2 Fixed problem with x-pointer-shape causing future frames not -;;; to be created. -;;; Fixed annoying habit of `speedbar-update-contents' to make -;;; it possible to accidentally kill the speedbar buffer. -;;; Clicking directory names now only changes the contents of -;;; the speedbar, and does not cause a dired mode to appear. -;;; Clicking the <+> next to the directory does cause dired to -;;; be run. -;;; Added XEmacs support, which means timer support moved to a -;;; platform independant call. -;;; Added imenu support. Now modes are supported by imenu -;;; first, and etags only if the imenu call doesn't work. -;;; Imenu is a little faster than etags, and is more emacs -;;; friendly. -;;; Added more user control variables described in the commentary. -;;; Added smart recentering when nodes are opened and closed. -;;; 0.3 x-pointer-shape fixed for emacs 19.35, so I put that check in. -;;; Added invisible codes to the beginning of each line. -;;; Added list aproach to node expansion for easier addition of new -;;; types of things to expand by -;;; Added multi-level path name support -;;; Added multi-level tag name support. -;;; Only mouse-2 is now used for node expansion -;;; Added keys e + - to edit expand, and contract node lines -;;; Added longer legal file regexp for all those modes which support -;;; imenu. (pascal, fortran90, ada, pearl) -;;; Fixed centering algorithm -;;; Tried to choose background independent colors. Made more robust. -;;; Rearranged code into a more logical order -;;; 0.3.1 Fixed doc & broken keybindings -;;; Added mode hooks. -;;; Improved color selection to be background mode smart -;;; `nil' passed to `speedbar-frame-mode' now toggles the frame as -;;; advertised in the doc string -;;; +;;; Change log: +;; 0.1 Initial Revision +;; 0.2 Fixed problem with x-pointer-shape causing future frames not +;; to be created. +;; Fixed annoying habit of `speedbar-update-contents' to make +;; it possible to accidentally kill the speedbar buffer. +;; Clicking directory names now only changes the contents of +;; the speedbar, and does not cause a dired mode to appear. +;; Clicking the <+> next to the directory does cause dired to +;; be run. +;; Added XEmacs support, which means timer support moved to a +;; platform independant call. +;; Added imenu support. Now modes are supported by imenu +;; first, and etags only if the imenu call doesn't work. +;; Imenu is a little faster than etags, and is more emacs +;; friendly. +;; Added more user control variables described in the commentary. +;; Added smart recentering when nodes are opened and closed. +;; 0.3 x-pointer-shape fixed for emacs 19.35, so I put that check in. +;; Added invisible codes to the beginning of each line. +;; Added list aproach to node expansion for easier addition of new +;; types of things to expand by +;; Added multi-level path name support +;; Added multi-level tag name support. +;; Only mouse-2 is now used for node expansion +;; Added keys e + - to edit expand, and contract node lines +;; Added longer legal file regexp for all those modes which support +;; imenu. (pascal, fortran90, ada, pearl) +;; Added pascal support to etags from Dave Penkler <dave_penkler@grenoble.hp.com> +;; Fixed centering algorithm +;; Tried to choose background independent colors. Made more robust. +;; Rearranged code into a more logical order +;; 0.3.1 Fixed doc & broken keybindings +;; Added mode hooks. +;; Improved color selection to be background mode smart +;; `nil' passed to `speedbar-frame-mode' now toggles the frame as +;; advertised in the doc string +;; 0.4a Added modified patch from Dan Schmidt <dfan@lglass.com> allowing a +;; directory cache to be maintained speeding up revisiting of files. +;; Default raise-lower behavior is now off by default. +;; Added some menu items for edit expand and contract. +;; Pre 19.31 emacsen can run without idle timers. +;; Added some patch information from Farzin Guilak <farzin@protocol.com> +;; adding xemacs specifics, and some etags upgrades. +;; Added ability to set a faces symbol-value to a string +;; representing the desired foreground color. (idea from +;; Farzin Guilak, but implemented differently) +;; Fixed problem with 1 character buttons. +;; Added support for new Imenu marker technique. +;; Added `speedbar-load-hooks' for things to run only once on +;; load such as updating one of the many lists. +;; Added `speedbar-supported-extension-expressions' which is a +;; list of extensions that speedbar will tag. This variable +;; should only be updated with `speedbar-add-supported-extension' +;; Moved configure dialog support to a separate file so +;; speedbar is not dependant on eieio to run +;; Fixed list-contraction problem when the item was at the end +;; of a sublist. +;; Fixed XEmacs multi-frame timer selecting bug problem. +;; Added `speedbar-ignored-modes' which is a list of major modes +;; speedbar will not follow when it is displayed in the selected frame +;; 0.4 When the file being edited is not in the list, and is a file +;; that should be in the list, the speedbar cache is replaced. +;; Temp buffers are now shown in the attached frame not the +;; speedbar frame +;; New variables `speedbar-vc-*' and `speedbar-stealthy-function-list' +;; added. `speedbar-update-current-file' is now a member of +;; the stealthy list. New function `speedbar-check-vc' will +;; examine each file and mark it if it is checked out. To +;; add new version control types, override the function +;; `speedbar-this-file-in-vc' and `speedbar-vc-check-dir-p'. +;; The stealth list is interruptible so that long operations +;; do not interrupt someones editing flow. Other long +;; speedbar updates will be added to the stealthy list in the +;; future should interesting ones be needed. +;; Added many new functions including: +;; `speedbar-item-byte-compile' `speedbar-item-load' +;; `speedbar-item-copy' `speedbar-item-rename' `speedbar-item-delete' +;; and `speedbar-item-info' +;; If the user kills the speedbar buffer in some way, the frame will +;; be removed. +;; 0.4.1 Bug fixes +;; <mark.jeffries@nomura.co.uk> added `speedbar-update-flag', +;; XEmacs fixes for menus, and tag sorting, and quit key. +;; Modeline now updates itself based on window-width. +;; Frame is cached when closed to make pulling it up again faster. +;; Speedbars window is now marked as dedicated. +;; Added bindings: <grossjoh@charly.informatik.uni-dortmund.de> +;; Long directories are now span multiple lines autmoatically +;; Added `speedbar-directory-button-trim-method' to specify how to +;; sorten the directory button to fit on the screen. +;; 0.4.2 Add one level of full-text cache. +;; Add `speedbar-get-focus' to switchto/raise the speedbar frame. +;; Editing thing-on-line will auto-raise the attached frame. +;; Bound `U' to `speedbar-up-directory' command. +;; Refresh will now maintain all subdirectories that were open +;; when the refresh was requested. (This does not include the +;; tags, only the directories) +;; 0.4.3 Bug fixes +;; 0.4.4 Added `speedbar-ignored-path-expressions' and friends. +;; Configuration menu items not displayed if dialog-mode not present +;; Speedbar buffer now starts with a space, and is not deleted +;; ewhen the speedbar frame is closed. This prevents the invisible +;; frame from preventing buffer switches with other buffers. +;; Fixed very bad bug in the -add-[extension|path] functions. +;; Added `speedbar-find-file-in-frame' which will always pop up a frame +;; that is already display a buffer selected in the speedbar buffer. +;; Added S-mouse2 as "power click" for always poping up a new frame. +;; and always rescanning with imenu (ditching the imenu cache), and +;; always rescanning directories. +;; 0.4.5 XEmacs bugfixes and enhancements. +;; Window Title simplified. +;; 0.4.6 Fixed problems w/ dedicated minibuffer frame. +;; Fixed errors reported by checkdoc. +;; 0.5 Mode-specific contents added. Controlled w/ the variable +;; `speedbar-mode-specific-contents-flag'. See speedbspec +;; for info on enabling this feature. +;; `speedbar-load-hook' name change and pointer check against +;; major-mode. Suggested by Sam Steingold <sds@ptc.com> +;; Quit auto-selects the attached frame. +;; Ranamed `speedbar-do-updates' to `speedbar-update-flag' +;; Passes checkdoc. + ;;; TODO: -;;; 1) Rember contents of directories when leaving them so it's faster -;;; when returning. -;;; 2) List of directories to never visit. (User might be browsing -;;; there temporarilly such as info files, documentation and the -;;; like) -;;; 3) Implement SHIFT-mouse2 to rescan buffers with imenu. -;;; 4) Better XEmacs support of menus and button-bar -;;; 5) More functions to create buttons and options -;;; 6) filtering algoritms to reduce the number of tags/files -;;; displayed. -;;; 7) Build `speedbar-file-regexp' on the fly. -;;; 8) More intelligent current file highlighting. +;; 1) More functions to create buttons and options +;; 2) filtering algoritms to reduce the number of tags/files displayed. +;; 3) Timeout directories we haven't visited in a while. +;; 4) Remeber tags when refreshing the display. (Refresh tags too?) +;; 5) More 'special mode support. +;; 6) Smart way to auto-expand instead of directory switch -(defvar speedbar-xemacsp (string-match "XEmacs" emacs-version)) +;;; Code: +(require 'assoc) +(require 'easymenu) + +(defvar speedbar-xemacsp (string-match "XEmacs" emacs-version) + "Non-nil if we are running in the XEmacs environment.") (defvar speedbar-initial-expansion-list '(speedbar-directory-buttons speedbar-default-directory-list) - "*List of functions to call to fill in the speedbar buffer whenever -a top level update is issued. These functions will allways get the -default directory to use passed in as the first parameter, and a 0 as -the second parameter. They must assume that the cursor is at the -postion where they start inserting buttons.") + "List of functions to call to fill in the speedbar buffer. +Whenever a top level update is issued all functions in this list are +run. These functions will always get the default directory to use +passed in as the first parameter, and a 0 as the second parameter. +The 0 indicates the uppermost indentation level. They must assume +that the cursor is at the position where they start inserting +buttons.") + +(defvar speedbar-stealthy-function-list + '(speedbar-update-current-file speedbar-check-vc) + "List of functions to periodically call stealthily. +Each function must return nil if interrupted, or t if completed. +Stealthy functions which have a single operation should always return +t. Functions which take a long time should maintain a state (where +they are in their speedbar related calculations) and permit +interruption. See `speedbar-check-vc' as a good example.") + +(defvar speedbar-mode-specific-contents-flag t + "*Non-nil means speedbar will show specail-mode contents. +This permits some modes to create customized contents for the speedbar +frame.") + +(defvar speedbar-special-mode-expansion-list nil + "Mode specific list of functions to call to fill in speedbar. +Some modes, such as Info or RMAIL, do not relate quite as easily into +a simple list of files. When this variable is non-nil and buffer-local, +then these functions are used, creating specialized contents. These +functions are called each time the speedbar timer is called. This +allows a mode to update it's contents regularly. + + Each function is called with the default and frame belonging to +speedbar, and with one parameter; the buffer requesting +the speedbar display.") + +(defvar speedbar-load-hook nil + "Hooks run when speedbar is loaded.") + +(defvar speedbar-desired-buffer nil + "Non-nil when speedbar is showing buttons specific a special mode. +In this case it is the originating buffer.") (defvar speedbar-show-unknown-files nil - "*Non-nil shows files with a ? in the expansion tag for files we can't -expand. `nil' means don't show the file in the list.") + "*Non-nil show files we can't expand with a ? in the expand button. +nil means don't show the file in the list.") ;; Xemacs timers aren't based on idleness. Therefore tune it down a little ;; or suffer mightilly! (defvar speedbar-update-speed (if speedbar-xemacsp 5 1) - "*Time in seconds of idle time needed before speedbar will update -it's buffer to match what you've been doing in your other frame.") + "*Idle time in seconds needed before speedbar will update itself. +Updates occur to allow speedbar to display directory information +relevant to the buffer you are currently editing.") (defvar speedbar-navigating-speed 10 - "*Idle time to wait before re-running the timer proc to pick up any new -activity if the user has started navigating directories in the speedbar.") - -(defvar speedbar-width 20 - "*Initial size of the speedbar window") + "*Idle time to wait after navigation commands in speedbar are executed. +Navigation commands included expanding/contracting nodes, and moving +between different directories.") -(defvar speedbar-scrollbar-width 10 - "*Initial sizeo of the speedbar scrollbar. The thinner, the more -display room you will have.") +(defvar speedbar-frame-parameters (list + ;; Xemacs fails to delete speedbar + ;; if minibuffer is off. + ;(cons 'minibuffer + ; (if speedbar-xemacsp t nil)) + ;; The above behavior seems to have fixed + ;; itself somewhere along the line. + ;; let me know if any problems arise. + '(minibuffer . nil) + '(width . 20) + '(scroll-bar-width . 10) + '(border-width . 0) + '(unsplittable . t) ) + "*Parameters to use when creating the speedbar frame. +Parameters not listed here which will be added automatically are +`height' which will be initialized to the height of the frame speedbar +is attached to. To add more frame defaults, `cons' new alist members +onto this variable through the `speedbar-load-hook'") -(defvar speedbar-raise-lower t - "*Non-nil means speedbar will auto raise and lower itself. When this -is set, you can have only a tiny strip visible under your main emacs, -and it will raise and lower itself when you put the pointer in it.") +(defvar speedbar-use-imenu-flag (stringp (locate-library "imenu")) + "*Non-nil means use imenu for file parsing. nil to use etags. +XEmacs doesn't support imenu, therefore the default is to use etags +instead. Etags support is not as robust as imenu support.") -(defvar speedbar-use-imenu-package (not speedbar-xemacsp) - "*Optionally use the imenu package instead of etags for parsing. This -is experimental for performace testing.") +(defvar speedbar-sort-tags nil + "*If Non-nil, sort tags in the speedbar display. (Etags only) +See imenu.el source for how imenu does sorting.") + +(defvar speedbar-directory-button-trim-method 'span + "*Indicates how the directory button will be displayed. +Possible values are: + 'span - span large directories over multiple lines. + 'trim - trim large directories to only show the last few. + nil - no trimming.") (defvar speedbar-before-delete-hook nil - "*Hooks called before deletiing the speedbar frame.") + "*Hooks called before deleting the speedbar frame.") (defvar speedbar-mode-hook nil - "*Hooks called after creating a speedbar buffer") + "*Hooks called after creating a speedbar buffer.") (defvar speedbar-timer-hook nil - "*Hooks called after running the speedbar timer function") + "*Hooks called after running the speedbar timer function.") + +(defvar speedbar-verbosity-level 1 + "*Verbosity level of the speedbar. 0 means say nothing. +1 means medium level verbosity. 2 and higher are higher levels of +verbosity.") + +(defvar speedbar-vc-indicator " *" + "*Text used to mark files which are currently checked out. +Currently only RCS is supported. Other version control systems can be +added by examining the function `speedbar-this-file-in-vc' and +`speedbar-vc-check-dir-p'") + +(defvar speedbar-vc-do-check t + "*Non-nil check all files in speedbar to see if they have been checked out. +Any file checked out is marked with `speedbar-vc-indicator'") + +(defvar speedbar-vc-to-do-point nil + "Local variable maintaining the current version control check position.") + +(defvar speedbar-ignored-modes nil + "*List of major modes which speedbar will not switch directories for.") + +(defvar speedbar-ignored-path-expressions + '("/log/$") + "*List of regular expressions matching directories speedbar will ignore. +They should included paths to directories which are notoriously very +large and take a long time to load in. Use the function +`speedbar-add-ignored-path-regexp' to add new items to this list after +speedbar is loaded. You may place anything you like in this list +before speedbar has been loaded.") (defvar speedbar-file-unshown-regexp (let ((nstr "") (noext completion-ignored-extensions)) @@ -203,17 +390,100 @@ (if (cdr noext) "\\|" "")) noext (cdr noext))) (concat nstr "\\|#[^#]+#$\\|\\.\\.?$")) - "*Regular expression matching files we don't want to display in a -speedbar buffer") + "*Regexp matching files we don't want displayed in a speedbar buffer. +It is generated from the variable `completion-ignored-extensions'") + +(defvar speedbar-supported-extension-expressions + (append '(".[CcHh]\\(++\\|pp\\|c\\|h\\)?" ".tex\\(i\\(nfo\\)?\\)?" + ".el" ".emacs" ".p" ".java") + (if speedbar-use-imenu-flag + '(".f90" ".ada" ".pl" ".tcl" ".m" + "Makefile\\(\\.in\\)?"))) + "*List of regular expressions which will match files supported by tagging. +Do not prefix the `.' char with a double \\ to quote it, as the period +will be stripped by a simplified optimizer when compiled into a +singular expression. This variable will be turned into +`speedbar-file-regexp' for use with speedbar. You should use the +function `speedbar-add-supported-extension' to add a new extension at +runtime, or use the configuration dialog to set it in your .emacs +file.") + +(defun speedbar-extension-list-to-regex (extlist) + "Takes EXTLIST, a list of extensions and transforms it into regexp. +All the preceding . are stripped for an optimized expression starting +with . followed by extensions, followed by full-filenames." + (let ((regex1 nil) (regex2 nil)) + (while extlist + (if (= (string-to-char (car extlist)) ?.) + (setq regex1 (concat regex1 (if regex1 "\\|" "") + (substring (car extlist) 1))) + (setq regex2 (concat regex2 (if regex2 "\\|" "") (car extlist)))) + (setq extlist (cdr extlist))) + ;; concat all the sub-exressions together, making sure all types + ;; of parts exist during concatination. + (concat "\\(" + (if regex1 (concat "\\(\\.\\(" regex1 "\\)\\)") "") + (if (and regex1 regex2) "\\|" "") + (if regex2 (concat "\\(" regex2 "\\)") "") + "\\)$"))) + +(defvar speedbar-ignored-path-regexp + (speedbar-extension-list-to-regex speedbar-ignored-path-expressions) + "Regular expression matching paths speedbar will not switch to. +Created from `speedbar-ignored-path-expressions' with the function +`speedbar-extension-list-to-regex' (A misnamed function in this case.) +Use the function `speedbar-add-ignored-path-regexp' to modify this +variable.") -(defvar speedbar-file-regexp - (if speedbar-use-imenu-package - "\\(\\.\\([CchH]\\|c\\(++\\|pp\\)\\|f90\\|ada\\|pl?\\|el\\|t\\(ex\\(i\\(nfo\\)?\\)?\\|cl\\)\\|emacs\\)$\\)\\|[Mm]akefile\\(\\.in\\)?" - "\\.\\([CchH]\\|c\\(++\\|pp\\)\\|p\\|el\\|tex\\(i\\(nfo\\)?\\)?\\|emacs\\)$") - "*Regular expresson matching files we know how to expand.") +(defvar speedbar-file-regexp + (speedbar-extension-list-to-regex speedbar-supported-extension-expressions) + "Regular expression matching files we know how to expand. +Created from `speedbar-supported-extension-expression' with the +function `speedbar-extension-list-to-regex'") + +(defun speedbar-add-supported-extension (extension) + "Add EXTENSION as a new supported extension for speedbar tagging. +This should start with a `.' if it is not a complete file name, and +the dot should NOT be quoted in with \\. Other regular expression +matchers are allowed however. EXTENSION may be a single string or a +list of strings." + (if (not (listp extension)) (setq extension (list extension))) + (while extension + (if (member (car extension) speedbar-supported-extension-expressions) + nil + (setq speedbar-supported-extension-expressions + (cons (car extension) speedbar-supported-extension-expressions))) + (setq extension (cdr extension))) + (setq speedbar-file-regexp (speedbar-extension-list-to-regex + speedbar-supported-extension-expressions))) + +(defun speedbar-add-ignored-path-regexp (path-expression) + "Add PATH-EXPRESSION as a new ignored path for speedbar tracking. +This function will modify `speedbar-ignored-path-regexp' and add +PATH-EXPRESSION to `speedbar-ignored-path-expressions'." + (if (not (listp path-expression)) + (setq path-expression (list path-expression))) + (while path-expression + (if (member (car path-expression) speedbar-ignored-path-expressions) + nil + (setq speedbar-ignored-path-expressions + (cons (car path-expression) speedbar-ignored-path-expressions))) + (setq path-expression (cdr path-expression))) + (setq speedbar-ignored-path-regexp (speedbar-extension-list-to-regex + speedbar-ignored-path-expressions))) + +(defvar speedbar-update-flag (or (not (fboundp 'run-with-idle-timer)) + (not (fboundp 'start-itimer))) + "*Non-nil means to automatically update the display. +When this is nil then speedbar will not follow the attached frame's path. +When speedbar is active, use: + +\\<speedbar-key-map> `\\[speedbar-toggle-updates]' + +to toggle this value.") (defvar speedbar-syntax-table nil - "Syntax-table used on the speedbar") + "Syntax-table used on the speedbar.") (if speedbar-syntax-table nil @@ -225,100 +495,193 @@ (modify-syntax-entry ?) " " speedbar-syntax-table) (modify-syntax-entry ?[ " " speedbar-syntax-table) (modify-syntax-entry ?] " " speedbar-syntax-table)) - + (defvar speedbar-key-map nil "Keymap used in speedbar buffer.") -(defvar speedbar-menu-map nil - "Keymap used in speedbar menu buffer.") + +(autoload 'speedbar-configure-options "speedbcfg" "Configure speedbar variables" t) +(autoload 'speedbar-configure-faces "speedbcfg" "Configure speedbar faces" t) (if speedbar-key-map nil (setq speedbar-key-map (make-keymap)) (suppress-keymap speedbar-key-map t) + ;; control (define-key speedbar-key-map "e" 'speedbar-edit-line) + (define-key speedbar-key-map "\C-m" 'speedbar-edit-line) (define-key speedbar-key-map "+" 'speedbar-expand-line) (define-key speedbar-key-map "-" 'speedbar-contract-line) + (define-key speedbar-key-map "g" 'speedbar-refresh) + (define-key speedbar-key-map "t" 'speedbar-toggle-updates) + (define-key speedbar-key-map "q" 'speedbar-close-frame) + (define-key speedbar-key-map "U" 'speedbar-up-directory) + + ;; navigation + (define-key speedbar-key-map "n" 'speedbar-next) + (define-key speedbar-key-map "p" 'speedbar-prev) + (define-key speedbar-key-map " " 'speedbar-scroll-up) + (define-key speedbar-key-map "\C-?" 'speedbar-scroll-down) + + ;; After much use, I suddenly desired in my heart to perform dired + ;; style operations since the directory was RIGHT THERE! + (define-key speedbar-key-map "I" 'speedbar-item-info) + (define-key speedbar-key-map "B" 'speedbar-item-byte-compile) + (define-key speedbar-key-map "L" 'speedbar-item-load) + (define-key speedbar-key-map "C" 'speedbar-item-copy) + (define-key speedbar-key-map "D" 'speedbar-item-delete) + (define-key speedbar-key-map "R" 'speedbar-item-rename) (if (string-match "XEmacs" emacs-version) (progn ;; bind mouse bindings so we can manipulate the items on each line (define-key speedbar-key-map 'button2 'speedbar-click) + (define-key speedbar-key-map '(shift button2) 'speedbar-power-click) + (define-key speedbar-key-map '(meta button3) 'speedbar-mouse-item-info) - ;; Xemacs users. You probably want your own toolbar for - ;; the speedbar frame or mode or whatever. Make some buttons - ;; and mail me how to do it! - ;; Also, how do you disable all those menu items? Email me that too - ;; as it would be most helpful. + ;; Setup XEmacs Menubar w/ etags specific items + (defvar speedbar-menu + '("Speed Bar" + ["Run Speedbar" (speedbar-frame-mode 1) t] + ["Refresh" speedbar-refresh t] + ["Allow Auto Updates" + speedbar-toggle-updates + :style toggle + :selected speedbar-update-flag] + "-----" + ["Sort etags in Speedbar" + (speedbar-toggle-etags "sort") + :style toggle + :selected speedbar-sort-tags] + ["Show unknown files" + (speedbar-toggle-etags "show") + :style toggle + :selected speedbar-show-unknown-files] + "-----" + ["Use C++ Tagging" + (speedbar-toggle-etags "-C") + :style toggle + :selected (member "-C" speedbar-fetch-etags-arguments)] + ["Tag preprocessor defs" + (speedbar-toggle-etags "-D") + :style toggle + :selected (not (member "-D" speedbar-fetch-etags-arguments))] + ["Use indentation" + (speedbar-toggle-etags "-S") + :style toggle + :selected (not (member "-S" speedbar-fetch-etags-arguments))])) + + (add-submenu '("Tools") speedbar-menu nil) + ) ;; bind mouse bindings so we can manipulate the items on each line (define-key speedbar-key-map [mouse-2] 'speedbar-click) - (define-key speedbar-key-map [down-mouse-2] 'speedbar-quick-mouse) - - ;; this was meant to do a rescan or something - ;;(define-key speedbar-key-map [shift-mouse-2] 'speedbar-hard-click) + ;; This is the power click for poping up new frames + (define-key speedbar-key-map [S-mouse-2] 'speedbar-power-click) + ;; This adds a small unecessary visual effect + ;;(define-key speedbar-key-map [down-mouse-2] 'speedbar-quick-mouse) + (define-key speedbar-key-map [M-mouse-2] 'speedbar-mouse-item-info) ;; disable all menus - we don't have a lot of space to play with - ;; in such a skinny frame. - (define-key speedbar-key-map [menu-bar buffer] 'undefined) - (define-key speedbar-key-map [menu-bar files] 'undefined) - (define-key speedbar-key-map [menu-bar tools] 'undefined) - (define-key speedbar-key-map [menu-bar edit] 'undefined) - (define-key speedbar-key-map [menu-bar search] 'undefined) - (define-key speedbar-key-map [menu-bar help-menu] 'undefined) + ;; in such a skinny frame. This will cleverly find and nuke some + ;; user-defined menus as well if they are there. Too bad it + ;; rely's on the structure of a keymap to work. + (let ((k (lookup-key global-map [menu-bar]))) + (while k + (if (and (listp (car k)) (listp (cdr (car k)))) + (define-key speedbar-key-map (vector 'menu-bar (car (car k))) + 'undefined)) + (setq k (cdr k)))) ;; This lets the user scroll as if we had a scrollbar... well maybe not (define-key speedbar-key-map [mode-line mouse-2] 'speedbar-mouse-hscroll) - - ;; Create a menu for speedbar - (setq speedbar-menu-map (make-sparse-keymap)) - (define-key speedbar-key-map [menu-bar speedbar] - (cons "Speedbar" speedbar-menu-map)) - (define-key speedbar-menu-map [close] - (cons "Close" 'speedbar-close-frame)) - (define-key speedbar-menu-map [clonfigure] - (cons "Configure Faces" 'speedbar-configure-faces)) - (define-key speedbar-menu-map [configopt] - (cons "Configure Options" 'speedbar-configure-options)) - (define-key speedbar-menu-map [Update] - (cons "Update" 'speedbar-update-contents)) )) -(put 'speedbar-configure-faces 'menu-enable '(featurep 'dialog)) -(put 'speedbar-configure-options 'menu-enable '(featurep 'dialog)) +(defvar speedbar-easymenu-definition-base + '("Speedbar" + ["Update" speedbar-refresh t] + ["Auto Update" speedbar-toggle-updates + :style toggle :selected speedbar-update-flag] + ) + "Base part of the speedbar menu.") + +(defvar speedbar-easymenu-definition-special + '(["Edit Item On Line" speedbar-edit-line t] + ["Show All Files" speedbar-toggle-show-all-files + :style toggle :selected speedbar-show-unknown-files] + ["Expand Item" speedbar-expand-line + (save-excursion (beginning-of-line) + (looking-at "[0-9]+: *.\\+. "))] + ["Contract Item" speedbar-contract-line + (save-excursion (beginning-of-line) + (looking-at "[0-9]+: *.-. "))] + "----" + ["Item Information" speedbar-item-info t] + ["Load Lisp File" speedbar-item-load + (save-excursion + (beginning-of-line) + (looking-at "[0-9]+: *\\[[+-]\\] .+\\(\\.el\\)\\( \\*\\)?$"))] + ["Byte Compile File" speedbar-item-byte-compile + (save-excursion + (beginning-of-line) + (looking-at "[0-9]+: *\\[[+-]\\] .+\\(\\.el\\)\\( \\*\\)?$"))] + ["Copy Item" speedbar-item-copy + (save-excursion (beginning-of-line) (looking-at "[0-9]+: *\\["))] + ["Rename Item" speedbar-item-rename + (save-excursion (beginning-of-line) (looking-at "[0-9]+: *[[<]"))] + ["Delete Item" speedbar-item-delete + (save-excursion (beginning-of-line) (looking-at "[0-9]+: *[[<]"))]) + "Additional menu items while in file-mode.") + +(defvar speedbar-easymenu-definition-trailer + '("----" + ["Close" speedbar-close-frame t]) + "Menu items appearing at the end of the speedbar menu.") (defvar speedbar-buffer nil "The buffer displaying the speedbar.") (defvar speedbar-frame nil "The frame displaying speedbar.") +(defvar speedbar-cached-frame nil + "The frame that was last created, then removed from the display.") +(defvar speedbar-full-text-cache nil + "The last open directory is saved in it's entirety for ultra-fast switching.") (defvar speedbar-timer nil "The speedbar timer used for updating the buffer.") (defvar speedbar-attached-frame nil - "The frame which started speedbar mode. This is the frame from -which all data displayed in the speedbar is gathered, and in which files -and such are displayed.") + "The frame which started speedbar mode. +This is the frame from which all data displayed in the speedbar is +gathered, and in which files and such are displayed.") (defvar speedbar-last-selected-file nil - "The last file which was selected in speedbar buffer") + "The last file which was selected in speedbar buffer.") (defvar speedbar-shown-directories nil - "Used to maintain list of directories simultaneously open in the current -speedbar.") + "Maintain list of directories simultaneously open in the current speedbar.") + +(defvar speedbar-directory-contents-alist nil + "An association list of directories and their contents. +Each sublist was returned by `speedbar-file-lists'. This list is +maintained to speed up the refresh rate when switching between +directories.") + +(defvar speedbar-power-click nil + "Never set this by hand. Value is t when S-mouse activity occurs.") -;;; ;;; Mode definitions/ user commands -;;; -;;;###autoload +;; +;;###autoload +(defalias 'speedbar 'speedbar-frame-mode) (defun speedbar-frame-mode (&optional arg) - "Enable or disable use of a speedbar. Positive number means turn -on, negative turns speedbar off, and nil means toggle. Once the -speedbar frame is activated, a buffer in `speedbar-mode' will be -displayed. Currently, only one speedbar is supported at a time." + "Enable or disable speedbar. Positive ARG means turn on, negative turn off. +nil means toggle. Once the speedbar frame is activated, a buffer in +`speedbar-mode' will be displayed. Currently, only one speedbar is +supported at a time." (interactive "P") (if (not window-system) - (error "Speedbar is not useful outside of a windowing environement")) + (error "Speedbar is not useful outside of a windowing environment")) ;; toggle frame on and off. (if (not arg) (if speedbar-frame (setq arg -1) (setq arg 1))) ;; turn the frame off on neg number @@ -326,63 +689,88 @@ (progn (run-hooks 'speedbar-before-delete-hook) (if (and speedbar-frame (frame-live-p speedbar-frame)) - (delete-frame speedbar-frame)) - (speedbar-set-timer nil) + (if speedbar-xemacsp + (delete-frame speedbar-frame) + (setq speedbar-cached-frame speedbar-frame) + (modify-frame-parameters speedbar-frame '((visibility . nil))))) (setq speedbar-frame nil) - (if (bufferp speedbar-buffer) - (kill-buffer speedbar-buffer))) + (speedbar-set-timer nil) + ;; Used to delete the buffer. This has the annoying affect of + ;; preventing whatever took it's place from ever appearing + ;; as the default after a C-x b was typed + ;;(if (bufferp speedbar-buffer) + ;; (kill-buffer speedbar-buffer)) + ) ;; Set this as our currently attached frame (setq speedbar-attached-frame (selected-frame)) - ;; Get the buffer to play with - (speedbar-mode) ;; Get the frame to work in - (if (and speedbar-frame (frame-live-p speedbar-frame)) - (raise-frame speedbar-frame) - (let ((params (list - ;; Xemacs fails to delete speedbar - ;; if minibuffer is off. - ;; JTL <<<< Seems to be OK for 19.15. - ;; removed tool- & menubar. - ;; <<<< JTL - (cons 'minibuffer nil) - (cons 'width speedbar-width) - (cons 'height (frame-height)) - (cons 'scroll-bar-width speedbar-scrollbar-width) - (cons 'auto-raise speedbar-raise-lower) - (cons 'auto-lower speedbar-raise-lower) - '(modeline . nil) - '(border-width . 0) - '(unsplittable . t) - '(default-toolbar-visible-p . nil) - '(menubar-visible-p . nil)))) - (setq speedbar-frame - (if (< emacs-minor-version 35) - (make-frame params) - (let ((x-pointer-shape x-pointer-top-left-arrow) - (x-sensitive-text-pointer-shape x-pointer-hand2)) - (make-frame params))))) - ;; reset the selection variable - (setq speedbar-last-selected-file nil) - ;; Put the buffer into the frame - (save-window-excursion - (select-frame speedbar-frame) - (switch-to-buffer speedbar-buffer) - (setq default-minibuffer-frame speedbar-attached-frame)) - (speedbar-set-timer speedbar-update-speed) - ))) + (if (frame-live-p speedbar-cached-frame) + (progn + (setq speedbar-frame speedbar-cached-frame) + (modify-frame-parameters speedbar-frame '((visibility . t))) + ;; Get the buffer to play with + (speedbar-mode) + (select-frame speedbar-frame) + (if (not (eq (current-buffer) speedbar-buffer)) + (switch-to-buffer speedbar-buffer)) + (set-window-dedicated-p (selected-window) t) + (raise-frame speedbar-frame) + (speedbar-set-timer speedbar-update-speed) + ) + (if (frame-live-p speedbar-frame) + (raise-frame speedbar-frame) + (let ((params (cons (cons 'height (frame-height)) + speedbar-frame-parameters))) + (setq speedbar-frame + (if (< emacs-major-version 20) ;a bug is fixed in v20 & later + (make-frame params) + (let ((x-pointer-shape x-pointer-top-left-arrow) + (x-sensitive-text-pointer-shape x-pointer-hand2)) + (make-frame params))))) + ;; reset the selection variable + (setq speedbar-last-selected-file nil) + ;; Put the buffer into the frame + (save-window-excursion + ;; Get the buffer to play with + (speedbar-mode) + (select-frame speedbar-frame) + (switch-to-buffer speedbar-buffer) + (set-window-dedicated-p (selected-window) t) + ;; Turn off toolbar and menubar under XEmacs + (if speedbar-xemacsp + (progn + (set-specifier default-toolbar-visible-p + (cons (selected-frame) nil)) + ;; These lines make the menu-bar go away nicely, but + ;; they also cause xemacs much heartache. + ;;(set-specifier menubar-visible-p (cons (selected-frame) nil)) + ;;(make-local-variable 'current-menubar) + ;;(setq current-menubar speedbar-menu) + ;;(add-submenu nil speedbar-menu nil) + ))) + (speedbar-set-timer speedbar-update-speed) + )))) (defun speedbar-close-frame () - "Turn off speedbar mode" + "Turn off a currently active speedbar." (interactive) - (speedbar-frame-mode -1)) + (speedbar-frame-mode -1) + (select-frame speedbar-attached-frame) + (other-frame 0)) + +(defun speedbar-frame-width () + "Return the width of the speedbar frame in characters. +nil if it doesn't exist." + (and speedbar-frame (cdr (assoc 'width (frame-parameters speedbar-frame))))) (defun speedbar-mode () - "Create and return a SPEEDBAR buffer. The speedbar buffer allows -the user to manage a list of directories and paths at different -depths. The first line represents the default path of the speedbar -frame. Each directory segment is a button which jumps speedbar's -default directory to that path. Buttons are activated by clicking -mouse-2. + "Major mode for managing a display of directories and tags. +\\<speedbar-key-map> +The first line represents the default path of the speedbar frame. +Each directory segment is a button which jumps speedbar's default +directory to that path. Buttons are activated by clicking `\\[speedbar-click]'. +In some situations using `\\[speedbar-power-click]' is a `power click' which will +rescan cached items, or pop up new frames. Each line starting with <+> represents a directory. Click on the <+> to insert the directory listing into the current tree. Click on the @@ -395,39 +783,119 @@ Files are completely ignored if they match `speedbar-file-unshown-regexp' which is generated from `completion-ignored-extensions'. +Files with a `*' character after their name are files checked out of a +version control system. (currently only RCS is supported.) New +version control systems can be added by examining the documentation +for `speedbar-this-file-in-vc' and `speedbar-vc-check-dir-p' + Click on the [+] to display a list of tags from that file. Click on the [-] to retract the list. Click on the file name to edit the file in the attached frame. If you open tags, you might find a node starting with {+}, which is a -category of tags. Click the {+} to expand the category. Jumpable +category of tags. Click the {+} to expand the category. Jump-able tags start with >. Click the name of the tag to go to that position in the selected file. -Keybindings: \\<speedbar-key-map> -\\[speedbar-click] Activate the button under the mouse. -\\[speedbar-edit-line] Edit the file/directory on this line. Same as clicking - on the name on the selected line.) -\\[speedbar-expand-line] Expand the current line. Same as clicking on the + on a line. -\\[speedbar-contract-line] Contract the current line. Same as clicking on the - on a line." - (setq speedbar-buffer (set-buffer (get-buffer-create "SPEEDBAR"))) - (kill-all-local-variables) - (setq major-mode 'speedbar-mode) - (setq mode-name "SB") - (use-local-map speedbar-key-map) - (set-syntax-table speedbar-syntax-table) - (setq mode-line-format - '("<< SPEEDBAR " (line-number-mode " %3l ") " >>")) - (setq font-lock-keywords nil) ;; no font-locking please - (setq truncate-lines t) - (if (not speedbar-xemacsp) (setq auto-show-mode nil)) ;no auto-show for FSF - (run-hooks 'speedbar-mode-hook) +\\{speedbar-key-map}" + ;; NOT interactive + (save-excursion + (setq speedbar-buffer (set-buffer (get-buffer-create " SPEEDBAR"))) + (kill-all-local-variables) + (setq major-mode 'speedbar-mode) + (setq mode-name "Speedbar") + (use-local-map speedbar-key-map) + (set-syntax-table speedbar-syntax-table) + (setq font-lock-keywords nil) ;; no font-locking please + (setq truncate-lines t) + (make-local-variable 'frame-title-format) + (setq frame-title-format "Speedbar") + ;; Set this up special just for the speedbar buffer + (if (null default-minibuffer-frame) + (progn + (make-local-variable 'default-minibuffer-frame) + (setq default-minibuffer-frame speedbar-attached-frame))) + (make-local-variable 'temp-buffer-show-function) + (setq temp-buffer-show-function 'speedbar-temp-buffer-show-function) + (setq kill-buffer-hook '(lambda () (let ((skilling (boundp 'skilling))) + (if skilling + nil + (if (eq (current-buffer) + speedbar-buffer) + (speedbar-frame-mode -1)))))) + (speedbar-set-mode-line-format) + (if (not speedbar-xemacsp) + (setq auto-show-mode nil)) ;no auto-show for Emacs + (run-hooks 'speedbar-mode-hook)) (speedbar-update-contents) - ) + speedbar-buffer) + +(defun speedbar-set-mode-line-format () + "Set the format of the mode line based on the current speedbar environment. +This gives visual indications of what is up. It EXPECTS the speedbar +frame and window to be the currently active frame and window." + (if (frame-live-p speedbar-frame) + (save-excursion + (set-buffer speedbar-buffer) + (let* ((w (or (speedbar-frame-width) 20)) + (p1 "<<") + (p5 ">>") + (p3 (if speedbar-update-flag "SPEEDBAR" "SLOWBAR")) + (blank (- w (length p1) (length p3) (length p5) + (if line-number-mode 4 0))) + (p2 (if (> blank 0) + (make-string (/ blank 2) ? ) + "")) + (p4 (if (> blank 0) + (make-string (+ (/ blank 2) (% blank 2)) ? ) + "")) + (tf + (if line-number-mode + (list (concat p1 p2 p3) '(line-number-mode " %3l") + (concat p4 p5)) + (list (concat p1 p2 p3 p4 p5))))) + (if (not (equal mode-line-format tf)) + (progn + (setq mode-line-format tf) + (force-mode-line-update))))))) +(defun speedbar-temp-buffer-show-function (buffer) + "Placed in the variable `temp-buffer-show-function' in `speedbar-mode'. +If a user requests help using \\[help-command] <Key> the temp BUFFER will be +redirected into a window on the attached frame." + (if speedbar-attached-frame (select-frame speedbar-attached-frame)) + (pop-to-buffer buffer nil) + (other-window -1) + (run-hooks 'temp-buffer-show-hook)) + +(defun speedbar-reconfigure-menubar () + "Reconfigure the menu-bar in a speedbar frame. +Different menu items are displayed depending on the current display mode +and the existence of packages." + (let ((km (make-sparse-keymap)) + (cf (selected-frame)) + (md (append speedbar-easymenu-definition-base + (if speedbar-shown-directories + ;; file display mode version + speedbar-easymenu-definition-special + (save-excursion + (select-frame speedbar-attached-frame) + (if (local-variable-p + 'speedbar-easymenu-definition-special) + ;; If bound locally, we can use it + speedbar-easymenu-definition-special))) + ;; The trailer + speedbar-easymenu-definition-trailer))) + (easy-menu-define speedbar-menu-map speedbar-key-map "Speedbar menu" md) + (if speedbar-xemacsp (set-buffer-menubar (list km))))) + + +;;; User Input stuff +;; (defun speedbar-mouse-hscroll (e) - "Read a mouse event from the mode line, and horizontally scroll if the -mouse is being clicked on the far left, or far right of the modeline." + "Read a mouse event E from the mode line, and horizontally scroll. +If the mouse is being clicked on the far left, or far right of the +mode-line. This is only useful for non-XEmacs" (interactive "e") (let* ((xp (car (nth 2 (car (cdr e))))) (cpw (/ (frame-pixel-width) @@ -442,17 +910,234 @@ ;;(message "X: Pixel %d Char Pixels %d On char %d" xp cpw oc) )) +(defun speedbar-get-focus () + "Change frame focus to or from the speedbar frame. +If the selected frame is not speedbar, then speedbar frame is +selected. If the speedbar frame is active, then select the attached frame." + (interactive) + (if (eq (selected-frame) speedbar-frame) + (if (frame-live-p speedbar-attached-frame) + (select-frame speedbar-attached-frame)) + ;; make sure we have a frame + (if (not (frame-live-p speedbar-frame)) (speedbar-frame-mode 1)) + ;; go there + (select-frame speedbar-frame)) + (other-frame 0)) + +(defun speedbar-next (arg) + "Move to the next ARGth line in a speedbar buffer." + (interactive "p") + (forward-line (or arg 1)) + (speedbar-item-info) + (speedbar-position-cursor-on-line)) + +(defun speedbar-prev (arg) + "Move to the previous ARGth line in a speedbar buffer." + (interactive "p") + (speedbar-next (if arg (- arg) -1))) + +(defun speedbar-scroll-up (&optional arg) + "Page down one screen-full of the speedbar, or ARG lines." + (interactive "P") + (scroll-up arg) + (speedbar-position-cursor-on-line)) + +(defun speedbar-scroll-down (&optional arg) + "Page up one screen-full of the speedbar, or ARG lines." + (interactive "P") + (scroll-down arg) + (speedbar-position-cursor-on-line)) + +(defun speedbar-up-directory () + "Keyboard accelerator for moving the default directory up one. +Assumes that the current buffer is the speedbar buffer" + (interactive) + (setq default-directory (expand-file-name (concat default-directory "../"))) + (speedbar-update-contents)) -;;; +;;; Speedbar file activity +;; +(defun speedbar-refresh () + "Refresh the current speedbar display, disposing of any cached data." + (interactive) + (let ((dl speedbar-shown-directories)) + (while dl + (adelete 'speedbar-directory-contents-alist (car dl)) + (setq dl (cdr dl)))) + (if (<= 1 speedbar-verbosity-level) (message "Refreshing speedbar...")) + (speedbar-update-contents) + (speedbar-stealthy-updates) + ;; Reset the timer in case it got really hosed for some reason... + (speedbar-set-timer speedbar-update-speed) + (if (<= 1 speedbar-verbosity-level) (message "Refreshing speedbar...done"))) + +(defun speedbar-item-load () + "Byte compile the item under the cursor or mouse if it is a lisp file." + (interactive) + (let ((f (speedbar-line-file))) + (if (and (file-exists-p f) (string-match "\\.el$" f)) + (if (and (file-exists-p (concat f "c")) + (y-or-n-p (format "Load %sc? " f))) + ;; If the compiled version exists, load that instead... + (load-file (concat f "c")) + (load-file f)) + (error "Not a loadable file...")))) + +(defun speedbar-item-byte-compile () + "Byte compile the item under the cursor or mouse if it is a lisp file." + (interactive) + (let ((f (speedbar-line-file)) + (sf (selected-frame))) + (if (and (file-exists-p f) (string-match "\\.el$" f)) + (progn + (select-frame speedbar-attached-frame) + (byte-compile-file f nil) + (select-frame sf))) + )) + +(defun speedbar-mouse-item-info (event) + "Provide information about what the user clicked on. +This should be bound to a mouse EVENT." + (interactive "e") + (mouse-set-point event) + (speedbar-item-info)) + +(defun speedbar-item-info () + "Display info in the mini-buffer about the button the mouse is over." + (interactive) + (if (not speedbar-shown-directories) + nil + (let* ((item (speedbar-line-file)) + (attr (if item (file-attributes item) nil))) + (if item (message "%s %d %s" (nth 8 attr) (nth 7 attr) item) + (save-excursion + (beginning-of-line) + (looking-at "\\([0-9]+\\):") + (setq item (speedbar-line-path (string-to-int (match-string 1)))) + (if (re-search-forward "> \\([^ ]+\\)$" + (save-excursion(end-of-line)(point)) t) + (progn + (setq attr (get-text-property (match-beginning 1) + 'speedbar-token)) + (message "Tag %s in %s at position %s" + (match-string 1) item (if attr attr 0))) + (message "No special info for this line."))) + )))) + +(defun speedbar-item-copy () + "Copy the item under the cursor. +Files can be copied to new names or places." + (interactive) + (let ((f (speedbar-line-file))) + (if (not f) (error "Not a file.")) + (if (file-directory-p f) + (error "Cannot copy directory.") + (let* ((rt (read-file-name (format "Copy %s to: " + (file-name-nondirectory f)) + (file-name-directory f))) + (refresh (member (expand-file-name (file-name-directory rt)) + speedbar-shown-directories))) + ;; Create the right file name part + (if (file-directory-p rt) + (setq rt + (concat (expand-file-name rt) + (if (string-match "/$" rt) "" "/") + (file-name-nondirectory f)))) + (if (or (not (file-exists-p rt)) + (y-or-n-p (format "Overwrite %s with %s? " rt f))) + (progn + (copy-file f rt t t) + ;; refresh display if the new place is currently displayed. + (if refresh + (progn + (speedbar-refresh) + (if (not (speedbar-goto-this-file rt)) + (speedbar-goto-this-file f)))) + )))))) + +(defun speedbar-item-rename () + "Rename the item under the cursor or mouse. +Files can be renamed to new names or moved to new directories." + (interactive) + (let ((f (speedbar-line-file))) + (if f + (let* ((rt (read-file-name (format "Rename %s to: " + (file-name-nondirectory f)) + (file-name-directory f))) + (refresh (member (expand-file-name (file-name-directory rt)) + speedbar-shown-directories))) + ;; Create the right file name part + (if (file-directory-p rt) + (setq rt + (concat (expand-file-name rt) + (if (string-match "/$" rt) "" "/") + (file-name-nondirectory f)))) + (if (or (not (file-exists-p rt)) + (y-or-n-p (format "Overwrite %s with %s? " rt f))) + (progn + (rename-file f rt t) + ;; refresh display if the new place is currently displayed. + (if refresh + (progn + (speedbar-refresh) + (speedbar-goto-this-file rt) + ))))) + (error "Not a file.")))) + +(defun speedbar-item-delete () + "Delete the item under the cursor. Files are removed from disk." + (interactive) + (let ((f (speedbar-line-file))) + (if (not f) (error "Not a file.")) + (if (y-or-n-p (format "Delete %s? " f)) + (progn + (if (file-directory-p f) + (delete-directory f) + (delete-file f)) + (message "Okie dokie..") + (let ((p (point))) + (speedbar-refresh) + (goto-char p)) + )) + )) + +(defun speedbar-enable-update () + "Enable automatic updating in speedbar via timers." + (interactive) + (setq speedbar-update-flag t) + (speedbar-set-mode-line-format) + (speedbar-set-timer speedbar-update-speed)) + +(defun speedbar-disable-update () + "Disable automatic updating and stop consuming resources." + (interactive) + (setq speedbar-update-flag nil) + (speedbar-set-mode-line-format) + (speedbar-set-timer nil)) + +(defun speedbar-toggle-updates () + "Toggle automatic update for the speedbar frame." + (interactive) + (if speedbar-update-flag + (speedbar-disable-update) + (speedbar-enable-update))) + +(defun speedbar-toggle-show-all-files () + "Toggle display of files speedbar can not tag." + (interactive) + (setq speedbar-show-unknown-files (not speedbar-show-unknown-files)) + (speedbar-refresh)) + ;;; Utility functions -;;; +;; (defun speedbar-set-timer (timeout) - "Unset an old timer (if there is one) and activate a new timer with the -given timeout value." - (cond + "Unset an old timer (if there is one) and activate a new timer with TIMEOUT. +TIMEOUT is the number of seconds until the speedbar timer is called +again." + (cond ;; Xemacs (speedbar-xemacsp - (if speedbar-timer + (if speedbar-timer (progn (delete-itimer speedbar-timer) (setq speedbar-timer nil))) (if timeout @@ -460,19 +1145,24 @@ 'speedbar-timer-fn timeout nil)))) - ;; GNU emacs - (t - (if speedbar-timer + ;; Post 19.31 Emacs + ((fboundp 'run-with-idle-timer) + (if speedbar-timer (progn (cancel-timer speedbar-timer) (setq speedbar-timer nil))) (if timeout - (setq speedbar-timer + (setq speedbar-timer (run-with-idle-timer timeout nil 'speedbar-timer-fn)))) - )) + ;; Older or other Emacsen with no timers. Set up so that it's + ;; obvious this emacs can't handle the updates + (t + (setq speedbar-update-flag nil))) + ;; change this if it changed for some reason + (speedbar-set-mode-line-format)) (defmacro speedbar-with-writable (&rest forms) - "Allow the buffer to be writable and evaluate forms. Turn read-only back -on when done." + "Allow the buffer to be writable and evaluate FORMS. +Turn read only back on when done." (list 'let '((speedbar-with-writable-buff (current-buffer))) '(toggle-read-only -1) (cons 'progn forms) @@ -480,37 +1170,99 @@ (toggle-read-only 1)))) (put 'speedbar-with-writable 'lisp-indent-function 0) +(defun speedbar-select-window (buffer) + "Select a window in which BUFFER is show. +If it is not shown, force it to appear in the default window." + (let ((win (get-buffer-window buffer speedbar-attached-frame))) + (if win + (select-window win) + (show-buffer (selected-window) buffer)))) + +(defmacro speedbar-with-attached-buffer (&rest forms) + "Execute FORMS in the attached frame's special buffer. +Optionally select that frame if necessary." + ;; Reset the timer with a new timeout when cliking a file + ;; in case the user was navigating directories, we can cancel + ;; that other timer. + (list + 'progn + '(speedbar-set-timer speedbar-update-speed) + (list + 'let '((cf (selected-frame))) + '(select-frame speedbar-attached-frame) + '(speedbar-select-window speedbar-desired-buffer) + (cons 'progn forms) + '(select-frame cf) + '(speedbar-maybee-jump-to-attached-frame) + ))) + +(defun speedbar-insert-button (text face mouse function + &optional token prevline) + "Insert TEXT as the next logical speedbar button. +FACE is the face to put on the button, MOUSE is the highlight face to use. +When the user clicks on TEXT, FUNCTION is called with the TOKEN parameter. +This function assumes that the current buffer is the speedbar buffer. +If PREVLINE, then put this button on the previous line. + +This is a convenience function for special mode that create their own +specialized speedbar displays." + (goto-char (point-max)) + (if (/= (current-column) 0) (insert "\n")) + (if prevline (progn (delete-char -1) (insert " "))) ;back up if desired... + (let ((start (point))) + (insert text) + (speedbar-make-button start (point) face mouse function token)) + (let ((start (point))) + (insert "\n") + (put-text-property start (point) 'face nil) + (put-text-property start (point) 'mouse-face nil))) + (defun speedbar-make-button (start end face mouse function &optional token) - "Create a button from START to END, with FACE as the display face -and MOUSE and the mouse face. When this button is clicked on FUNCTION -will be run with the token parameter of TOKEN (any lisp object)" + "Create a button from START to END, with FACE as the display face. +MOUSE is the mouse face. When this button is clicked on FUNCTION +will be run with the TOKEN parameter (any lisp object)" (put-text-property start end 'face face) (put-text-property start end 'mouse-face mouse) (put-text-property start end 'invisible nil) (if function (put-text-property start end 'speedbar-function function)) (if token (put-text-property start end 'speedbar-token token)) ) - + +;;; File button management +;; (defun speedbar-file-lists (directory) - "Create file lists for DIRECTORY. The car is the list of -directories, the cdr is list of files not matching ignored headers." - (let ((default-directory directory) - (dir (directory-files directory nil)) - (dirs nil) - (files nil)) - (while dir - (if (not (string-match speedbar-file-unshown-regexp (car dir))) - (if (file-directory-p (car dir)) - (setq dirs (cons (car dir) dirs)) - (setq files (cons (car dir) files)))) - (setq dir (cdr dir))) - (cons (nreverse dirs) (list (nreverse files)))) - ) + "Create file lists for DIRECTORY. +The car is the list of directories, the cdr is list of files not +matching ignored headers. Cache any directory files found in +`speedbar-directory-contents-alist' and use that cache before scanning +the file-system" + (setq directory (expand-file-name directory)) + ;; If in powerclick mode, then the directory we are getting + ;; should be rescanned. + (if speedbar-power-click + (adelete 'speedbar-directory-contents-alist directory)) + ;; find the directory, either in the cache, or build it. + (or (cdr-safe (assoc directory speedbar-directory-contents-alist)) + (let ((default-directory directory) + (dir (directory-files directory nil)) + (dirs nil) + (files nil)) + (while dir + (if (not (string-match speedbar-file-unshown-regexp (car dir))) + (if (file-directory-p (car dir)) + (setq dirs (cons (car dir) dirs)) + (setq files (cons (car dir) files)))) + (setq dir (cdr dir))) + (let ((nl (cons (nreverse dirs) (list (nreverse files))))) + (aput 'speedbar-directory-contents-alist directory nl) + nl)) + )) (defun speedbar-directory-buttons (directory index) - "Inserts a single button group at point for DIRECTORY. Each directory -path part is a different button. If part of the path matches the user -directory ~, then it is replaced with a ~" + "Insert a single button group at point for DIRECTORY. +Each directory path part is a different button. If part of the path +matches the user directory ~, then it is replaced with a ~. +INDEX is not used, but is required by the caller." (let* ((tilde (expand-file-name "~")) (dd (expand-file-name directory)) (junk (string-match (regexp-quote tilde) dd)) @@ -530,7 +1282,37 @@ (if (= (match-beginning 1) p) (expand-file-name "~/") ;the tilde (buffer-substring-no-properties - p (match-end 0)))))) + p (match-end 0))))) + ;; Nuke the beginning of the directory if it's too long... + (cond ((eq speedbar-directory-button-trim-method 'span) + (beginning-of-line) + (let ((ww (or (speedbar-frame-width) 20))) + (move-to-column ww nil) + (while (>= (current-column) ww) + (re-search-backward "/" nil t) + (if (<= (current-column) 2) + (progn + (re-search-forward "/" nil t) + (if (< (current-column) 4) + (re-search-forward "/" nil t)) + (forward-char -1))) + (if (looking-at "/?$") + (beginning-of-line) + (insert "/...\n ") + (move-to-column ww nil))))) + ((eq speedbar-directory-button-trim-method 'trim) + (end-of-line) + (let ((ww (or (speedbar-frame-width) 20)) + (tl (current-column))) + (if (< ww tl) + (progn + (move-to-column (- tl ww)) + (if (re-search-backward "/" nil t) + (progn + (delete-region (point-min) (point)) + (insert "$") + ))))))) + ) (if (string-match "^/[^/]+/$" displayme) (progn (insert " ") @@ -541,6 +1323,7 @@ 'speedbar-highlight-face 'speedbar-directory-buttons-follow "/")))) + (end-of-line) (insert-char ?\n 1 nil))) (defun speedbar-make-tag-line (exp-button-type @@ -548,19 +1331,21 @@ exp-button-data tag-button tag-button-function tag-button-data tag-button-face depth) - "Creates a tag line with BUTTON-TYPE for the small button that -expands or contracts a node (if applicable), and BUTTON-CHAR the -character in it (+, -, ?, etc). BUTTON-FUNCTION is the function to -call if it's clicked on. Button types are 'bracket, 'angle, 'curly, or nil. + "Create a tag line with EXP-BUTTON-TYPE for the small expansion button. +This is the button that expands or contracts a node (if applicable), +and EXP-BUTTON-CHAR the character in it (+, -, ?, etc). EXP-BUTTON-FUNCTION +is the function to call if it's clicked on. Button types are +'bracket, 'angle, 'curly, or nil. EXP-BUTTON-DATA is extra data +attached to the text forming the expansion button. -Next, TAG-BUTTON is the text of the tag. TAG-FUNCTION is the function -to call if clicked on, and TAG-DATA is the data to attach to the text -field (such a tag positioning, etc). TAG-FACE is a face used for this -type of tag. +Next, TAG-BUTTON is the text of the tag. TAG-BUTTON-FUNCTION is the +function to call if clicked on, and TAG-BUTTON-DATA is the data to +attach to the text field (such a tag positioning, etc). +TAG-BUTTON-FACE is a face used for this type of tag. Lastly, DEPTH shows the depth of expansion. -This function assumes that the cursor is in the speecbar window at the +This function assumes that the cursor is in the speedbar window at the position to insert a new item, and that the new item will end with a CR" (let ((start (point)) (end (progn @@ -588,16 +1373,16 @@ (end (progn (insert tag-button) (point)))) (insert-char ?\n 1 nil) (put-text-property (1- (point)) (point) 'invisible nil) - (speedbar-make-button start end tag-button-face + (speedbar-make-button start end tag-button-face (if tag-button-function 'speedbar-highlight-face nil) tag-button-function tag-button-data)) ) (defun speedbar-change-expand-button-char (char) - "Change the expanson button character to CHAR for the current line." + "Change the expansion button character to CHAR for the current line." (save-excursion (beginning-of-line) - (if (re-search-forward ":\\s-*.\\([-+?]\\)" (save-excursion (end-of-line) + (if (re-search-forward ":\\s-*.\\([-+?]\\)" (save-excursion (end-of-line) (point)) t) (speedbar-with-writable (goto-char (match-beginning 1)) @@ -605,14 +1390,13 @@ (insert-char char 1 t))))) -;;; ;;; Build button lists -;;; +;; (defun speedbar-insert-files-at-point (files level) - "Insert list of FILES starting at point, and indenting all files to LEVEL -depth. Tag exapndable items with a +, otherwise a ?. Don't highlight ? as -we don't know how to manage them. The input parameter FILES is a cons -cell of the form ( 'dir-list . 'file-list )" + "Insert list of FILES starting at point, and indenting all files to LEVEL. +Tag expandable items with a +, otherwise a ?. Don't highlight ? as we +don't know how to manage them. The input parameter FILES is a cons +cell of the form ( 'DIRLIST . 'FILELIST )" ;; Start inserting all the directories (let ((dirs (car files))) (while dirs @@ -632,175 +1416,534 @@ (setq lst (cdr lst))))) (defun speedbar-default-directory-list (directory index) - "Inserts files for DIRECTORY with level INDEX at point" + "Insert files for DIRECTORY with level INDEX at point." (speedbar-insert-files-at-point (speedbar-file-lists directory) index) - ) + (speedbar-reset-scanners) + (if (= index 0) + ;; If the shown files variable has extra directories, then + ;; it is our responsibility to redraw them all + ;; Luckilly, the nature of inserting items into this list means + ;; that by reversing it, we can easilly go in the right order + (let ((sf (cdr (reverse speedbar-shown-directories)))) + (setq speedbar-shown-directories + (list (expand-file-name default-directory))) + ;; exand them all as we find them + (while sf + (if (speedbar-goto-this-file (car sf)) + (progn + (beginning-of-line) + (if (looking-at "[0-9]+:[ ]*<") + (progn + (goto-char (match-end 0)) + (speedbar-do-function-pointer))) + (setq sf (cdr sf))))) + ))) (defun speedbar-insert-generic-list (level lst expand-fun find-fun) - "At LEVEL, inserts a generic multi-level alist LIST. Associations with -lists get {+} tags (to expand into more nodes) and those with positions -just get a > as the indicator. {+} buttons will have the function -EXPAND-FUN and the token is the CDR list. The token name will have the -function FIND-FUN and not token." + "At LEVEL, insert a generic multi-level alist LST. +Associations with lists get {+} tags (to expand into more nodes) and +those with positions just get a > as the indicator. {+} buttons will +have the function EXPAND-FUN and the token is the CDR list. The token +name will have the function FIND-FUN and not token." ;; Remove imenu rescan button (if (string= (car (car lst)) "*Rescan*") (setq lst (cdr lst))) ;; insert the parts (while lst (cond ((null (car-safe lst)) nil) ;this would be a separator - ((numberp (cdr-safe (car-safe lst))) + ((or (numberp (cdr-safe (car-safe lst))) + (markerp (cdr-safe (car-safe lst)))) (speedbar-make-tag-line nil nil nil nil ;no expand button data (car (car lst)) ;button name - find-fun ;function + find-fun ;function (cdr (car lst)) ;token is position - 'speedbar-tag-face + 'speedbar-tag-face (1+ level))) ((listp (cdr-safe (car-safe lst))) (speedbar-make-tag-line 'curly ?+ expand-fun (cdr (car lst)) (car (car lst)) ;button name - nil nil 'speedbar-tag-face + nil nil 'speedbar-tag-face (1+ level))) (t (message "Ooops!"))) (setq lst (cdr lst)))) -;;; ;;; Timed functions -;;; +;; (defun speedbar-update-contents () - "Update the contents of the speedbar buffer." + "Generically update the contents of the speedbar buffer." (interactive) - (setq speedbar-last-selected-file nil) - (setq speedbar-shown-directories (list (expand-file-name default-directory))) - (let ((cbd default-directory) - (funclst speedbar-initial-expansion-list)) + ;; Set the current special buffer + (setq speedbar-desired-buffer nil) + (if (and speedbar-mode-specific-contents-flag + speedbar-special-mode-expansion-list + (local-variable-p + 'speedbar-special-mode-expansion-list)) + ;(eq (get major-mode 'mode-class 'special))) + (speedbar-update-special-contents) + (speedbar-update-directory-contents))) + +(defun speedbar-update-directory-contents () + "Update the contents of the speedbar buffer based on the current directory." + (let ((cbd (expand-file-name default-directory)) + (funclst speedbar-initial-expansion-list) + (cache speedbar-full-text-cache) + ;; disable stealth during update + (speedbar-stealthy-function-list nil) + (use-cache nil) + ;; Because there is a bug I can't find just yet + (inhibit-quit nil)) (save-excursion (set-buffer speedbar-buffer) + ;; If we are updating contents to a where we are, then this is + ;; really a request to update existing contents, so we must be + ;; careful with our text cache! + (if (member cbd speedbar-shown-directories) + (setq cache nil) + ;; If this directory is NOT in the current list of available + ;; paths, then use the cache, and set the cache to our new + ;; value. Make sure to unhighlight the current file, or if we + ;; come back to this directory, it might be a different file + ;; and then we get a mess! + (if (> (point-max) 1) + (progn + (speedbar-clear-current-file) + (setq speedbar-full-text-cache + (cons speedbar-shown-directories (buffer-string))))) + + ;; Check if our new directory is in the list of directories + ;; show in the text-cahce + (if (member cbd (car cache)) + (setq speedbar-shown-directories (car cache) + use-cache t) + ;; default the shown directories to this list... + (setq speedbar-shown-directories (list cbd))) + ) + (setq speedbar-last-selected-file nil) (speedbar-with-writable (setq default-directory cbd) - (delete-region (point-min) (point-max)) + (erase-buffer) + (if use-cache + (insert (cdr cache)) + (while funclst + (funcall (car funclst) cbd 0) + (setq funclst (cdr funclst))))) + (goto-char (point-min)))) + (speedbar-reconfigure-menubar)) + +(defun speedbar-update-special-contents () + "Used the mode-specific variable to fill in the speedbar buffer. +This should only be used by modes classified as special." + (let ((funclst speedbar-special-mode-expansion-list) + (specialbuff (current-buffer))) + (save-excursion + (setq speedbar-desired-buffer specialbuff) + (set-buffer speedbar-buffer) + ;; If we are leaving a directory, cache it. + (if (not speedbar-shown-directories) + ;; Do nothing + nil + ;; Clean up directory maintenance stuff + (speedbar-clear-current-file) + (setq speedbar-full-text-cache + (cons speedbar-shown-directories (buffer-string)) + speedbar-shown-directories nil)) + ;; Now fill in the buffer with our newly found specialized list. + (speedbar-with-writable (while funclst - (funcall (car funclst) cbd 0) - (setq funclst (cdr funclst))))))) + ;; We do not erase the buffer because these functions may + ;; decide NOT to update themselves. + (funcall (car funclst) specialbuff) + (setq funclst (cdr funclst)))) + (goto-char (point-min)))) + (speedbar-reconfigure-menubar)) (defun speedbar-timer-fn () - "Run whenever emacs is idle to update the speedbar item" - (if (not (and speedbar-frame - (frame-live-p speedbar-frame) - speedbar-attached-frame + "Run whenever emacs is idle to update the speedbar item." + (if (not (and (frame-live-p speedbar-frame) (frame-live-p speedbar-attached-frame))) (speedbar-set-timer nil) - (unwind-protect - (if (frame-visible-p speedbar-frame) - (let ((af (selected-frame))) - (save-window-excursion - (select-frame speedbar-attached-frame) - ;; make sure we at least choose a window to - ;; get a good directory from - (if (string-match "\\*Minibuf-[0-9]+\\*" (buffer-name)) - (other-window 1)) - ;; Update all the contents if directories change! - (if (or (member (expand-file-name default-directory) - speedbar-shown-directories) - (eq af speedbar-frame) - (not (buffer-file-name)) - ) - nil - (message "Updating speedbar to: %s..." default-directory) - (speedbar-update-contents) - (message "Updating speedbar to: %s...done" default-directory))))) - ;; Reset the timer - (speedbar-set-timer speedbar-update-speed) - ;; Ok, un-underline old file, underline current file - (speedbar-update-current-file))) + (condition-case nil + ;; Save all the match data so that we don't mess up executing fns + (save-match-data + (if (and (frame-visible-p speedbar-frame) speedbar-update-flag) + (let ((af (selected-frame))) + (save-window-excursion + (select-frame speedbar-attached-frame) + ;; make sure we at least choose a window to + ;; get a good directory from + (if (string-match "\\*Minibuf-[0-9]+\\*" (buffer-name)) + (other-window 1)) + ;; Update for special mode all the time! + (if (and speedbar-mode-specific-contents-flag + speedbar-special-mode-expansion-list + (local-variable-p + 'speedbar-special-mode-expansion-list)) + ;(eq (get major-mode 'mode-class 'special))) + (speedbar-update-special-contents) + ;; Update all the contents if directories change! + (if (or (member (expand-file-name default-directory) + speedbar-shown-directories) + (string-match speedbar-ignored-path-regexp + (expand-file-name default-directory)) + (member major-mode speedbar-ignored-modes) + (eq af speedbar-frame) + (not (buffer-file-name))) + nil + (if (<= 1 speedbar-verbosity-level) + (message "Updating speedbar to: %s..." + default-directory)) + (speedbar-update-directory-contents) + (if (<= 1 speedbar-verbosity-level) + (message "Updating speedbar to: %s...done" + default-directory)))) + (select-frame af)) + ;; Now run stealthy updates of time-consuming items + (speedbar-stealthy-updates)))) + ;; errors that might occur + (error (message "Speedbar error!"))) + ;; Reset the timer + (speedbar-set-timer speedbar-update-speed)) (run-hooks 'speedbar-timer-hook) ) + +;;; Stealthy activities +;; +(defun speedbar-stealthy-updates () + "For a given speedbar, run all items in the stealthy function list. +Each item returns t if it completes successfully, or nil if +interrupted by the user." + (let ((l speedbar-stealthy-function-list)) + (unwind-protect + (while (and l (funcall (car l))) + (sit-for 0) + (setq l (cdr l))) + ;(message "Exit with %S" (car l)) + ))) + +(defun speedbar-reset-scanners () + "Reset any variables used by functions in the stealthy list as state. +If new functions are added, their state needs to be updated here." + (setq speedbar-vc-to-do-point t) + ) + +(defun speedbar-clear-current-file () + "Locate the file thought to be current, and unhighlight it." + (save-excursion + (set-buffer speedbar-buffer) + (if speedbar-last-selected-file + (speedbar-with-writable + (goto-char (point-min)) + (if (and + speedbar-last-selected-file + (re-search-forward + (concat " \\(" (regexp-quote speedbar-last-selected-file) + "\\)\\(" (regexp-quote speedbar-vc-indicator) + "\\)?\n") + nil t)) + (put-text-property (match-beginning 1) + (match-end 1) + 'face + 'speedbar-file-face)))))) + (defun speedbar-update-current-file () - "Find out what the current file is, and update our visuals to indicate -what it is. This is specific to file names." + "Find the current file is, and update our visuals to indicate its name. +This is specific to file names. If the file name doesn't show up, but +it should be in the list, then the directory cache needs to be +updated." (let* ((lastf (selected-frame)) - (newcf (save-excursion - (select-frame speedbar-attached-frame) - (let ((rf (if (buffer-file-name) - (file-name-nondirectory (buffer-file-name)) - nil))) - (select-frame lastf) - rf))) - (lastb (current-buffer))) - (if (and newcf (not (string= newcf speedbar-last-selected-file))) + (newcfd (save-excursion + (select-frame speedbar-attached-frame) + (let ((rf (if (buffer-file-name) + (buffer-file-name) + nil))) + (select-frame lastf) + rf))) + (newcf (if newcfd (file-name-nondirectory newcfd))) + (lastb (current-buffer)) + (sucf-recursive (boundp 'sucf-recursive))) + (if (and newcf + ;; check here, that way we won't refresh to newcf until + ;; its been written, thus saving ourselves some time + (file-exists-p newcf) + (not (string= newcf speedbar-last-selected-file))) (progn + ;; It is important to select the frame, otherwise the window + ;; we want the cursor to move in will not be updated by the + ;; search-forward command. (select-frame speedbar-frame) + ;; Remove the old file... + (speedbar-clear-current-file) + ;; now highlight the new one. (set-buffer speedbar-buffer) (speedbar-with-writable (goto-char (point-min)) - (if (and - speedbar-last-selected-file - (re-search-forward - (concat " \\(" (regexp-quote speedbar-last-selected-file) "\\)\n") - nil t)) - (put-text-property (match-beginning 1) - (match-end 1) - 'face - 'speedbar-file-face)) - (goto-char (point-min)) - (if (re-search-forward - (concat " \\(" (regexp-quote newcf) "\\)\n") nil t) - (put-text-property (match-beginning 1) - (match-end 1) - 'face - 'speedbar-selected-face)) + (if (re-search-forward + (concat " \\(" (regexp-quote newcf) "\\)\\(" + (regexp-quote speedbar-vc-indicator) + "\\)?\n") nil t) + ;; put the property on it + (put-text-property (match-beginning 1) + (match-end 1) + 'face + 'speedbar-selected-face) + ;; Oops, it's not in the list. Should it be? + (if (and (string-match speedbar-file-regexp newcf) + (string= (file-name-directory newcfd) + (expand-file-name default-directory))) + ;; yes, it is (we will ignore unknowns for now...) + (progn + (speedbar-refresh) + (if (re-search-forward + (concat " \\(" (regexp-quote newcf) "\\)\n") nil t) + ;; put the property on it + (put-text-property (match-beginning 1) + (match-end 1) + 'face + 'speedbar-selected-face))) + ;; if it's not in there now, whatever... + )) (setq speedbar-last-selected-file newcf)) - (forward-line -1) - (speedbar-position-cursor-on-line) + (if (not sucf-recursive) + (progn + (forward-line -1) + (speedbar-position-cursor-on-line))) (set-buffer lastb) - (select-frame lastf))))) + (select-frame lastf) + ))) + ;; return that we are done with this activity. + t) + +;; If it's being used, check for it +(eval-when-compile (or (featurep 'xemacs) (require 'ange-ftp))) + +(defun speedbar-check-vc () + "Scan all files in a directory, and for each see if it's checked out. +See `speedbar-this-file-in-vc' and `speedbar-vc-check-dir-p' for how +to add more types of version control systems." + ;; Check for to-do to be reset. If reset but no RCS is available + ;; then set to nil (do nothing) otherwise, start at the beginning + (save-excursion + (set-buffer speedbar-buffer) + (if (and speedbar-vc-do-check (eq speedbar-vc-to-do-point t) + (speedbar-vc-check-dir-p default-directory) + (not (and (featurep 'ange-ftp) + (string-match (car + (if speedbar-xemacsp + ange-ftp-path-format + ange-ftp-name-format)) + (expand-file-name default-directory))))) + (setq speedbar-vc-to-do-point 0)) + (if (numberp speedbar-vc-to-do-point) + (progn + (goto-char speedbar-vc-to-do-point) + (while (and (not (input-pending-p)) + (re-search-forward "^\\([0-9]+\\):\\s-*\\[[+-]\\] " nil t)) + (setq speedbar-vc-to-do-point (point)) + (if (speedbar-check-vc-this-line) + (speedbar-with-writable + (insert speedbar-vc-indicator)))) + (if (input-pending-p) + ;; return that we are incomplete + nil + ;; we are done, set to-do to nil + (setq speedbar-vc-to-do-point nil) + ;; and return t + t)) + t))) + +(defun speedbar-check-vc-this-line () + "Return t if the file on this line is check of of a version control system. +The one caller-requirement is that the last regexp matching operation +has the current depth stored in (MATCHSTRING 1), and that the cursor +is right in front of the file name." + (let* ((d (string-to-int (match-string 1))) + (f (speedbar-line-path d)) + (fn (buffer-substring-no-properties + (point) (progn (end-of-line) (point)))) + (fulln (concat f fn))) + (if (<= 2 speedbar-verbosity-level) + (message "Speedbar vc check...%s" fulln)) + (and (file-writable-p fulln) + (speedbar-this-file-in-vc f fn)))) + +(defun speedbar-vc-check-dir-p (path) + "Return t if we should bother checking PATH for version control files. +This can be overloaded to add new types of version control systems." + (or + (file-exists-p (concat path "RCS/")) + ;; If SCCS is added in `speedbar-this-file-in-vc' + ;; (file-exists-p (concat path "SCCS/")) + ;; (file-exists-p (getenv "SCCSPATHTHINGIDONTREMEMBER")) + )) + +(defun speedbar-this-file-in-vc (path name) + "Check to see if the file in PATH with NAME is in a version control system. +You can add new VC systems by overriding this function. You can +optimize this function by overriding it and only doing those checks +that will occur on your system." + (or + (file-exists-p (concat path "RCS/" name ",v")) + ;; Is this right? I don't recall + ;;(file-exists-p (concat path "SCCS/," fn)) + ;;(file-exists-p (concat (getenv "SCCSPATHTHING") "/SCCS/," fn)) + )) -;;; ;;; Clicking Activity -;;; +;; (defun speedbar-quick-mouse (e) - "Since mouse events are strange, this will keep the mouse nicely -positioned." + "Since mouse events are strange, this will keep the mouse nicely positioned. +This should be bound to mouse event E." (interactive "e") (mouse-set-point e) - (beginning-of-line) - (forward-char 3) + (speedbar-position-cursor-on-line) ) (defun speedbar-position-cursor-on-line () "Position the cursor on a line." - (beginning-of-line) - (re-search-forward "[]>}]" (save-excursion (end-of-line) (point)) t)) + (let ((oldpos (point))) + (beginning-of-line) + (if (looking-at "[0-9]+:\\s-*..?.? ") + (goto-char (1- (match-end 0))) + (goto-char oldpos)))) + +(defun speedbar-power-click (e) + "Activate any speedbar button as a power click. +This should be bound to mouse event E." + (interactive "e") + (let ((speedbar-power-click t)) + (speedbar-click e))) + +(defun speedbar-click (e) + "Activate any speedbar buttons where the mouse is clicked. +This must be bound to a mouse event. A button is any location of text +with a mouse face that has a text property called `speedbar-function'. +This should be bound to mouse event E." + (interactive "e") + (mouse-set-point e) + (speedbar-do-function-pointer) + (speedbar-quick-mouse e)) + +(defun speedbar-do-function-pointer () + "Look under the cursor and examine the text properties. +From this extract the file/tag name, token, indentation level and call +a function if appropriate" + (let* ((fn (get-text-property (point) 'speedbar-function)) + (tok (get-text-property (point) 'speedbar-token)) + ;; The 1-,+ is safe because scaning starts AFTER the point + ;; specified. This lets the search include the character the + ;; cursor is on. + (tp (previous-single-property-change + (1+ (point)) 'speedbar-function)) + (np (next-single-property-change + (point) 'speedbar-function)) + (txt (buffer-substring-no-properties (or tp (point-min)) + (or np (point-max)))) + (dent (save-excursion (beginning-of-line) + (string-to-number + (if (looking-at "[0-9]+") + (buffer-substring-no-properties + (match-beginning 0) (match-end 0)) + "0"))))) + ;;(message "%S:%S:%S:%s" fn tok txt dent) + (and fn (funcall fn txt tok dent))) + (speedbar-position-cursor-on-line)) + +;;; Reading info from the speedbar buffer +;; +(defun speedbar-line-file (&optional p) + "Retrieve the file or whatever from the line at P point. +The return value is a string representing the file. If it is a +directory, then it is the directory name." + (save-excursion + (save-match-data + (beginning-of-line) + (if (looking-at (concat + "\\([0-9]+\\): *[[<][-+][]>] \\([^ \n]+\\)\\(" + (regexp-quote speedbar-vc-indicator) + "\\)?")) + (let* ((depth (string-to-int (match-string 1))) + (path (speedbar-line-path depth)) + (f (match-string 2))) + (concat path f)) + nil)))) + +(defun speedbar-goto-this-file (file) + "If FILE is displayed, goto this line and return t. +Otherwise do not move and return nil." + (let ((path (substring (file-name-directory (expand-file-name file)) + (length (expand-file-name default-directory)))) + (dest (point))) + (save-match-data + (goto-char (point-min)) + ;; scan all the directories + (while (and path (not (eq path t))) + (if (string-match "^/?\\([^/]+\\)" path) + (let ((pp (match-string 1 path))) + (if (save-match-data + (re-search-forward (concat "> " (regexp-quote pp) "$") + nil t)) + (setq path (substring path (match-end 1))) + (setq path nil))) + (setq path t))) + ;; find the file part + (if (or (not path) (string= (file-name-nondirectory file) "")) + ;; only had a dir part + (if path + (progn + (speedbar-position-cursor-on-line) + t) + (goto-char dest) nil) + ;; find the file part + (let ((nd (file-name-nondirectory file))) + (if (re-search-forward + (concat "] \\(" (regexp-quote nd) + "\\)\\(" (regexp-quote speedbar-vc-indicator) "\\)?$") + nil t) + (progn + (speedbar-position-cursor-on-line) + t) + (goto-char dest) + nil)))))) (defun speedbar-line-path (depth) - "Retrieve the pathname associated with the current line. This may -require traversing backwards and combinding the default directory with -these items." + "Retrieve the pathname associated with the current line. +This may require traversing backwards from DEPTH and combining the default +directory with these items." (save-excursion - (let ((path nil)) - (setq depth (1- depth)) - (while (/= depth -1) - (if (not (re-search-backward (format "^%d:" depth) nil t)) - (error "Error building path of tag") - (cond ((looking-at "[0-9]+:\\s-*<->\\s-+\\([^\n]+\\)$") - (setq path (concat (buffer-substring-no-properties - (match-beginning 1) (match-end 1)) - "/" - path))) - ((looking-at "[0-9]+:\\s-*[-]\\s-+\\([^\n]+\\)$") - ;; This is the start of our path. - (setq path (buffer-substring-no-properties - (match-beginning 1) (match-end 1)))))) - (setq depth (1- depth))) - (concat default-directory path)))) + (save-match-data + (let ((path nil)) + (setq depth (1- depth)) + (while (/= depth -1) + (if (not (re-search-backward (format "^%d:" depth) nil t)) + (error "Error building path of tag") + (cond ((looking-at "[0-9]+:\\s-*<->\\s-+\\([^\n]+\\)$") + (setq path (concat (buffer-substring-no-properties + (match-beginning 1) (match-end 1)) + "/" + path))) + ((looking-at "[0-9]+:\\s-*[-]\\s-+\\([^\n]+\\)$") + ;; This is the start of our path. + (setq path (buffer-substring-no-properties + (match-beginning 1) (match-end 1)))))) + (setq depth (1- depth))) + (if (and path + (string-match (concat (regexp-quote speedbar-vc-indicator) "$") + path)) + (setq path (substring path 0 (match-beginning 0)))) + (concat default-directory path))))) (defun speedbar-edit-line () "Edit whatever tag or file is on the current speedbar line." (interactive) - (beginning-of-line) - (re-search-forward "[]>}] [a-zA-Z0-9]" (save-excursion (end-of-line) (point))) - (speedbar-do-function-pointer)) + (save-excursion + (beginning-of-line) + ;; If this fails, then it is a non-standard click, and as such, + ;; perfectly allowed. + (re-search-forward "[]>}] [a-zA-Z0-9]" + (save-excursion (end-of-line) (point)) t) + (speedbar-do-function-pointer))) (defun speedbar-expand-line () "Expand the line under the cursor." @@ -811,64 +1954,40 @@ (speedbar-do-function-pointer)) (defun speedbar-contract-line () - "Expand the line under the cursor." + "Contract the line under the cursor." (interactive) (beginning-of-line) (re-search-forward ":\\s-*.-. " (save-excursion (end-of-line) (point))) (forward-char -2) (speedbar-do-function-pointer)) -(defun speedbar-click (e) - "When the user clicks mouse 1 on our speedbar, we must decide what -we want to do! The entire speedbar has functions attached to -buttons. All we have to do is extract from the buffer the information -we need. See `speedbar-mode' for the type of behaviour we want to achieve" - (interactive "e") - (mouse-set-point e) - (speedbar-do-function-pointer)) - -(defun speedbar-do-function-pointer () - "Look under the cursor and examine the text properties. From this extract -the file/tag name, token, indentation level and call a function if apropriate" - (let* ((fn (get-text-property (point) 'speedbar-function)) - (tok (get-text-property (point) 'speedbar-token)) - ;; The 1-,+ is safe because scaning starts AFTER the point - ;; specified. This lets the search include the character the - ;; cursor is on. - (tp (previous-single-property-change - (if (get-text-property (1+ (point)) 'speedbar-function) - (1+ (point)) (point)) 'speedbar-function)) - (np (next-single-property-change - (if (and (> (point) 1) (get-text-property (1- (point)) 'speedbar-function)) - (1- (point)) (point)) 'speedbar-function)) - (txt (buffer-substring-no-properties (or tp (point-min)) - (or np (point-max)))) - (dent (save-excursion (beginning-of-line) - (string-to-number - (if (looking-at "[0-9]+") - (buffer-substring-no-properties - (match-beginning 0) (match-end 0)) - "0"))))) - ;;(message "%S:%S:%S:%s" fn tok txt dent) - (and fn (funcall fn txt tok dent))) - (speedbar-position-cursor-on-line)) +(defun speedbar-maybee-jump-to-attached-frame () + "Jump to the attached frame ONLY if this was not a mouse event." + (if (numberp last-input-char) + (progn + (select-frame speedbar-attached-frame) + (other-frame 0)))) (defun speedbar-find-file (text token indent) - "Speedbar click handler for filenames. Clicking the filename loads -that file into the attached buffer." + "Speedbar click handler for filenames. +TEXT, the file will be displayed in the attached frame. +TOKEN is unused, but required by the click handler. INDENT is the +current indentation level." (let ((cdd (speedbar-line-path indent))) - (select-frame speedbar-attached-frame) - (find-file (concat cdd text)) - (speedbar-update-current-file) + (speedbar-find-file-in-frame (concat cdd text)) + (speedbar-stealthy-updates) ;; Reset the timer with a new timeout when cliking a file ;; in case the user was navigating directories, we can cancel ;; that other timer. - (speedbar-set-timer speedbar-update-speed))) + (speedbar-set-timer speedbar-update-speed)) + (speedbar-maybee-jump-to-attached-frame)) (defun speedbar-dir-follow (text token indent) - "Speedbar click handler for directory names. Clicking a directory will -cause the speedbar to list files in the selected subdirectory." - (setq default-directory + "Speedbar click handler for directory names. +Clicking a directory will cause the speedbar to list files in the +the subdirectory TEXT. TOKEN is an unused requirement. The +subdirectory chosen will be at INDENT level." + (setq default-directory (concat (expand-file-name (concat (speedbar-line-path indent) text)) "/")) ;; Because we leave speedbar as the current buffer, @@ -877,28 +1996,46 @@ (speedbar-update-contents) (speedbar-set-timer speedbar-navigating-speed) (setq speedbar-last-selected-file nil) - (speedbar-update-current-file)) + (speedbar-stealthy-updates)) +(defun speedbar-delete-subblock (indent) + "Delete text from point to indentation level INDENT or greater. +Handles end-of-sublist smartly." + (speedbar-with-writable + (save-excursion + (end-of-line) (forward-char 1) + (while (and (not (save-excursion + (re-search-forward (format "^%d:" indent) + nil t))) + (>= indent 0)) + (setq indent (1- indent))) + (delete-region (point) (if (>= indent 0) + (match-beginning 0) + (point-max)))))) (defun speedbar-dired (text token indent) - "Speedbar click handler for filenames. Clicking the filename loads -that file into the attached buffer." - (cond ((string-match "+" text) ;we have to expand this file - (setq speedbar-shown-directories - (cons (expand-file-name + "Speedbar click handler for directory expand button. +Clicking this button expands or contracts a directory. TEXT is the +button clicked which has either a + or -. TOKEN is the directory to be +expanded. INDENT is the current indentation level." + (cond ((string-match "+" text) ;we have to expand this dir + (setq speedbar-shown-directories + (cons (expand-file-name (concat (speedbar-line-path indent) token "/")) speedbar-shown-directories)) (speedbar-change-expand-button-char ?-) + (speedbar-reset-scanners) (save-excursion (end-of-line) (forward-char 1) (speedbar-with-writable - (speedbar-default-directory-list + (speedbar-default-directory-list (concat (speedbar-line-path indent) token "/") (1+ indent))))) ((string-match "-" text) ;we have to contract this node + (speedbar-reset-scanners) (let ((oldl speedbar-shown-directories) (newl nil) - (td (expand-file-name + (td (expand-file-name (concat (speedbar-line-path indent) token)))) (while oldl (if (not (string-match (concat "^" (regexp-quote td)) (car oldl))) @@ -906,20 +2043,17 @@ (setq oldl (cdr oldl))) (setq speedbar-shown-directories newl)) (speedbar-change-expand-button-char ?+) - (save-excursion - (end-of-line) (forward-char 1) - (speedbar-with-writable - (if (save-excursion (re-search-forward (format "^%d:" indent) nil t)) - (delete-region (point) (match-beginning 0)) - (delete-region (point) (point-max))))) + (speedbar-delete-subblock indent) ) (t (error "Ooops... not sure what to do."))) (speedbar-center-buffer-smartly) (setq speedbar-last-selected-file nil) - (save-excursion (speedbar-update-current-file))) + (save-excursion (speedbar-stealthy-updates))) -(defun speedbar-directory-buttons-follow (text token ident) - "Speedbar click handler for default directory buttons." +(defun speedbar-directory-buttons-follow (text token indent) + "Speedbar click handler for default directory buttons. +TEXT is the button clicked on. TOKEN is the directory to follow. +INDENT is the current indentation level and is unused." (setq default-directory token) ;; Because we leave speedbar as the current buffer, ;; update contents will change directory without @@ -928,13 +2062,14 @@ (speedbar-set-timer speedbar-navigating-speed)) (defun speedbar-tag-file (text token indent) - "The cursor is on a selected line. Expand the tags in the specified -file. The parameter TXT and TOK are required, where TXT is the button -clicked, and TOK is the file to expand." + "The cursor is on a selected line. Expand the tags in the specified file. +The parameter TEXT and TOKEN are required, where TEXT is the button +clicked, and TOKEN is the file to expand. INDENT is the current +indentation level." (cond ((string-match "+" text) ;we have to expand this file (let* ((fn (expand-file-name (concat (speedbar-line-path indent) token))) - (lst (if speedbar-use-imenu-package + (lst (if speedbar-use-imenu-flag (let ((tim (speedbar-fetch-dynamic-imenu fn))) (if (eq tim t) (speedbar-fetch-dynamic-etags fn) @@ -952,30 +2087,30 @@ 'speedbar-tag-find)))))) ((string-match "-" text) ;we have to contract this node (speedbar-change-expand-button-char ?+) - (speedbar-with-writable - (save-excursion - (end-of-line) (forward-char 1) - (if (save-excursion (re-search-forward (format "^%d:" indent) nil t)) - (delete-region (point) (match-beginning 0)) - (delete-region (point) (point-max)))))) + (speedbar-delete-subblock indent)) (t (error "Ooops... not sure what to do."))) (speedbar-center-buffer-smartly)) (defun speedbar-tag-find (text token indent) - "For the tag in a file, goto that position" + "For the tag TEXT in a file TOKEN, goto that position. +INDENT is the current indentation level." (let ((file (speedbar-line-path indent))) - (select-frame speedbar-attached-frame) - (find-file file) - (save-excursion (speedbar-update-current-file)) + (speedbar-find-file-in-frame file) + (save-excursion (speedbar-stealthy-updates)) ;; Reset the timer with a new timeout when cliking a file ;; in case the user was navigating directories, we can cancel ;; that other timer. (speedbar-set-timer speedbar-update-speed) - (goto-char token))) + (goto-char token) + ;;(recenter) + (speedbar-maybee-jump-to-attached-frame) + )) (defun speedbar-tag-expand (text token indent) - "For the tag in a file which is really a list of tags of a certain type, -expand or contract that list." + "Expand a tag sublist. Imenu will return sub-lists of specialized tag types. +Etags does not support this feature. TEXT will be the button +string. TOKEN will be the list, and INDENT is the current indentation +level." (cond ((string-match "+" text) ;we have to expand this file (speedbar-change-expand-button-char ?-) (speedbar-with-writable @@ -986,23 +2121,35 @@ 'speedbar-tag-find)))) ((string-match "-" text) ;we have to contract this node (speedbar-change-expand-button-char ?+) - (speedbar-with-writable - (save-excursion - (end-of-line) (forward-char 1) - (if (save-excursion (re-search-forward (format "^%d:" indent) nil t)) - (delete-region (point) (match-beginning 0)))))) + (speedbar-delete-subblock indent)) (t (error "Ooops... not sure what to do."))) (speedbar-center-buffer-smartly)) + +;;; Loading files into the attached frame. +;; +(defun speedbar-find-file-in-frame (file) + "This will load FILE into the speedbar attached frame. +If the file is being displayed in a different frame already, then raise that +frame instead." + (let* ((buff (find-file-noselect file)) + (bwin (get-buffer-window buff 0))) + (if bwin + (progn + (select-window bwin) + (raise-frame (window-frame bwin))) + (if speedbar-power-click + (let ((pop-up-frames t)) (select-window (display-buffer buff))) + (select-frame speedbar-attached-frame) + (switch-to-buffer buff)))) + ) -;;; ;;; Centering Utility -;;; +;; (defun speedbar-center-buffer-smartly () - "Look at the buffer, and center it so that which the user is most -interested in (as far as we can tell) is all visible. This assumes -that the cursor is on a file, or tag of a file which the user is + "Recenter a speedbar buffer so the current indentation level is all visible. +This assumes that the cursor is on a file, or tag of a file which the user is interested in." - (if (<= (count-lines (point-min) (point-max)) + (if (<= (count-lines (point-min) (point-max)) (window-height (selected-window))) ;; whole buffer fits (let ((cp (point))) @@ -1058,68 +2205,100 @@ (goto-char cp))))) -;;; ;;; Tag Management -- Imenu -;;; +;; +(if (string-match "XEmacs" emacs-version) + + nil + +(eval-when-compile (if (locate-library "imenu") (require 'imenu))) + (defun speedbar-fetch-dynamic-imenu (file) - "Use the imenu package to load in file, and extract all the items -tags we wish to display in the speedbar package." -;; (eval-when-compile (require 'imenu)) + "Load FILE into a buffer, and generate tags using Imenu. +Returns the tag list, or t for an error." + ;; Load this AND compile it in + (require 'imenu) (save-excursion (set-buffer (find-file-noselect file)) (condition-case nil - (imenu--make-index-alist t) + (progn + (if speedbar-power-click (setq imenu--index-alist nil)) + (imenu--make-index-alist t)) (error t)))) - +) -;;; -;;; Tag Management -- etags (Not useful for FSF emacs) -;;; +;;; Tag Management -- etags (XEmacs compatibility part) +;; (defvar speedbar-fetch-etags-parse-list - '(("\\.\\([cChH]\\|c++\\|cpp\\|cc\\)$" . speedbar-parse-c-or-c++tag) - ("\\.el\\|\\.emacs" . - "defun\\s-+\\(\\(\\w\\|[-_]\\)+\\)\\s-*\C-?") + '(;; Note that java has the same parse-group as c + ("\\.\\([cChH]\\|c++\\|cpp\\|cc\\|hh\\|java\\)$" . speedbar-parse-c-or-c++tag) + ("\\.el\\|\\.emacs" . "defun\\s-+\\(\\(\\w\\|[-_]\\)+\\)\\s-*\C-?") ("\\.tex$" . speedbar-parse-tex-string) ("\\.p" . "\\(\\(FUNCTION\\|function\\|PROCEDURE\\|procedure\\)\\s-+\\([a-zA-Z0-9_.:]+\\)\\)\\s-*(?^?") ) - "*Alist matching extension vs an expression which will extract the -symbol name we wish to display as match 1. To add a new file type, you -would want to add a new association to the list, where the car -is the file match, and the cdr is the way to extract an element from -the tags output. If the output is complex, use a function symbol -instead of regexp. The function should expect to be at the beginning -of a line in the etags buffer. + "Associations of file extensions and expressions for extracting tags. +To add a new file type, you would want to add a new association to the +list, where the car is the file match, and the cdr is the way to +extract an element from the tags output. If the output is complex, +use a function symbol instead of regexp. The function should expect +to be at the beginning of a line in the etags buffer. -This variable is ignored if `speedbar-use-imenu-package' is `t'") +This variable is ignored if `speedbar-use-imenu-flag' is t") (defvar speedbar-fetch-etags-command "etags" "*Command used to create an etags file. -This variable is ignored if `speedbar-use-imenu-package' is `t'") +This variable is ignored if `speedbar-use-imenu-flag' is t") (defvar speedbar-fetch-etags-arguments '("-D" "-I" "-o" "-") - "*List of arguments to use with `speedbar-fetch-etags-command' to create -an etags output buffer. + "*List of arguments to use with `speedbar-fetch-etags-command'. +This creates an etags output buffer. Use `speedbar-toggle-etags' to +modify this list conveniently. + +This variable is ignored if `speedbar-use-imenu-flag' is t") + +(defun speedbar-toggle-etags (flag) + "Toggle FLAG in `speedbar-fetch-etags-arguments'. +FLAG then becomes a member of etags command line arguments. If flag +is \"sort\", then toggle the value of `speedbar-sort-tags'. If it's +value is \"show\" then toggle the value of +`speedbar-show-unknown-files'. -This variable is ignored if `speedbar-use-imenu-package' is `t'") + This function is a convenience function for XEmacs menu created by +Farzin Guilak <farzin@protocol.com>" + (interactive) + (cond + ((equal flag "sort") + (setq speedbar-sort-tags (not speedbar-sort-tags))) + ((equal flag "show") + (setq speedbar-show-unknown-files (not speedbar-show-unknown-files))) + ((or (equal flag "-C") + (equal flag "-S") + (equal flag "-D")) + (if (member flag speedbar-fetch-etags-arguments) + (setq speedbar-fetch-etags-arguments + (delete flag speedbar-fetch-etags-arguments)) + (add-to-list 'speedbar-fetch-etags-arguments flag))) + (t nil))) (defun speedbar-fetch-dynamic-etags (file) - "For the complete file definition FILE, run etags as a subprocess, -fetch it's output, and create a list of symbols extracted, and their -position in FILE." + "For FILE, run etags and create a list of symbols extracted. +Each symbol will be associated with it's line position in FILE." (let ((newlist nil)) (unwind-protect (save-excursion (if (get-buffer "*etags tmp*") (kill-buffer "*etags tmp*")) ;kill to clean it up + (if (<= 1 speedbar-verbosity-level) (message "Fetching etags...")) (set-buffer (get-buffer-create "*etags tmp*")) - (apply 'call-process speedbar-fetch-etags-command nil - (current-buffer) nil + (apply 'call-process speedbar-fetch-etags-command nil + (current-buffer) nil (append speedbar-fetch-etags-arguments (list file))) (goto-char (point-min)) - (let ((expr + (if (<= 1 speedbar-verbosity-level) (message "Fetching etags...")) + (let ((expr (let ((exprlst speedbar-fetch-etags-parse-list) (ans nil)) (while (and (not ans) exprlst) @@ -1136,15 +2315,34 @@ (forward-line 1))) (message "Sorry, no support for a file of that extension")))) ) - (reverse newlist))) + (if speedbar-sort-tags + (sort newlist (lambda (a b) (string< (car a) (car b)))) + (reverse newlist)))) + +;; This bit donated by Farzin Guilak <farzin@protocol.com> but I'm not +;; sure it's needed with the different sorting method. +;; +;(defun speedbar-clean-etags() +; "Removes spaces before the ^? character, and removes `#define', +;return types, etc. preceding tags. This ensures that the sort operation +;works on the tags, not the return types." +; (save-excursion +; (goto-char (point-min)) +; (while +; (re-search-forward "(?[ \t](?\C-?" nil t) +; (replace-match "\C-?" nil nil)) +; (goto-char (point-min)) +; (while +; (re-search-forward "\\(.*[ \t]+\\)\\([^ \t\n]+.*\C-?\\)" nil t) +; (delete-region (match-beginning 1) (match-end 1))))) (defun speedbar-extract-one-symbol (expr) - "At point in current buffer, return nil, or one alist of the form -of a dotted pair: ( symbol . position ) from etags output. Parse the -output using the regular expression EXPR" + "At point, return nil, or one alist in the form: ( symbol . position ) +The line should contain output from etags. Parse the output using the +regular expression EXPR" (let* ((sym (if (stringp expr) (if (save-excursion - (re-search-forward expr (save-excursion + (re-search-forward expr (save-excursion (end-of-line) (point)) t)) (buffer-substring-no-properties (match-beginning 1) @@ -1157,7 +2355,7 @@ t))) (if (and j sym) (1+ (string-to-int (buffer-substring-no-properties - (match-beginning 2) + (match-beginning 2) (match-end 2)))) 0)))) (if (/= pos 0) @@ -1181,7 +2379,7 @@ ))) (defun speedbar-parse-tex-string () - "Parse a tex string. Only find data which is relevant" + "Parse a Tex string. Only find data which is relevant." (save-excursion (let ((bound (save-excursion (end-of-line) (point)))) (cond ((re-search-forward "\\(section\\|chapter\\|cite\\)\\s-*{[^\C-?}]*}?" bound t) @@ -1190,107 +2388,15 @@ (t nil))))) -;;; -;;; configuration scripts (optional) -;;; -(defun speedbar-configure-options () - "Configure variable options for the speedbar program using dlg-config" - (interactive) - (require 'dlg-config) - (save-excursion - (select-frame speedbar-attached-frame) - (dlg-init) - (let ((oframe (create-widget "Speedbar Options" widget-frame - widget-toplevel-shell - :x 2 :y -3 - :frame-label "Speedbar Options")) - ) - (create-widget "show-unknown" widget-toggle-button oframe - :x 1 :y 1 :label-value "Show files that are not supported by imenu" - :state (data-object-symbol "speedbar-show-unknown-files" - :value speedbar-show-unknown-files - :symbol 'speedbar-show-unknown-files)) - - (create-widget "raiselower" widget-toggle-button oframe - :x 1 :y -1 :label-value "Use frame auto raise/lower property" - :state (data-object-symbol "speedbar-raise-lower" - :value speedbar-raise-lower - :symbol 'speedbar-raise-lower)) - - (create-widget "update-speed" widget-label oframe - :x 1 :y -2 :label-value "Update Delay :") - (create-widget "update-speed-txt" widget-text-field oframe - :width 5 :height 1 :x -2 :y t - :value (data-object-symbol-string-to-int - "update-speed" - :symbol 'speedbar-update-speed - :value (int-to-string speedbar-update-speed))) - (create-widget "update-speed-unit" widget-label oframe - :x -3 :y t :label-value "Seconds") - - (create-widget "navigating-speed" widget-label oframe - :x 1 :y -1 :label-value "Navigating Delay:") - (create-widget "navigating-speed-txt" widget-text-field oframe - :width 5 :height 1 :x -2 :y t - :value (data-object-symbol-string-to-int - "navigating-speed" - :symbol 'speedbar-navigating-speed - :value (int-to-string speedbar-navigating-speed))) - (create-widget "navigating-speed-unit" widget-label oframe - :x -3 :y t :label-value "Seconds") - - (create-widget "width" widget-label oframe - :x 1 :y -2 :label-value "Display Width :") - (create-widget "width-txt" widget-text-field oframe - :width 5 :height 1 :x -2 :y t - :value (data-object-symbol-string-to-int - "width" - :symbol 'speedbar-width - :value (int-to-string speedbar-width))) - (create-widget "width-unit" widget-label oframe - :x -3 :y t :label-value "Characters") - - (create-widget "scrollbar-width" widget-label oframe - :x 1 :y -1 :label-value "Scrollbar Width :") - (create-widget "scrollbar-width-txt" widget-text-field oframe - :width 5 :height 1 :x -2 :y t - :value (data-object-symbol-string-to-int - "width" - :symbol 'speedbar-width - :value (int-to-string speedbar-scrollbar-width))) - (create-widget "scrollbar-width-unit" widget-label oframe - :x -3 :y t :label-value "Pixels") - - - ) - (dlg-end) - (dialog-refresh) - )) - -(defun speedbar-configure-faces () - "Configure faces for the speedbar program using dlg-config." - (interactive) - (require 'dlg-config) - (save-excursion - (select-frame speedbar-attached-frame) - (dlg-faces '(speedbar-button-face - speedbar-file-face - speedbar-directory-face - speedbar-tag-face - speedbar-highlight-face - speedbar-selected-face)))) - -;;; -;;; Color loading section This is message *Blech!* -;;; +;;; Color loading section This is messy *Blech!* +;; (defun speedbar-load-color (sym l-fg l-bg d-fg d-bg &optional bold italic underline) - "Create a color for SYM with a L-FG and L-BG color, or D-FG and -D-BG. Optionally make BOLD, ITALIC, or UNDERLINED if applicable. If -the background attribute of the current frame is determined to be -light (white, for example) then L-FG and L-BG is used. If not, then -D-FG and D-BG is used. This will allocate the colors in the best -possible mannor. This will allow me to store multiple defaults and -dynamically determine which colors to use." + "Create a color for SYM with a L-FG and L-BG color, or D-FG and D-BG. +Optionally make BOLD, ITALIC, or UNDERLINE if applicable. If the background +attribute of the current frame is determined to be light (white, for example) +then L-FG and L-BG is used. If not, then D-FG and D-BG is used. This will +allocate the colors in the best possible manor. This will allow me to store +multiple defaults and dynamically determine which colors to use." (let* ((params (frame-parameters)) (disp-res (if (fboundp 'x-get-resource) (if speedbar-xemacsp @@ -1308,24 +2414,36 @@ nil)) (bgmode (cond (bg-res (intern (downcase bg-res))) - ((and params - (fboundp 'x-color-values) - (< (apply '+ (x-color-values - (cdr (assq 'background-color params)))) - (/ (apply '+ (x-color-values "white")) 3))) + ((let* ((bgc (or (cdr (assq 'background-color params)) + (if speedbar-xemacsp + (x-get-resource ".background" + "Background" 'string) + (x-get-resource ".background" + "Background")) + ;; if no other options, default is white + "white")) + (bgcr (if speedbar-xemacsp + (color-instance-rgb-components + (make-color-instance bgc)) + (x-color-values bgc))) + (wcr (if speedbar-xemacsp + (color-instance-rgb-components + (make-color-instance "white")) + (x-color-values "white")))) + (< (apply '+ bgcr) (/ (apply '+ wcr) 3))) 'dark) (t 'light))) ;our default (set-p (function (lambda (face-name resource) (if speedbar-xemacsp - (x-get-resource + (x-get-resource (concat face-name ".attribute" resource) (concat "Face.Attribute" resource) 'string) - (x-get-resource + (x-get-resource (concat face-name ".attribute" resource) (concat "Face.Attribute" resource))) ))) - (nbg (cond ((eq bgmode 'dark) d-bg) + (nbg (cond ((eq bgmode 'dark) d-bg) (t l-bg))) (nfg (cond ((eq bgmode 'dark) d-fg) (t l-fg)))) @@ -1336,7 +2454,7 @@ (copy-face 'default sym) (if bold (condition-case nil (make-face-bold sym) - (error (message "Cannot make face %s bold!" + (error (message "Cannot make face %s bold!" (symbol-name sym))))) (if italic (condition-case nil (make-face-italic sym) @@ -1349,27 +2467,24 @@ (let ((newface (make-face sym))) ;; For each attribute, check if it might already be set by Xdefaults (if (and nfg (not (funcall set-p (symbol-name sym) "Foreground"))) - (set-face-foreground sym nfg)) + (set-face-foreground newface nfg)) (if (and nbg (not (funcall set-p (symbol-name sym) "Background"))) - (set-face-background sym nbg)) - + (set-face-background newface nbg)) + (if bold (condition-case nil - (make-face-bold sym) + (make-face-bold newface) (error (message "Cannot make face %s bold!" (symbol-name sym))))) (if italic (condition-case nil - (make-face-italic sym) + (make-face-italic newface) (error (message "Cannot make face %s italic!" - (symbol-name sym))))) - (set-face-underline-p sym underline) + (symbol-name newface))))) + (set-face-underline-p newface underline) )))) - ;; JTL <<<< -(if nil ;;(x-display-color-p) ;; just a quick hack so it will run. - ;; we can use customize for this. - ;; <<<< JTL +(if (x-display-color-p) (progn - (speedbar-load-color 'speedbar-button-face "green4" "default" "green3" "default") + (speedbar-load-color 'speedbar-button-face "green4" nil "green3" nil nil nil nil) (speedbar-load-color 'speedbar-file-face "cyan4" nil "cyan" nil nil nil nil) (speedbar-load-color 'speedbar-directory-face "blue4" nil "light blue" nil nil nil nil) (speedbar-load-color 'speedbar-tag-face "brown" nil "yellow" nil nil nil nil) @@ -1388,7 +2503,13 @@ ) ;; monochrome -;;; end of lisp +;; some edebug hooks +(add-hook 'edebug-setup-hook + (lambda () + (def-edebug-spec speedbar-with-writable def-body))) + +;; run load-time hooks +(run-hooks 'speedbar-load-hook) + (provide 'speedbar) - -;;; speedbar.el ends here +;;; speedbar ends here