1 ;;; org-reftable.el --- Ordered lookup table for reference numbers
\r
3 ;; Copyright (C) 2011,2012 Free Software Foundation, Inc.
\r
5 ;; Author: Marc-Oliver Ihm <org-reftable@ferntreffer.de>
\r
6 ;; Keywords: hypermedia, matching
\r
8 ;; Download: http://orgmode.org/worg/code/elisp/org-reftable.el
\r
13 ;; This program is free software; you can redistribute it and/or modify
\r
14 ;; it under the terms of the GNU General Public License as published by
\r
15 ;; the Free Software Foundation; either version 3, or (at your option)
\r
16 ;; any later version.
\r
18 ;; This program is distributed in the hope that it will be useful,
\r
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
21 ;; GNU General Public License for more details.
\r
23 ;; You should have received a copy of the GNU General Public License
\r
24 ;; along with GNU Emacs; see the file COPYING. If not, write to the
\r
25 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
\r
26 ;; Boston, MA 02110-1301, USA.
\r
32 ;; Create, search and look up numbers from a dedicated reference table.
\r
33 ;; These numbers (e.g. "R237" or "-455-") may be used to refer to:
\r
35 ;; - Nodes in Org-mode (insert them into the heading)
\r
37 ;; - Things outside of org (e.g. mailfolders, directories, reports or
\r
40 ;; The table is kept sorted for most frequently or most recently used
\r
41 ;; reference numbers. Additionally, lines can be selected by keywords, so
\r
42 ;; that specific references can be found very easily. Earlier versions of
\r
43 ;; this extension had been named org-refer-by-number.el.
\r
48 ;; - Add these lines to your .emacs:
\r
50 ;; (require 'org-reftable)
\r
51 ;; ;; Later you should probably change this id, as will be explained below
\r
52 ;; (setq org-reftable-id "00e26bef-1929-4110-b8b4-7eb9c9ab1fd4")
\r
53 ;; ;; Optionally assign a key; pick your own favorite
\r
54 ;; (global-set-key (kbd "C-+") 'org-reftable)
\r
56 ;; - Just invoke `org-reftable', which will explain how to complete your
\r
57 ;; setup by creating the necessary reference table.
\r
62 ;; - For the necessary setup read the documentation of `org-reftable-id'
\r
63 ;; (which is, what `org-reftable' shows, as long as your setup is still
\r
66 ;; - For regular usage, see the function `org-reftable'.
\r
71 ;; [2012-12-07 Fr] Version 2.0.0:
\r
72 ;; - renamed the package from \"org-refer-by-number\" to \"org-reftable\"
\r
73 ;; - The format of the reference table has changed ! You need to bring
\r
74 ;; your existing table into the new format by hand (which however is
\r
75 ;; easy and explained below)
\r
76 ;; - Reference table can be sorted after usage count or date of last access
\r
77 ;; - Ask user explicitly, which command to invoke
\r
79 ;; [2012-09-22 Sa] Version 1.5.0:
\r
80 ;; - New command "sort" to sort a buffer or region by reference number
\r
81 ;; - New commands "highlight" and "unhighlight" to mark references
\r
83 ;; [2012-07-13 Fr] Version 1.4.0:
\r
84 ;; - New command "head" to find a headline with a reference number
\r
86 ;; [2012-04-28 Sa] Version 1.3.0:
\r
87 ;; - New commands occur and multi-occur
\r
88 ;; - All commands can now be invoked explicitly
\r
89 ;; - New documentation
\r
92 ;; [2011-12-10 Sa] Version 1.2.0:
\r
93 ;; - Fixed a bug, which lead to a loss of newly created reference numbers
\r
94 ;; - Introduced single and double prefix arguments
\r
95 ;; - Started this Change Log
\r
99 (require 'org-table)
\r
102 (defvar org-reftable-preferred-command nil
\r
103 "Preferred command when choosing")
\r
105 (defvar org-reftable-commands '(occur head new enter leave goto help reorder sort update highlight unhighlight)
\r
106 "List of commands known to org-reftable:
\r
109 occur: If you supply a keyword (text): Apply emacs standard
\r
110 occur operation on the table of references; ask for a
\r
111 string (keyword) to select lines. Occur will only show you
\r
112 references which contain the given keyword, so you can easily
\r
115 If you supply a reference (number): Apply emacs standard
\r
116 multi-occur operation all org-mode buffers to search for a
\r
119 head: Scan all headings until the first one with the given
\r
120 reference number is found
\r
122 new: Create a new reference. Copy any previously selected text
\r
124 leave: Leave the table of references. If the last command has
\r
125 been \"new\", the new reference is copied and ready to yank
\r
127 enter: Just enter the node with the table of references
\r
129 goto: Search for a specific references within the table of
\r
132 help: Show this list of commands
\r
134 all: Show all commands including the less frequently used ones
\r
137 reorder: Temporarily reorder the table of references, e.g. by
\r
138 cound or last access
\r
140 sort: Sort a set of lines (either the active region or the
\r
141 whole buffer) by the references found within each line
\r
143 update: For the given reference update the line in the
\r
146 highlight: Highlight references in region or buffer
\r
148 unhighlight: Remove highlights
\r
151 When prompting for a command, org-reftable puts the most likely
\r
152 chosen one (e.g. \"occur\" or \"new\") at the front of the list,
\r
153 so that you may just type RET. If this command needs additional
\r
154 input (like e.g. \"occur\" does, which needs a string to search
\r
155 for), you may supply this input right away, although you are
\r
156 still beeing prompted for the command (in that case your input
\r
157 will not match any of the given choices).
\r
161 (defvar org-reftable-commands-some '(occur head new leave enter goto all help)
\r
162 "Subset of org-reftable-commands shown initially" )
\r
164 (defvar org-reftable-id nil
\r
165 "Id of the Org-mode node, which contains the reference table.
\r
167 Read below, on how to set up things. See the documentation of
\r
168 `org-reftable' for normal usage after setup.
\r
170 Setup requires two steps:
\r
172 - Adjust your .emacs initialization file
\r
174 - Create a suitable org-mode node
\r
177 Here are the lines, you should add to your .emacs:
\r
179 (require 'org-reftable)
\r
180 ;; Later you should probably change this id, as will be explained below
\r
181 (setq org-reftable-id \"00e26bef-1929-4110-b8b4-7eb9c9ab1fd4\")
\r
182 ;; Optionally assign a key; pick your own favorite
\r
183 (global-set-key (kbd \"C-+\") 'org-reftable)
\r
185 Do not forget to restart emacs to make these lines effective.
\r
187 The id given above is an example, yours can be different.
\r
190 As a second step you need to create the org-mode node, where your
\r
191 reference numbers will be stored. It may look like this:
\r
196 :ID: 00e26bef-1929-4110-b8b4-7eb9c9ab1fd4
\r
200 | | comment | | | |
\r
201 | ref | ;c | count;s | created | last-accessed |
\r
202 |-----+--------------------+---------+---------+---------------|
\r
203 | R1 | My first reference | | | |
\r
207 You may just want to copy this node into one of your org-files.
\r
208 Many things however can or should be adjusted:
\r
210 - The node needs not be a top level node.
\r
212 - Its name is completely at you choice. The node is found
\r
215 - There are two lines of headings above the first hline. The
\r
216 first one is ignored by org-reftable, and you can use them to
\r
217 give meaningful names to columns; the second line however
\r
218 contains configuration information for org-reftable; please
\r
219 read further below for its format.
\r
221 - The sequence of columns does not matter. You may reorder them
\r
222 any way you like; e.g. make the comment-column the last
\r
223 columns within the table.
\r
225 - You can add further columns or even remove the
\r
226 \"Comment\"-column. The columns \"ref\" and \"created\"
\r
227 however are required. Columns \"cound\" and \"last-accessed\"
\r
228 are optional, but highly suggested anyway.
\r
230 - Your references need not start at \"R1\"; However, having an
\r
231 initial row is required (it servers a template for subsequent
\r
234 - Your reference need not have the form \"R1\"; you may just as
\r
235 well choose any text, that contains a single number,
\r
236 e.g. \"reference-{1}\" or \"#1\" or \"++1++\" or \"-1-\". The
\r
237 function `org-reftable' will inspect your first reference and
\r
238 create all subsequent references in the same way.
\r
240 - You may want to change the ID-Property of the node above and
\r
241 create a new one, which is unique (and not just a copy of
\r
242 mine). You need to change it in the lines copied to your .emacs
\r
243 too. However, this is not strictly required to make things
\r
244 work, so you may do this later, after trying out this package.
\r
247 Optionally you may tweak the second header line to adjust
\r
248 `org-reftable' a bit. In the ecample above it looks like this:
\r
251 | ref | ;c | count;s | created | last-accessed |
\r
252 |-----+--------------------+---------+---------+---------------|
\r
254 The different fields have different meanings:
\r
256 - ref : this denotes the column which contains you references
\r
258 - ;c : the flag \"c\" (\"c\" for \"copy\") denotes this column
\r
259 as the one beeing copied on command \"leave\". In the example above,
\r
260 it is the comment-column.
\r
262 - count;s : this is the column which counts last access, whence
\r
263 \"count\"; the flag \"s\" stands for \"sort\", so this is the
\r
264 column after which the table is sorted. You may also sort
\r
265 after columns \"ref\" or \"last-accessed\".
\r
267 - created : date when this reference was created.
\r
269 - last-accessed : date and time, when this reference was last accessed.
\r
272 After this two-step setup process you may invoke `org-reftable'
\r
273 to create a new reference number; read there for instructions on
\r
276 If you have an existing reference table from a version of
\r
277 org-reftable before 2.0 (in fact earlier versions were rather
\r
278 named org-refer-by-number), you need to add a second headline
\r
279 like this, just about the hline to reflect the usage of columns
\r
280 in earlier versions:
\r
284 This will mark the first column as the actual references and the
\r
285 second column as the date of creation. However, to take advantage
\r
286 of the new features you should also add two other columns \"count;s\"
\r
287 (marked as the sort-column) and \"last-accessed\".
\r
291 (defvar org-reftable-windowconfig-before nil
\r
292 "Saved window-configuration for `org-reftable'.
\r
293 This variable is only used internally.")
\r
295 (defvar org-reftable-marker-outside-before nil
\r
296 "Saved position in reftable-buffer bit outside of reftable (if at all).
\r
297 This variable is only used internally.")
\r
299 (defvar org-reftable-last-action nil
\r
300 "Last action performed by `org-reftable'.
\r
301 This variable is only used internally.")
\r
303 (defvar org-reftable-occur-buffer nil
\r
304 "Buffer (if any) with result from occur or multi-occur.
\r
305 This variable is only used internally.")
\r
307 (defvar org-reftable-ref-regex nil
\r
308 "Regular expression to search for
\r
309 This variable is only used internally.")
\r
313 (defun org-reftable (&optional what search)
\r
314 "Create, search and look up numbers from a dedicated reference table.
\r
315 These numbers (e.g. \"R237\" or \"-455-\") may be used to refer to:
\r
317 - Nodes in Org-mode (insert them into the heading)
\r
319 - Things outside of org (e.g. mailfolders, directories, reports or
\r
322 The table is kept sorted for most frequently or most recently used
\r
323 reference numbers. Additionally, lines can be selected by keywords, so
\r
324 that specific references can be found easily.
\r
327 Read below for a detailed description of this function. See the
\r
328 documentation of `org-reftable-id' for the necessary
\r
332 The function `org-reftable' operates on a dedicated table (called
\r
333 the reference table) within a special Org-mode node. The node has
\r
334 to be created as part of your initial setup. Each line of the
\r
335 reference table contains:
\r
339 - Its respective creation date
\r
341 - A number; counting, how often each reference has been
\r
342 used. This number is updated automatically and the table can
\r
343 be sorted according to it, so that most frequently used
\r
344 references appear at the top of the table and can be spotted
\r
347 - Date and time of last access. This column can alternatively be
\r
348 used to sort the table.
\r
350 The reference table is found through the id of the containing
\r
351 node; this id must be stored within `org-reftable-id' (see there
\r
355 The function `org-reftable' is the only interactive function of
\r
356 this package and its sole entry point; it offers several commands
\r
357 to create, find and look up these reference numbers. All of them
\r
358 are described in the docstring of `org-reftable-commands' (see
\r
359 there for details).
\r
362 Finally, org-reftable can also be invoked from elisp; the two
\r
363 optional arguments to be accepted are:
\r
365 search : string to search for
\r
366 what : symbol of the command to invoke
\r
368 An example would be:
\r
370 (org-reftable \"237\" 'head) ;; find heading with ref 237
\r
376 (let (within-node ; True, if we are within node with reference table
\r
377 result-is-visible ; True, if node or occur is visible in any window
\r
378 ref-node-buffer-and-point ; cons with buffer and point of reference node
\r
379 below-cursor ; word below cursor
\r
380 active-region ; active region (if any)
\r
381 guarded-search ; with guard against additional digits
\r
382 commands ; currently active set of selectable commands
\r
383 what-adjusted ; True, if we had to adjust what
\r
384 what-input ; Input on what question (need not necessary be "what")
\r
385 reorder-once ; Column to use for single time sorting
\r
386 parts ; Parts of a typical reference number (which
\r
387 ; need not be a plain number); these are:
\r
388 head ; Any header before number (e.g. "R")
\r
389 maxref ; Maximum number from reference table (e.g. "153")
\r
390 tail ; Tail after number (e.g. "}" or "")
\r
391 ref-regex ; Regular expression to match a reference
\r
392 numcols ; Number of columns in reference table
\r
393 columns ; Associate column names with numbers
\r
394 kill-new-text ; Text that will be appended to kill ring
\r
395 message-text ; Text that will be issued as an explanation,
\r
396 ; what we have done
\r
400 ;; Examine current buffer and location, before turning to reference table
\r
403 ;; Get the content of the active region or the word under cursor
\r
404 (if (and transient-mark-mode
\r
406 (setq active-region (buffer-substring (region-beginning) (region-end))))
\r
407 (setq below-cursor (thing-at-point 'symbol))
\r
410 ;; Find out, if we are within reference table or not
\r
411 (setq within-node (string= (org-id-get) org-reftable-id))
\r
413 ;; Find out, if point in any window is within node with reference table
\r
414 (mapc (lambda (x) (with-current-buffer (window-buffer x)
\r
416 (string= (org-id-get) org-reftable-id)
\r
417 (eq (window-buffer x)
\r
418 org-reftable-occur-buffer))
\r
419 (setq result-is-visible t))))
\r
425 ;; Get decoration of references and highest number from reference table
\r
429 (setq ref-node-buffer-and-point (org-reftable-id-find))
\r
430 (unless ref-node-buffer-and-point
\r
431 (org-reftable-report-setup-error
\r
432 (format "Cannot find node with id \"%s\"" org-reftable-id)))
\r
434 ;; Get configuration of reftable
\r
435 (with-current-buffer (car ref-node-buffer-and-point)
\r
436 (unless (string= (org-id-get) org-reftable-id)
\r
437 ;; Get marker for point within reftable-buffer, but only if outside
\r
438 ;; of reftable (if point is within reftable, we will try to stay at
\r
440 (setq org-reftable-marker-outside-before (point-marker))
\r
441 (goto-char (cdr ref-node-buffer-and-point)))
\r
444 (setq parts (org-reftable-parse-and-adjust-table reorder-once)))
\r
446 ;; Give names to parts of configuration
\r
447 (setq head (nth 0 parts))
\r
448 (setq maxref (nth 1 parts))
\r
449 (setq tail (nth 2 parts))
\r
450 (setq numcols (nth 3 parts))
\r
451 (setq columns (nth 4 parts))
\r
452 (setq ref-regex (nth 5 parts))
\r
455 ;; Find out, what we are supposed to do
\r
458 (if (equal what '(4)) (setq what 'leave))
\r
460 ;; Set preferred action, that will be the default choice
\r
461 (setq org-reftable-preferred-command
\r
463 (if (eq org-reftable-last-action 'new)
\r
468 (if (and below-cursor (string-match ref-regex below-cursor))
\r
474 (setq commands (copy-list org-reftable-commands-some))
\r
477 (org-icompleting-read
\r
479 (mapcar 'symbol-name
\r
480 ;; Construct unique list of commands with
\r
481 ;; preferred one at front
\r
482 (delq nil (delete-dups
\r
484 (list org-reftable-preferred-command)
\r
487 (setq what (intern what-input))
\r
489 ;; user is not required to input one of the commands; if
\r
490 ;; not, take the first one and use the original input for
\r
492 (if (memq what commands)
\r
493 ;; input matched one element of list, dont need original
\r
495 (setq what-input nil)
\r
496 ;; what-input will be used for next question, use first
\r
497 ;; command for what
\r
498 (setq what (or org-reftable-preferred-command
\r
500 ;; remove any trailing dot, that user might have added to
\r
501 ;; disambiguate his input
\r
502 (if (equal (substring what-input -1) ".")
\r
503 ;; but do this only, if dot was really necessary to
\r
505 (let ((shortened-what-input (substring what-input 0 -1)))
\r
506 (unless (test-completion shortened-what-input
\r
507 (mapcar 'symbol-name
\r
508 org-reftable-commands))
\r
509 (setq what-input shortened-what-input)))))
\r
512 ;; ask for reorder in loop, because we have to ask for
\r
513 ;; what right again
\r
514 (if (eq what 'reorder)
\r
517 (org-icompleting-read
\r
518 "Please choose column to reorder reftable once: "
\r
519 (mapcar 'symbol-name '(ref count last-accessed))
\r
522 ;; offer extended selection of commands, if asked for
\r
524 (setq commands (copy-list org-reftable-commands)))
\r
526 ;; maybe ask initial question again
\r
527 (memq what '(reorder all)))))
\r
531 ;; Get search, if required
\r
534 ;; These actions need a search string:
\r
535 (when (memq what '(goto occur head update))
\r
537 ;; Maybe we've got a search string from the arguments
\r
539 (let (search-from-table
\r
540 search-from-cursor)
\r
542 ;; Search string can come from several sources:
\r
543 ;; From ref column of table
\r
545 (save-excursion (setq search-from-table (org-table-get-field (cdr (assoc 'ref columns)))))
\r
546 (if (string= search-from-table "") (setq search-from-table nil)))
\r
547 ;; From string below cursor
\r
548 (when (and (not within-node)
\r
550 (string-match (concat "\\(" ref-regex "\\)")
\r
552 (setq search-from-cursor (match-string 1 below-cursor)))
\r
554 ;; Depending on requested action, get search from one of the sources above
\r
555 (cond ((eq what 'goto)
\r
556 (setq search (or what-input search-from-cursor)))
\r
557 ((memq what '(head occur))
\r
558 (setq search (or what-input search-from-table search-from-cursor))))))
\r
561 ;; If we still do not have a search string, ask user explicitly
\r
565 (setq search what-input)
\r
566 (setq search (read-from-minibuffer
\r
567 (cond ((memq what '(goto occur head))
\r
568 "Text or reference number to search for: ")
\r
570 "Reference number to update: ")))))
\r
572 (if (string-match "^\\s *[0-9]*\\s *$" search)
\r
573 (unless (string= search "")
\r
574 (setq search (format "%s%s%s" head (org-trim search) tail)))))
\r
576 ;; Clean up search string
\r
577 (if (string= search "") (setq search nil))
\r
578 (if search (setq search (org-trim search)))
\r
580 (setq guarded-search
\r
581 (concat (regexp-quote search)
\r
582 ;; if there is no tail in reference number, we
\r
583 ;; have to guard agains trailing digits
\r
584 (if (string= tail "") "\\($\\|[^0-9]\\)" "")))
\r
588 ;; Do some sanity checking before really starting
\r
591 ;; Correct requested action, if nothing to search
\r
592 (when (and (not search)
\r
593 (memq what '(search occur head)))
\r
595 (setq what-adjusted t))
\r
597 ;; Check for invalid combinations of arguments; try to be helpful
\r
598 (if (string-match ref-regex search)
\r
600 ;; Count searches and update last access date
\r
601 (if search (org-reftable-update-reference-line search columns))
\r
602 (if (eq what 'occur) (setq what 'multi-occur)))
\r
603 (when (memq what '(goto head))
\r
604 (error "Can do '%s' only for a number (not '%s'), try 'occur' to search for text" what search))))
\r
611 ;; Move into table, if outside
\r
612 (when (memq what '(enter new goto occur multi-occur))
\r
613 ;; Save current window configuration
\r
614 (when (or (not result-is-visible)
\r
615 (not org-reftable-windowconfig-before))
\r
616 (setq org-reftable-windowconfig-before (current-window-configuration)))
\r
618 ;; Switch to reference table
\r
619 (org-pop-to-buffer-same-window (car ref-node-buffer-and-point))
\r
620 (goto-char (cdr ref-node-buffer-and-point))
\r
622 (org-show-context))
\r
626 ;; Actually do, what is requested
\r
635 ;; which sort of help ?
\r
639 (org-icompleting-read
\r
641 (mapcar 'symbol-name '(commands usage setup))
\r
644 ;; help is taken from docstring of functions or variables
\r
645 (cond ((eq help-what 'help-commands)
\r
646 (org-reftable-show-help 'org-reftable-commands))
\r
647 ((eq help-what 'help-usage)
\r
648 (org-reftable-show-help 'org-reftable))
\r
649 ((eq help-what 'help-setup)
\r
650 (org-reftable-show-help 'org-reftable-id)))))
\r
653 ((eq what 'multi-occur)
\r
655 ;; Conveniently position cursor on number to search for
\r
656 (org-reftable-goto-top)
\r
657 (let (found (initial (point)))
\r
658 (while (and (not found)
\r
662 (setq found (string= search
\r
663 (org-trim (org-table-get-field (cdr (assoc 'ref columns))))))))
\r
665 (org-table-goto-column (cdr (assoc 'ref columns)))
\r
666 (goto-char initial)))
\r
668 ;; Construct list of all org-buffers
\r
669 (let (buff org-buffers)
\r
670 (dolist (buff (buffer-list))
\r
672 (if (string= major-mode "org-mode")
\r
673 (setq org-buffers (cons buff org-buffers))))
\r
676 (multi-occur org-buffers guarded-search)
\r
677 (if (get-buffer "*Occur*")
\r
679 (setq message-text (format "multi-occur for '%s'" search))
\r
680 (setq org-reftable-occur-buffer (get-buffer "*Occur*"))
\r
682 (toggle-truncate-lines 1))
\r
683 (setq message-text (format "Did not find '%s'" search)))))
\r
688 (message (format "Scanning headlines for '%s' ..." search))
\r
689 (let (buffer point)
\r
692 ;; loop over all headlines, stop on first match
\r
695 (when (looking-at (concat ".*\\b" guarded-search))
\r
696 (setq buffer (current-buffer))
\r
697 (setq point (point))
\r
698 (throw 'found t)))
\r
702 (setq message-text (format "Found '%s'" search))
\r
703 (org-pop-to-buffer-same-window buffer)
\r
706 (setq message-text (format "Did not find '%s'" search)))))
\r
711 (when result-is-visible
\r
713 ;; If we are within the occur-buffer, switch over to get current line
\r
714 (if (and (string= (buffer-name) "*Occur*")
\r
715 (eq org-reftable-last-action 'occur))
\r
716 (occur-mode-goto-occurrence))
\r
718 (let (copy-column)
\r
719 ;; Try to copy requested column
\r
720 (setq copy-column (cdr (assoc
\r
721 (if (eq org-reftable-last-action 'new)
\r
726 ;; Add to kill ring
\r
727 (if (memq org-reftable-last-action '(new enter goto occur))
\r
728 (setq kill-new-text
\r
729 (org-trim (org-table-get-field copy-column))))))
\r
731 ;; Restore position within buffer with reference table
\r
732 (with-current-buffer (car ref-node-buffer-and-point)
\r
733 (when org-reftable-marker-outside-before
\r
734 (goto-char (marker-position org-reftable-marker-outside-before))
\r
735 (move-marker org-reftable-marker-outside-before nil)))
\r
737 ;; Restore windowconfig
\r
738 (if org-reftable-windowconfig-before
\r
740 ;; Restore initial window configuration
\r
741 (set-window-configuration org-reftable-windowconfig-before)
\r
742 (setq org-reftable-windowconfig-before nil)
\r
743 ;; Goto initial position
\r
745 (setq message-text "Back"))
\r
746 ;; We did not have a window-configuration to restore, so we cannot
\r
747 ;; pretend we have returned back
\r
748 (setq message-text "Cannot leave; nowhere to go to")
\r
749 (setq kill-new-text nil)))
\r
754 ;; Go downward in table to requested reference
\r
755 (let (found (initial (point)))
\r
756 (org-reftable-goto-top)
\r
757 (while (and (not found)
\r
763 (org-trim (org-table-get-field (cdr (assoc 'ref columns))))))))
\r
766 (setq message-text (format "Found '%s'" search))
\r
767 (org-table-goto-column (cdr (assoc 'ref columns)))
\r
768 (if (looking-back " ") (backward-char)))
\r
769 (setq message-text (format "Did not find '%s'" search))
\r
770 (goto-char initial)
\r
772 (setq what 'missed))))
\r
777 ;; search for string: occur
\r
779 (org-narrow-to-subtree)
\r
782 (if (get-buffer "*Occur*")
\r
783 (with-current-buffer "*Occur*"
\r
785 ;; install helpful keyboard-shortcuts within occur-buffer
\r
786 (let ((keymap (make-sparse-keymap)))
\r
787 (set-keymap-parent keymap occur-mode-map)
\r
789 (define-key keymap (kbd "RET")
\r
790 (lambda () (interactive)
\r
791 (org-reftable-occur-helper 'head)))
\r
793 (define-key keymap (kbd "<C-return>")
\r
794 (lambda () (interactive)
\r
795 (org-reftable-occur-helper 'multi-occur)))
\r
797 (use-local-map keymap))
\r
798 (setq org-reftable-ref-regex ref-regex)
\r
800 ;; insert some help text
\r
802 (toggle-truncate-lines 1)
\r
803 (let ((inhibit-read-only t))
\r
804 (insert (substitute-command-keys
\r
805 "Type RET to find heading, C-RET for multi-occur, \\[next-error-follow-minor-mode] for follow-mode.\n\n")))
\r
808 (format "Occur for '%s'" search)))
\r
810 (format "Did not find any matches for '%s'" search)))))
\r
816 (org-reftable-goto-top)
\r
817 (let ((new (format "%s%d%s" head (1+ maxref) tail)))
\r
819 (org-table-insert-row)
\r
821 ;; fill special columns with standard values
\r
822 (org-table-goto-column (cdr (assoc 'ref columns)))
\r
824 (org-table-goto-column (cdr (assoc 'created columns)))
\r
825 (org-insert-time-stamp nil nil t)
\r
827 ;; goto first nonempty field
\r
829 (dotimes (col numcols)
\r
830 (org-table-goto-column (+ col 1))
\r
831 (if (string= (org-trim (org-table-get-field)) "")
\r
833 ;; none found, goto first
\r
834 (org-table-goto-column 1))
\r
837 (if active-region (setq kill-new-text active-region))
\r
838 (setq message-text (format "Adding a new row '%s'" new))))
\r
843 ;; simply go into table
\r
844 (org-reftable-goto-top)
\r
848 (setq message-text "Nothing to search for; at reference table")
\r
849 (setq message-text "At reference table")))
\r
854 ;; sort lines according to contained reference
\r
855 (let (begin end where)
\r
857 ;; either active region or whole buffer
\r
858 (if (and transient-mark-mode
\r
860 ;; sort only region
\r
862 (setq begin (region-beginning))
\r
863 (setq end (region-end))
\r
864 (setq where "region"))
\r
865 ;; sort whole buffer
\r
866 (setq begin (point-min))
\r
867 (setq end (point-max))
\r
868 (setq where "whole buffer")
\r
870 (unless (y-or-n-p "Sort whole buffer ")
\r
871 (setq message-text "Sort aborted")
\r
872 (throw 'aborted nil)))
\r
876 (goto-char (point-min))
\r
877 (narrow-to-region begin end)
\r
878 (sort-subr nil 'forward-line 'end-of-line
\r
880 (if (looking-at (concat "^.*\\b" ref-regex "\\b"))
\r
881 (string-to-number (match-string 1))
\r
883 (highlight-regexp ref-regex)
\r
884 (setq message-text (format "Sorted %s from character %d to %d, %d lines"
\r
886 (count-lines begin end)))))))
\r
891 ;; simply update line in reftable
\r
893 (beginning-of-line)
\r
894 (if (org-reftable-update-reference-line search columns)
\r
895 (setq message-text (format "Updated reference '%s'" search))
\r
896 (setq message-text (format "Did not find reference '%s'" search)))))
\r
899 ((memq what '(highlight unhighlight))
\r
901 (let ((where "buffer"))
\r
904 (when (and transient-mark-mode
\r
906 (narrow-to-region (region-beginning) (region-end))
\r
907 (setq where "region"))
\r
909 (if (eq what 'highlight)
\r
911 (highlight-regexp ref-regex)
\r
912 (setq message-text (format "Highlighted references in %s" where)))
\r
913 (unhighlight-regexp ref-regex)
\r
914 (setq message-text (format "Removed highlights for references in %s" where)))))))
\r
917 (t (error "This is a bug: Unmatched condition '%s'" what)))
\r
920 ;; remember what we have done for next time
\r
921 (setq org-reftable-last-action what)
\r
923 ;; tell, what we have done and what can be yanked
\r
924 (if kill-new-text (setq kill-new-text
\r
925 (substring-no-properties kill-new-text)))
\r
926 (if (string= kill-new-text "") (setq kill-new-text nil))
\r
929 (if (and message-text kill-new-text)
\r
931 (if kill-new-text "R" ""))
\r
932 (if kill-new-text (format "eady to yank '%s'" kill-new-text) ""))))
\r
933 (unless (string= m "") (message m)))
\r
934 (if kill-new-text (kill-new kill-new-text))))
\r
938 (defun org-reftable-parse-and-adjust-table (&optional sort-column)
\r
939 "Trim reference table, only used internally"
\r
954 (setq initial-point (point))
\r
955 (org-reftable-goto-top)
\r
961 (org-table-goto-column 100)
\r
962 (setq numcols (- (org-table-current-column) 1))
\r
963 (org-table-goto-column 1)
\r
965 ;; get contents of columns
\r
967 (unless (org-at-table-p)
\r
968 (org-reftable-report-setup-error
\r
969 "Reference table starts with a hline" t))
\r
971 (setq columns (org-reftable-parse-headings numcols))
\r
973 ;; Go beyond end of table
\r
974 (while (org-at-table-p) (forward-line 1))
\r
976 ;; Kill all empty rows at bottom
\r
979 (org-table-goto-column 1)
\r
980 (string= "" (org-trim (org-table-get-field (cdr (assoc 'ref columns))))))
\r
981 (org-table-kill-row))
\r
983 (setq bottom (point))
\r
986 ;; Retrieve any decorations around the number within ref-field of
\r
989 (setq field (org-trim (org-table-get-field (cdr (assoc 'ref columns)))))
\r
990 (or (numberp (string-match "^\\([^0-9]*\\)\\([0-9]+\\)\\([^0-9]*\\)$" field))
\r
991 (org-reftable-report-setup-error
\r
992 (format "reference column in first line of reference table '%s' does not contain a number" field) t))
\r
994 ;; These are the decorations used within the first row of the
\r
996 (setq head (match-string 1 field))
\r
997 (setq tail (match-string 3 field))
\r
998 (setq ref-regex (concat (regexp-quote head)
\r
1000 (regexp-quote tail)))
\r
1002 ;; Save initial ref
\r
1005 (goto-char initial-point)
\r
1006 (setq field (org-table-get-field (cdr (assoc 'ref columns))))
\r
1007 (if (string-match ref-regex field)
\r
1008 (setq initial-ref (concat head (match-string 1 field) tail)))))
\r
1010 ;; Go through table to find maximum number
\r
1013 (while (org-at-table-p)
\r
1014 (setq field (org-trim (org-table-get-field (cdr (assoc 'ref columns)))))
\r
1015 (if (string-match ref-regex field)
\r
1016 (setq ref (string-to-number (match-string 1 field)))
\r
1018 (unless (string= "" field)
\r
1019 (org-reftable-report-setup-error
\r
1020 (format "Reference field in line of reference table '%s' does not contain a number" field) t)))
\r
1021 (if (> ref maxref) (setq maxref ref))
\r
1022 (forward-line 1)))
\r
1024 (setq parts (list head maxref tail numcols columns ref-regex))
\r
1026 ;; sort table after sort-column
\r
1027 (unless sort-column (setq sort-column (cdr (assoc 'sort columns))))
\r
1031 (narrow-to-region (point) bottom)
\r
1037 (ref-field (org-table-get-field
\r
1038 (cdr (assoc 'ref columns)))))
\r
1039 (string-match ref-regex ref-field)
\r
1040 ;; get reference with leading zeroes, so it can be
\r
1042 (setq ref (format
\r
1044 (string-to-number
\r
1045 (match-string 1 ref-field))))
\r
1047 ;; Construct different sort-keys according to
\r
1048 ;; requested sort column; append ref as a secondary
\r
1052 (cond ((eq sort-column 'count)
\r
1055 (string-to-number
\r
1056 (org-table-get-field
\r
1057 (cdr (assoc 'count columns)))))
\r
1060 ((eq sort-column 'last-accessed)
\r
1061 (concat (org-table-get-field
\r
1062 (cdr (assoc 'last-accessed columns)))
\r
1065 ((eq sort-column 'ref)
\r
1069 (error "Bug !")))))
\r
1077 ;; go back to top of table
\r
1080 ;; Goto back to initial ref, because reformatting of table above might
\r
1081 ;; have moved point
\r
1083 (while (and (org-at-table-p)
\r
1084 (not (string= initial-ref (org-trim (org-table-get-field (cdr (assoc 'ref columns)))))))
\r
1086 ;; did not find ref, go back to top
\r
1087 (if (not (org-at-table-p)) (goto-char top)))
\r
1093 (defun org-reftable-goto-top ()
\r
1094 "Goto topmost reference line in reftable"
\r
1096 ;; go to heading of node
\r
1097 (while (not (org-at-heading-p)) (forward-line -1))
\r
1099 ;; go to table within node, but make sure we do not get into another node
\r
1100 (while (and (not (org-at-heading-p))
\r
1101 (not (org-at-table-p))
\r
1102 (not (eq (point) (point-max))))
\r
1105 ;; check, if there really is a table
\r
1106 (unless (org-at-table-p)
\r
1107 (org-reftable-report-setup-error
\r
1108 "Cannot find reference table within reference node" t))
\r
1110 ;; go to first hline
\r
1111 (while (and (not (org-at-table-hline-p))
\r
1116 (unless (org-at-table-hline-p)
\r
1117 (org-reftable-report-setup-error
\r
1118 "Cannot find hline within reference table" t))
\r
1121 (org-table-goto-column 1))
\r
1125 (defun org-reftable-id-find ()
\r
1126 "Find org-reftable-id"
\r
1127 (let ((marker (org-id-find org-reftable-id 'marker))
\r
1128 marker-and-buffer)
\r
1132 (setq marker-and-buffer (cons (marker-buffer marker) (marker-position marker)))
\r
1133 (move-marker marker nil)
\r
1134 marker-and-buffer)
\r
1139 (defun org-reftable-parse-headings (numcols)
\r
1140 "Parse headings to find special columns"
\r
1144 ;; Associate names of special columns with column-numbers
\r
1145 (setq columns (copy-tree '((ref . 0) (created . 0) (last-accessed . 0)
\r
1146 (count . 0) (sort . nil) (copy . nil))))
\r
1148 ;; For each column
\r
1149 (dotimes (col numcols)
\r
1150 (let* (field-flags ;; raw heading, consisting of file name and maybe
\r
1151 ;; flags (seperated by ";")
\r
1152 field ;; field name only
\r
1153 field-symbol ;; and as a symbol
\r
1154 flags ;; flags from field-flags
\r
1157 ;; parse field-flags into field and flags
\r
1158 (setq field-flags (org-trim (org-table-get-field (+ col 1))))
\r
1159 (if (string-match "^\\([^;]*\\);\\([a-z]+\\)$" field-flags)
\r
1161 (setq field (downcase (or (match-string 1 field-flags) "")))
\r
1162 ;; get flags as list of characters
\r
1163 (setq flags (mapcar 'string-to-char
\r
1165 (downcase (match-string 2 field-flags))
\r
1168 (setq field field-flags))
\r
1170 (unless (string= field "") (setq field-symbol (intern (downcase field))))
\r
1172 ;; Check, that no flags appear twice
\r
1174 (when (memq (car x) flags)
\r
1175 (if (cdr (assoc (cdr x) columns))
\r
1176 (org-reftable-report-setup-error
\r
1177 (format "More than one heading is marked with flag '%c'" (car x)) t))))
\r
1182 (if (memq ?s flags)
\r
1183 (setcdr (assoc 'sort columns) field-symbol))
\r
1184 (if (memq ?c flags)
\r
1185 (setcdr (assoc 'copy columns) (+ col 1)))
\r
1187 ;; Store columns in alist
\r
1188 (setq found (assoc field-symbol columns))
\r
1190 (if (> (cdr found) 0)
\r
1191 (org-reftable-report-setup-error
\r
1192 (format "'%s' appears two times as column heading" (downcase field)) t))
\r
1193 (setcdr found (+ col 1)))))
\r
1195 ;; check if all necessary informations have been specified
\r
1196 (unless (> (cdr (assoc 'ref columns)) 0)
\r
1197 (org-reftable-report-setup-error
\r
1198 "column 'ref' has not been set" t))
\r
1200 ;; use ref as a default sort-column
\r
1201 (unless (cdr (assoc 'sort columns))
\r
1202 (setcdr (assoc 'sort columns) 'ref))
\r
1207 (defun org-reftable-report-setup-error (text &optional switch-to-node)
\r
1208 "Report error, which might be related with incomplete setup; offer help"
\r
1210 (when switch-to-node
\r
1211 (org-id-goto org-reftable-id)
\r
1212 (delete-other-windows))
\r
1214 (when (y-or-n-p (concat
\r
1217 "the correct setup is explained in the documentation of 'org-reftable-id'.\n"
\r
1218 "Do you want to read it ? "))
\r
1219 (org-reftable-show-help 'org-reftable-id))
\r
1222 (setq org-reftable-windowconfig-before nil)
\r
1223 (move-marker org-reftable-marker-outside-before nil)
\r
1224 (setq org-reftable-last-action 'leave))
\r
1228 (defun org-reftable-show-help (function-or-variable)
\r
1229 "Show help on command or function and trim help buffer displayed"
\r
1231 (let ((isfun (functionp function-or-variable)))
\r
1232 ;; bring up help-buffer for function or variable
\r
1234 (describe-function function-or-variable)
\r
1235 (describe-variable function-or-variable))
\r
1238 ;; clean up help-buffer
\r
1239 (pop-to-buffer "*Help*")
\r
1240 (let ((inhibit-read-only t))
\r
1241 (goto-char (point-min))
\r
1247 "Documentation:")))))
\r
1248 (kill-line (if isfun 2 1))
\r
1249 (goto-char (point-max))
\r
1251 (goto-char (point-min)))))
\r
1255 (defun org-reftable-update-reference-line (reference columns)
\r
1256 "Update access count and time of reference number"
\r
1258 (let ((initial (point))
\r
1260 (ref-node-buffer-and-point (org-reftable-id-find)))
\r
1261 (with-current-buffer (car ref-node-buffer-and-point)
\r
1262 (goto-char (cdr ref-node-buffer-and-point))
\r
1263 (org-reftable-goto-top)
\r
1264 (while (and (org-at-table-p)
\r
1265 (if (string= reference (org-trim (org-table-get-field (cdr (assoc 'ref columns)))))
\r
1266 (progn (org-table-get-field (cdr (assoc 'count columns))
\r
1267 (number-to-string
\r
1268 (+ 1 (string-to-number
\r
1269 (org-table-get-field (cdr (assoc 'count columns)))))))
\r
1270 (org-table-goto-column (cdr (assoc 'last-accessed columns)))
\r
1271 (org-table-blank-field)
\r
1272 (org-insert-time-stamp nil t t)
\r
1278 (goto-char initial))
\r
1283 (defun org-reftable-occur-helper (action)
\r
1284 "Internal helper function for occur in org-reftable"
\r
1286 (beginning-of-line)
\r
1287 (if (looking-at (concat ".*\\b\\(" org-reftable-ref-regex "\\)\\b"))
\r
1288 (org-reftable action (match-string 1)))))
\r
1291 (provide 'org-reftable)
\r
1293 ;; Local Variables:
\r
1294 ;; fill-column: 75
\r
1295 ;; comment-column: 50
\r
1298 ;;; org-reftable.el ends here
\r