emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Paul Sexton <psexton@xnet.co.nz>
To: emacs-orgmode@gnu.org
Subject: Integrating ctags & org mode (patch)
Date: Tue, 15 Dec 2009 03:02:12 +0000 (UTC)	[thread overview]
Message-ID: <loom.20091215T032440-734@post.gmane.org> (raw)

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 <<like this>> 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 "<M-kp-multiply>") '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 <<double angle brackets>>.
(triple angle brackets work too)

When you click on a link "[[foo]]" and org cannot find a matching "<<foo>>" in
the current buffer, the tags facility will take over. The file TAGS is examined
to see if the tags facility knows about "<<foo>>" in any other files. If it
does, the matching file will be opened and the cursor will jump to the position
of "<<foo>>" 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 <<link
targets>> 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)----

             reply	other threads:[~2009-12-15  3:05 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-12-15  3:02 Paul Sexton [this message]
2009-12-15  3:41 ` Integrating ctags & org mode (patch) Bill Powell
2009-12-15  7:20 ` Carsten Dominik
2009-12-15 14:29   ` Carsten Dominik
2009-12-15 19:58   ` Paul Sexton
2009-12-15 20:36     ` Paul Sexton
2009-12-15 21:08       ` Paul Sexton
2009-12-15 21:33         ` Nick Dokos
2009-12-15 22:06           ` Óscar Fuentes
2009-12-18 16:00         ` Carsten Dominik
2010-02-06  0:10           ` Samuel Wales

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=loom.20091215T032440-734@post.gmane.org \
    --to=psexton@xnet.co.nz \
    --cc=emacs-orgmode@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).