From mboxrd@z Thu Jan 1 00:00:00 1970 From: Paul Sexton Subject: Integrating ctags & org mode (patch) Date: Tue, 15 Dec 2009 03:02:12 +0000 (UTC) Message-ID: Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit Return-path: Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1NKNiw-0000FC-Sa for emacs-orgmode@gnu.org; Mon, 14 Dec 2009 22:05:18 -0500 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1NKNis-00009W-Lf for emacs-orgmode@gnu.org; Mon, 14 Dec 2009 22:05:18 -0500 Received: from [199.232.76.173] (port=47670 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1NKNis-00009P-Gb for emacs-orgmode@gnu.org; Mon, 14 Dec 2009 22:05:14 -0500 Received: from lo.gmane.org ([80.91.229.12]:43567) by monty-python.gnu.org with esmtps (TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.60) (envelope-from ) id 1NKNir-000055-SM for emacs-orgmode@gnu.org; Mon, 14 Dec 2009 22:05:14 -0500 Received: from list by lo.gmane.org with local (Exim 4.50) id 1NKNik-0008FB-Pm for emacs-orgmode@gnu.org; Tue, 15 Dec 2009 04:05:09 +0100 Received: from rp.young.med.auckland.ac.nz ([130.216.140.20]) by main.gmane.org with esmtp (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Tue, 15 Dec 2009 04:05:05 +0100 Received: from psexton by rp.young.med.auckland.ac.nz with local (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Tue, 15 Dec 2009 04:05:05 +0100 List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org To: emacs-orgmode@gnu.org Hi, I have managed to get exuberant ctags working with org mode. This means that plain links [[like this]], instead of defaulting to plain text search when no match is found in the current file, now look for a matching tag <> in all your *.org files, and jumps there. This means your org files all now "seamlessly" talk to each other and interlink. You can split that monolithic file up into smaller files and the plain links still work. You can throw a plain link to a topic you know exists in another file, without having to worry about the format of special inter-file links, whether you got the directory right, and so on. Steps: 1. First you need a small patch to org.el. This is necessary because AFAIK there is no easy way to customise or advise org's behaviour when opening a plain link. The patch is at the end of this post. 2. Next you need to get exuberant ctags from http://ctags.sourceforge.net/ There is a windows executable there (a zip file). Many linux distributions have 'ctags-exuberant' as an installable package in their repositories. If you are using windows, extract the ctags.zip file into a directory somewhere, eg C:\emacs23\ctags 3. Now make a new file called .ctags in your HOME directory. If you are not sure where this is, evaluate (getenv "HOME") in the emacs scratch buffer. Put the following 3 lines into this file, then save it: --langdef=orgmode --langmap=orgmode:.org --regex-orgmode=/<<([^>]+)>>/\1/d,definition/ The last line is a regular expression that defines what a tag is in orgmode. If you don't like my definition based on angle brackets, or you want to add other destinations as tags, just alter the bit between the first two /...slashes.../ 4. Paste the following into your .emacs file: (defvar home-dir "/home/paul/") ; replace with your home dir (defun operating-system () (case system-type ((windows-nt cygwin) 'windows) (darwin 'macos) (otherwise 'unix))) (defvar path-to-ctags (case (operating-system) (windows "c:/emacs/ctags/ctags.exe") ; or whereever you extracted it (unix "/usr/bin/ctags-exuberant")) ; wherever it went "Path to the CTAGS executable.") (defun create-tags (dir-name) "Create tags file." (interactive "DBase directory: ") (shell-command (format "%s --options=%s/.ctags -f %s/TAGS -e -R %s/*" path-to-ctags home-dir dir-name dir-name))) (global-set-key (kbd "") 'pop-tag-mark) (defadvice find-tag (before set-org-mark-before-finding-tag activate compile) "Before trying to find a tag, save our current position on org mark ring." (save-excursion (if (org-mode-p) (org-mark-ring-push)))) 5. Now run create-tags: M-x create-tags ENTER "/path/to/org/files/" ENTER create-tags searches all subdirectories as well, and will also create tags for all source code files that if finds (*.c, *.lisp, *.el, etc). All these tags will go in one big TAGS file, located in the "base" directory that you specify as an argument to create-tags. Thus, if you have any large source trees in subdirectories, create-tags may pause for a few seconds. 6. Check that the file 'TAGS' is in the right place and is not an empty file. Tags is now ready to use. See below for usage. The first time you try and find a tag, you will be asked which tags file to use. The right answer is the file named TAGS which you created with create-tags. To avoid being asked this every time you restart emacs, try putting this in your .emacs: (add-hook 'org-mode-hook (lambda () (visit-tags-table "/path/to/TAGSfile") Tags are defined in the .ctags file as any text in <>. (triple angle brackets work too) When you click on a link "[[foo]]" and org cannot find a matching "<>" in the current buffer, the tags facility will take over. The file TAGS is examined to see if the tags facility knows about "<>" in any other files. If it does, the matching file will be opened and the cursor will jump to the position of "<>" in that file. Important commands: * M-. Press to enter a tag name (default is a string extracted from the current cursor position) and then try & jump there. No autocompletion yet. * C-M-. as above, but search term is a regular expression * M-x tags-search Also searches for a regexp, but searches through the *entire text* of all the files that the tags facility knows about. Jumps to the first match. Then, press M-, to jump to each successive match. * M-* "go back" from a tag jump. (note: if you jumped from an org-mode buffer, your previous position will also have been saved on the org mark ring). Tags mode has no way of knowing when you create new tags. So, any new <> you make after running create-tags will not be in the 'TAGS' file & so will be unknown to the tags facility. For new tags to make it into the TAGS file, you need to re-run (create-tags "path") to refresh the file. You also might want to put (create-tags "/path/to/org/files") in your .emacs or even "ctags-exuberant -e -R /path/to/org/ &" in your .bashrc or equivalent. There is probably a clever way to re-run create-tags at sensible times eg when saving an org file, but I haven't looked into it. Also if someone clever wants to enable tabbed completion when prompted for a tag name with find-tag: please feel free. There are other tags programs that interface with emacs, for example GNU Global. However I believe that to write parsers for new languages like orgmode, you have to write them in C for most of the other tags programs. With exuberant ctags it's just a matter of writing a regular expression. Hope some people find this helpful. Here is the patch. Sadly the dumb gmane web interface thingy forced me to change all '<' to '{' and '>' to '}' before I could post this, as it accused me of top posting. You will therefore need to change them all back. ----BEGIN PATCH for org.el (delete this line)---- 8349,8369c8349,8350 { (condition-case nil (eval cmd) { ;; ORG-TAGS { (error { (progn { (widen) { (condition-case nil (eval cmd) { (error { ;; No matching link found anywhere in this file { ;; See if we can find a tag { ;; If so, jump to it { (condition-case nil (find-tag path) { (error { (cond { (org-make-new-topics-for-missing-links-p { (if (y-or-n-p { (format "Topic `%S' not found; append to current buffer?" { path))) { (org-append-new-topic path nil)) { (t { (error "No match found")))))))))))) { --- } (condition-case nil (eval cmd) } (error (progn (widen) (eval cmd)))))) 8592,8595d8572 { ;; ORG-TAGS { ((not org-open-link-defaults-to-normal-string-search-p) { ;; We don't want to search for a plain text match. { (error "No match.")) 8651,8682d8627 { { { ;; ORG-TAGS { { (defvar org-open-link-defaults-to-normal-string-search-p nil { "Behaviour when attempting to open a 'thisfile' hyperlink for which no { EXACT match can be found (i.e. no match in angled brackets, etc). { If true (default), exhibit normal org behaviour of doing a search for a string { matching the link name. { If nil, abort the attempt to open the link.") { { { (defvar org-make-new-topics-for-missing-links-p nil { "If true, when attempting to follow a 'plain' hyperlink for which no precise { match is found, offer to append a top-level heading with the same name as the { hyperlink, to the end of the buffer.") { { { (defun org-append-new-topic (word) { (interactive "s") { (widen) { (end-of-buffer) { (newline 2) { (insert (format "* <<%s>>" word)) ; <<<>>> to make radio word { (backward-char 4) { ;;(org-update-radio-target-regexp) { (end-of-line) { (newline 2) { (next-line 2)) { { { ----END PATCH (delete this line)----