1aa499eb1d5e58f20ee00fe79dc34535cdc3c414
[worg.git] / code / elisp / org-refer-by-number.el
1 ;;; org-refer-by-number.el --- Create and search numbers used as references\r
2 \r
3 ;; Copyright (C) 2011,2012 Free Software Foundation, Inc.\r
4 \r
5 ;; Author: Marc-Oliver Ihm <ihm@ferntreffer.de>\r
6 ;; Keywords: hypermedia, matching\r
7 ;; Requires: org\r
8 ;; Download: http://orgmode.org/worg/code/elisp/org-refer-by-number.el\r
9 ;; Version: 1.4.0\r
10 \r
11 ;;; License:\r
12 \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
17 ;;\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
22 ;;\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
27 \r
28 ;;; Commentary:\r
29 \r
30 ;; Purpose:\r
31 ;;\r
32 ;;  Refer to things by reference numbers, especially if direct linking is\r
33 ;;  not possible. These reference numbers are added to and kept within a\r
34 ;;  table along with the timestamp of their creation.\r
35 ;;\r
36 ;;  These reference numbers may then be used to refer to things outside of\r
37 ;;  or within Org. E.g. by writing them on a piece of paper or using them\r
38 ;;  as part of a directory name or the heading of an Org node. Within Org\r
39 ;;  you may then refer to these things by their reference number\r
40 ;;  (e.g. "R153"); the numbers can be looked up and searched easily.\r
41 ;;\r
42 ;;  The whole functionality is available through the function\r
43 ;;  `org-refer-by-number'; the necessary setup is described in the\r
44 ;;  docstring of the variable `org-refer-by-number-id'.\r
45 ;;\r
46 ;;  org-refer-by-number.el is only a small add-on to Carsten Dominiks\r
47 ;;  Org-mode, which must be installed as a prerequisite. See\r
48 ;;  http://orgmode.org or elpa for Org-mode itself.\r
49 ;;\r
50 ;; Setup:\r
51 ;;\r
52 ;;  - Adjust these lines and add them to your .emacs:\r
53 ;;\r
54 ;;    (require 'org-refer-by-number)\r
55 ;;    (setq org-refer-by-number-id "00e26bef-1929-4110-b8b4-7eb9c9ab1fd4")\r
56 ;;    ;; Optionally assign a key\r
57 ;;    (global-set-key (kbd "C-+") 'org-refer-by-number)\r
58 ;;\r
59 ;;  - Create an Org-mode node with a reference table\r
60 ;;    as described in the documentation of `org-refer-by-number-id'\r
61 ;;\r
62 ;; Further reading:\r
63 ;;\r
64 ;;  For the necessary setup, see the variable `org-refer-by-number-id';\r
65 ;;  for regular usage, see the function `org-refer-by-number'.\r
66 ;;\r
67 \r
68 ;;; Change Log:\r
69 \r
70 ;;   [2012-07-13 Fr] Version 1.4.0:\r
71 ;;   - New operation heading\r
72 \r
73 ;;   [2012-04-28 Sa] Version 1.3.0:\r
74 ;;   - New operations occur and multi-occur\r
75 ;;   - All operations can now be invoked explicitly\r
76 ;;   - New documentation\r
77 ;;   - Many bugfixes\r
78 \r
79 ;;   [2011-12-10 Sa] Version 1.2.0:\r
80 ;;   - Fixed a bug, which lead to a loss of newly created reference numbers\r
81 ;;   - Introduced single and double prefix arguments\r
82 ;;   - Started this Change Log\r
83 \r
84 ;;; Code:\r
85 \r
86 (require 'org-table)\r
87 \r
88 (defvar org-refer-by-number-id nil \r
89   "Id of the Org-mode node, with the table of reference numbers.\r
90 \r
91 Read below, on how to set up things. See the documentation of\r
92 `org-refer-by-number' for normal usage after setup.\r
93 \r
94 Setup requires two steps:\r
95 \r
96 - Create a suitable org-mode node\r
97 - Adjust your .emacs initialization file\r
98 \r
99 \r
100 Here is how you create the org-mode node, where your reference\r
101 numbers will be stored. It may look like this:\r
102 \r
103 \r
104   * My node for org-refer-by-number\r
105     :PROPERTIES:\r
106     :ID:       00e26bef-1929-4110-b8b4-7eb9c9ab1fd4\r
107     :END:\r
108   \r
109     | Number | Date            | Comment                    |\r
110     |--------+-----------------+----------------------------|\r
111     | R1     | [2012-04-28 Sa] | My first reference number  |\r
112 \r
113 \r
114 You may just want to copy this node into one of your org-files.\r
115 Many things however can or should be adjusted:\r
116 \r
117 - The node needs not be a top level node.\r
118 \r
119 - Its name is completely at you choice. The node is found\r
120   through its ID.\r
121 \r
122 - Column names can be changed.\r
123 \r
124 - You can add further columns or even remove the\r
125   \"Comment\"-column. The columns \"Number\" and \"Date\" however\r
126   are required.\r
127 \r
128 - Your references need not start at \"R1\"; and of course you can\r
129   adjust date and comment.  However, having an initial row is\r
130   required (it servers a template for subsequent references).\r
131 \r
132 - Your reference need not have the form \"R1\"; you may just as\r
133   well choose any text, that contains a single number,\r
134   e.g. \"reference-{1}\" or \"#1\" or \"++1++\". The function\r
135   `org-refer-by-number' will inspect your first reference and\r
136   create all subsequent references in the same way.\r
137     \r
138 - You may want to change the ID-Property of the node above and\r
139   create a new one, which is unique (and not just a copy of\r
140   mine). You need to change it in the lines copied to your .emacs\r
141   too. However, this is not strictly required to make things\r
142   work, so you may do this later, after trying out this package.\r
143 \r
144 \r
145 Having created the node with your reference table, you only need\r
146 to add some lines to your .emacs:\r
147 \r
148   (require 'org-refer-by-number)\r
149   (setq org-refer-by-number-id \"00e26bef-1929-4110-b8b4-7eb9c9ab1fd4\")\r
150   ;; Optionally assign a key\r
151   (global-set-key (kbd \"C-+\") 'org-refer-by-number)\r
152 \r
153 Do not forget to restart emacs to make these lines effective.\r
154 \r
155 \r
156 After this two-step setup you may invoke `org-refer-by-number' to\r
157 create a new reference number; read there for instructions on\r
158 normal usage.\r
159 \r
160 ")\r
161 \r
162 (defvar org-refer-by-number-windowconfig nil \r
163   "Saved window-configuration for `org-refer-by-number'.\r
164 This variable is only used internally.")\r
165 \r
166 (defvar org-refer-by-number-marker nil \r
167   "Saved marker for `org-refer-by-number'.\r
168 This variable is only used internally.")\r
169 \r
170 (defvar org-refer-by-number-last-action nil \r
171   "Last action performed by `org-refer-by-number'.\r
172 This variable is only used internally.")\r
173 \r
174 (defvar org-refer-by-number-occur-buffer nil\r
175   "Buffer (if any) with result from occur or multi-occur.\r
176 This variable is only used internally.")\r
177 \r
178 (defun org-refer-by-number (arg) \r
179   "Add a new reference number or search for an existing one.\r
180 \r
181 These reference numbers may then be used to refer to things\r
182 outside of Org in cases, where direct linking is not\r
183 possible. E.g. you may write them on documents or letters you\r
184 receive or use them on your computer as part of foldernames that\r
185 you create.\r
186 \r
187 Read below for a detailed description of this function. See the\r
188 documentation of `org-refer-by-number-id' for the necessary\r
189 setup.\r
190 \r
191 \r
192 The function `org-refer-by-number' operates on a dedicated\r
193 table (called the reference table) within a special Org-mode\r
194 node. The node has to be created as part of your initial\r
195 setup. The reference table has at least two columns: The\r
196 reference number (automatically increasing by one from row to\r
197 row) and the date of creation. The table is found through the id\r
198 of the containing node; this id must be stored within\r
199 `org-refer-by-number-id' (see there for details).\r
200 \r
201 \r
202 The function `org-refer-by-number' is the only interactive\r
203 function of this package and its sole entry point; it offers seven\r
204 different operations (short names in parens):\r
205 \r
206 - Add a new row with a new reference number and the\r
207   date of creation (\"add\"):\r
208 \r
209 - Search for an existing reference number within your reference\r
210   table (\"search\").\r
211 \r
212 - Find all occurences of a particular string within your\r
213   reference table; typically within the comment\r
214   column (\"occur\").\r
215 \r
216 - Find all occurences of a particular reference number within all\r
217   of your org-files (\"multi-occur\").\r
218 \r
219 - Go to the first heading, that contains a given reference\r
220   number (\"heading\").\r
221 \r
222 - Enter the reference table and position the cursor at the\r
223   top (\"enter\").\r
224 \r
225 - Leave the reference table and restore cursor position and\r
226   window configuration, back to the state before entering\r
227   it (\"leave\").\r
228 \r
229 The most straightforward way to select between these operations\r
230 is to supply a negative or a a double prefix argument:\r
231 \r
232 `C-- \\[org-refer-by-number]' or `\\[universal-argument] \\[universal-argument] \\[org-refer-by-number]'\r
233 \r
234 You will then be prompted to type a single letter (\"a\", \"s\",\r
235 \"o\", \"m\", \"h\", \"e\" or \"l\") to invoke the respective\r
236 operation from the list above.\r
237 \r
238 \r
239 Some of the operations above can be invoked with less keystrokes. In that\r
240 case the precise operation invoked depends on two things:\r
241 \r
242 - The kind of a prefix argument (or absence of such)\r
243 \r
244 - The location of point; either outside the reference table or\r
245   within\r
246 \r
247 \r
248 The following cases explain, which of the seven\r
249 operations (\"add\", \"search\", \"occur\", \"multi-occur\",\r
250 \"heading\", \"enter\" and \"leave\") is actually invoked\r
251 dependng on the conditions above:\r
252 \r
253 \r
254   If no prefix argument is given (`\\[org-refer-by-number]') and\r
255   point is withib the reference table, the operation \"leave\"\r
256   will be invoked. If point is within reference table, a\r
257   \"search\" will be done in most cases; if, however, there is an\r
258   active region, the operation \"add\" is performed.\r
259 \r
260   If a numeric prefix argument is given (e.g. `153 \\[org-refer-by-number]'): \r
261   The function does a \"search\" for this reference number, \r
262   if point is outside and a \"multi-occur\", if point is within\r
263   regardless of position of point.\r
264 \r
265   If a single prefix argument is given (e.g. `\\[universal-argument] \\[org-refer-by-number]')\r
266   and point outside the reference table: \"add\" a new reference. \r
267   If point within: Do a \"multi-occur\" for the given reference.\r
268 \r
269 \r
270 In any case the function `org-refer-by-number' will give a short\r
271 message to explain, what operation has actually been invoked.\r
272 \r
273 \r
274 Before you can successfully use `org-refer-by-number' finally,\r
275 you need to read the documentation of `org-refer-by-number-id'\r
276 and complete the necessary setup decribed there.\r
277 \r
278 \r
279 "\r
280 \r
281   (interactive "P")\r
282 \r
283   (let (within-node        ; True, if we are within node with reference table\r
284                            ; (false otherwise, even if we are in the\r
285                            ; right buffer)\r
286         result-is-visible  ; True, if node or occur is visible in any window\r
287         search-from-prefix ; search string from prefix-argument\r
288         search-from-table  ; search string from first column of table\r
289         search-from-cursor ; search string from text below cursro\r
290         search-from-user   ; search string from user input\r
291         below-cursor       ; word below cursor\r
292         active-region      ; active region (if any)\r
293         search             ; final string to search for\r
294         guarded-search     ; with guard against additional digits\r
295         what               ; What are we supposed to do ? Will be stored in\r
296                            ; org-refer-by-number-last-action\r
297         what-adjusted      ; True, if we had to adjust what\r
298         what-explicit      ; True, if what has been specified explicitly\r
299         parts              ; Parts of a typical reference number (which\r
300                            ; need not be a plain number); these are:\r
301         head               ; Any header before number (e.g. "R")\r
302         last-number        ; Last number used in reference table (e.g. "153")\r
303         tail               ; Tail after number (e.g. "}" or "")\r
304         columns            ; Number of columns in reference table\r
305         kill-new-text      ; Text that will be appended to kill ring\r
306         message-text       ; Text that will be issued as an explanation,\r
307                            ; what we have done\r
308         node-marker        ; Initial point within buffer with reference table\r
309         )\r
310         \r
311     ;; Find out, if we are within reference table or not\r
312     (setq within-node (string= (org-id-get) org-refer-by-number-id))\r
313     ;; Find out, if point in any window is within node with reference table\r
314     (mapc (lambda (x) (save-excursion \r
315                         (set-buffer (window-buffer x))\r
316                         (when (or \r
317                                (string= (org-id-get) org-refer-by-number-id)\r
318                                (eq (window-buffer x) \r
319                                    org-refer-by-number-occur-buffer))\r
320                           (setq result-is-visible t))))\r
321           (window-list))\r
322 \r
323     ;; Get the content of the active region or the word under cursor; do\r
324     ;; this before examinig reference table\r
325      (if (and transient-mark-mode\r
326               mark-active)\r
327         (setq active-region (buffer-substring (region-beginning) (region-end))))\r
328      (setq below-cursor (thing-at-point 'symbol))\r
329 \r
330     ;; Find out, what we are supposed to do\r
331     (cond ((equal arg nil)\r
332            (setq what (if result-is-visible 'leave \r
333                         (if active-region 'add 'search))))\r
334           ((equal arg '(4))\r
335            (setq what (if within-node 'multi-occur 'add)))\r
336           ((numberp arg)\r
337            (setq what (if within-node 'multi-occur 'search)))\r
338           (t ; C-- or C-u C-u\r
339            (let (key)\r
340              (while \r
341                  (progn \r
342                    (setq key (read-char-exclusive \r
343                               "Please choose: e=enter l=leave s=search a=add o=occur m=multi-occur h=heading"))\r
344                    (not \r
345                     (setq what (case key\r
346                                  (?l 'leave) (?e 'enter) (?a 'add) \r
347                                  (?s 'search) (?o 'occur) (?m 'multi-occur) (?h 'heading)))))\r
348                (message "Invalid key '%c'" key)\r
349                (sleep-for 1))\r
350            (setq what-explicit t))))\r
351 \r
352     ;; Get decoration and number of last row from reference table\r
353     (let ((m (org-id-find org-refer-by-number-id 'marker)))\r
354       (unless m\r
355         (org-refer-by-number-report-setup-error \r
356          (format "Cannot find node with id \"%s\"" org-refer-by-number-id)))\r
357       (with-current-buffer (marker-buffer m)\r
358         (setq node-marker (point-marker))\r
359         (setq node-buffer (marker-buffer node-marker))\r
360         (goto-char m)\r
361         (setq parts (org-refer-by-number-trim-table nil t))\r
362         (goto-char node-marker))\r
363       (move-marker m nil)\r
364       (setq head (nth 0 parts))\r
365       (setq last-number (nth 1 parts))\r
366       (setq tail (nth 2 parts))\r
367       (setq columns (nth 3 parts)))\r
368     \r
369 \r
370     ;; These actions need a search string:\r
371     (when (memq what '(search occur multi-occur heading))\r
372 \r
373       ;; Search string can come from several sources:\r
374       ;; From explicit numerical prefix\r
375       (if (numberp arg) \r
376           (setq search-from-prefix (format "%s%d%s" head arg tail)))\r
377       ;; From first column of table\r
378       (when within-node\r
379         (save-excursion (setq search-from-table (org-table-get-field 1)))\r
380         (if (string= search-from-table "") (setq search-from-table nil)))      \r
381       ;; From string below cursor\r
382       (when (and (not within-node)\r
383                  below-cursor\r
384                  (string-match (concat "\\(" (regexp-quote head) "[0-9]+" (regexp-quote tail) "\\)") \r
385                                below-cursor))\r
386         (setq search-from-cursor (match-string 1 below-cursor)))\r
387       \r
388       ;; Depending on requested action, get search from one of the sources above\r
389       (cond ((eq what 'search)\r
390              (setq search (or search-from-prefix search-from-cursor)))\r
391             ((or (eq what 'multi-occur) (eq what 'heading))\r
392              (setq search (or search-from-table search-from-cursor)))\r
393             ((eq what 'occur)\r
394              (setq search active-region)))\r
395 \r
396 \r
397       ;; If we still do not have a search string, ask user explicitly\r
398       (unless search\r
399         (setq search (read-from-minibuffer\r
400                       (cond ((memq what '(search multi-occur heading))\r
401                              "Reference number to search for: ")\r
402                             ((eq what 'occur)\r
403                              "Text to search for: "))))\r
404         (if (string-match "^\\s *[0-9]*\\s *$" search)\r
405             (unless (string= search "")\r
406               (setq search (format "%s%s%s" head (org-trim search) tail)))))\r
407       \r
408       ;; Clean up search string\r
409       (if (string= search "") (setq search nil))\r
410       (if search (setq search (org-trim search)))\r
411 \r
412       (setq guarded-search \r
413             (concat (regexp-quote search)\r
414                     ;; if there is no tail in reference number, we\r
415                     ;; have to guard agains trailing digits\r
416                     (if (string= tail "") "\\($\\|[^0-9]\\)" "")))\r
417 \r
418     \r
419       ;; Correct requested action, if nothing to search\r
420       (when (and (not search)\r
421                (memq what '(search occur multi-occur heading)))\r
422           (setq what 'enter)\r
423           (setq what-adjusted t))\r
424            \r
425       ;; Check for invalid combinations of arguments; try to be helpful\r
426       (if (string-match (concat (regexp-quote head) "[0-9]+" (regexp-quote tail) )\r
427                         search)\r
428           (if (eq what 'occur) \r
429               (error "Can do 'occur' only for text, try 'search', 'multi-occur' or 'heading' for a number"))\r
430         (if (memq what '(search multi-occur heading))\r
431             (error "Can do '%s' only for a number, try 'occur' to search for text" what))))\r
432     \r
433     ;; Move into table, if outside ...\r
434     (when (memq what '(enter add search occur multi-occur))\r
435       ;; Save current window configuration\r
436       (when (or (not result-is-visible)\r
437                 (not org-refer-by-number-windowconfig))\r
438         (setq org-refer-by-number-windowconfig (current-window-configuration))\r
439         (setq org-refer-by-number-marker node-marker))\r
440       \r
441       ;; Switch to reference table; this needs to duplicate some code from\r
442       ;; org-id-goto, because point should be moved, if what equals 'enter\r
443       (let ((m (org-id-find org-refer-by-number-id 'marker)))\r
444         (org-pop-to-buffer-same-window (marker-buffer m))\r
445         ;; After changing buffer we might be in table or not, so check again\r
446         (setq within-node (string= (org-id-get) org-refer-by-number-id))\r
447         ;; Be careful with position within table, if we should just enter it\r
448         (unless within-node (goto-char m))\r
449         (move-marker m nil)\r
450         (show-subtree)\r
451         (org-show-context)))\r
452 \r
453 \r
454     ;; Actually do, what is requested\r
455     (cond\r
456      ((eq what 'multi-occur) \r
457       \r
458       ;; Position cursor on number to search for\r
459       (org-refer-by-number-trim-table t)\r
460       (let (found (initial (point)))\r
461         (forward-line)\r
462         (while (and (not found)\r
463                     (forward-line -1)\r
464                     (org-at-table-p))\r
465           (save-excursion \r
466             (setq found (string= search \r
467                                  (org-trim (org-table-get-field 1))))))\r
468         (if found \r
469             (org-table-goto-column 1)\r
470           (goto-char initial)))\r
471 \r
472       ;; Construct list of all org-buffers\r
473       (let (buff org-buffers)\r
474         (dolist (buff (buffer-list))\r
475           (set-buffer buff)\r
476           (if (string= major-mode "org-mode")\r
477               (setq org-buffers (cons buff org-buffers))))\r
478        \r
479         ;; Do multi-occur\r
480         (multi-occur org-buffers guarded-search)\r
481         (if (get-buffer "*Occur*")\r
482             (progn \r
483               (setq message-text (format "multi-occur for '%s'" search))\r
484               (setq org-refer-by-number-occur-buffer (get-buffer "*Occur*")))\r
485           (setq message-text (format "Did not find '%s'" search)))))\r
486 \r
487 \r
488      ((eq what 'heading)\r
489       (message (format "Scanning headlines for '%s' ..." search))\r
490       (let (buffer point)\r
491         (if (catch 'found\r
492               (progn\r
493                 (org-map-entries \r
494                  (lambda () \r
495                    (when (looking-at (concat ".*\\b" guarded-search))\r
496                      (setq buffer (current-buffer))\r
497                      (setq point (point))\r
498                      (throw 'found t)))          \r
499                  nil 'agenda)\r
500                 nil))\r
501             (progn\r
502               (setq message-text (format "Found '%s'" search))\r
503               (org-pop-to-buffer-same-window buffer)\r
504               (goto-char point)\r
505               (org-reveal))\r
506           (setq message-text (format "Did not find '%s'" search)))))\r
507 \r
508 \r
509      ((eq what 'leave)\r
510 \r
511       (when result-is-visible\r
512 \r
513         ;; if we are within the occur-buffer, switch over to get current line\r
514         (if (and (string= (buffer-name) "*Occur*")\r
515                  (eq org-refer-by-number-last-action 'occur))\r
516             (occur-mode-goto-occurrence))\r
517         \r
518         (if (org-at-table-p)\r
519             (let ((column (org-table-current-column)))\r
520               ;; Copy different things depending on the last action\r
521               (if (and (eq org-refer-by-number-last-action 'search)\r
522                        (= column 1))\r
523                   ;; It does not help to copy the first field, because\r
524                   ;; thats what we just searched for, so take last one\r
525                   (setq column columns))\r
526               (if (or (memq org-refer-by-number-last-action '(add occur))\r
527                       (< column 1))\r
528                   (setq column 1))\r
529               \r
530               ;; Add to kill ring\r
531               (if (memq org-refer-by-number-last-action '(add enter search occur))\r
532                   ;; Got left to first nonempty column\r
533                   (while (progn \r
534                            (save-excursion \r
535                              (setq kill-new-text \r
536                                    (org-trim (org-table-get-field column))))\r
537                            (and (> column 0)\r
538                                 (string= kill-new-text "")))\r
539                     (setq column (- column 1))))))\r
540         \r
541         ;; Clean up table before leaving\r
542         (with-current-buffer node-buffer \r
543           (org-refer-by-number-trim-table t)\r
544           (let ((buffer-modified (buffer-modified-p)))\r
545             (org-table-align)\r
546             (set-buffer-modified-p buffer-modified))))\r
547 \r
548       ;; Restore position within buffer with reference table\r
549       (if org-refer-by-number-windowconfig \r
550           (progn  \r
551             (with-current-buffer node-buffer\r
552               (goto-char org-refer-by-number-marker)\r
553               (set-marker org-refer-by-number-marker nil))\r
554             ;; Restore initial window configuration\r
555             (set-window-configuration org-refer-by-number-windowconfig)\r
556             (setq org-refer-by-number-windowconfig nil)\r
557             (recenter)\r
558             (setq message-text "Back"))\r
559         ;; We did not have a window-configuration to restore, so we cannot\r
560         ;; pretend we have retturned back\r
561         (setq message-text "Cannot leave; nowhere to go to")))\r
562 \r
563         \r
564      ((eq what 'search)\r
565       ;; Go upward in table within first column\r
566       (org-refer-by-number-trim-table t)\r
567       (let (found (initial (point)))\r
568         (forward-line)\r
569         (while (and (not found)\r
570                     (forward-line -1)\r
571                     (org-at-table-p))\r
572           (save-excursion \r
573             (setq found \r
574                   (string= search \r
575                            (org-trim (org-table-get-field 1))))))\r
576         (if found\r
577             (progn\r
578               (setq message-text (format "Found '%s'" search))\r
579               (org-table-goto-column 1)\r
580               (if (looking-back " ") (backward-char)))\r
581           (setq message-text (format "Did not find '%s'" search))\r
582           (goto-char initial)\r
583           (forward-line)\r
584           (setq what 'missed))))\r
585 \r
586 \r
587      ((eq what 'occur)\r
588       ;; search for string: occur\r
589       (org-narrow-to-subtree)\r
590       (occur search)\r
591       (widen)\r
592       (if (get-buffer "*Occur*")\r
593           (progn\r
594             (put 'org-refer-by-number 'occur-buffer (current-buffer))\r
595             (other-window 1)\r
596             (toggle-truncate-lines 1)\r
597             (forward-line 1)\r
598             (occur-mode-display-occurrence)\r
599             (setq message-text\r
600                   (format  "Occur for '%s'" search)))\r
601         (setq message-text\r
602               (format "Did not find any matches for '%s'" search))))\r
603 \r
604         \r
605      ((eq what 'add)\r
606       ;; Nothing to search for, add a new row\r
607       (org-refer-by-number-trim-table t)\r
608       (let ((new (format "%s%d%s" head (1+ last-number) tail)))\r
609         (org-table-insert-row 1)\r
610         (insert new)\r
611         (org-table-goto-column 2)\r
612         (org-insert-time-stamp nil nil t)\r
613         (org-table-goto-column 3)\r
614         (org-table-align)\r
615         (if active-region (setq kill-new-text active-region))\r
616         (setq message-text (format "Adding a new row '%s'" new))))\r
617      \r
618      \r
619      ((eq what 'enter)\r
620       ;; Already there, not much to do left\r
621       (show-subtree)\r
622       (recenter)\r
623       (if what-adjusted\r
624           (setq message-text "Nothing to search for; at reference table")\r
625         (setq message-text "At reference table")))\r
626      \r
627 \r
628      (t (error "This is a bug: Unmatched condition '%s'" what)))\r
629 \r
630     \r
631     ;; Remember what we have done for next time\r
632     (setq org-refer-by-number-last-action what)\r
633     \r
634     ;; Tell, what we have done and what can be yanked\r
635     (if kill-new-text (setq kill-new-text \r
636                             (substring-no-properties kill-new-text)))\r
637     (if (string= kill-new-text "") (setq kill-new-text nil))\r
638     (let ((m (concat \r
639               message-text\r
640               (if (and message-text kill-new-text) \r
641                   " and r" \r
642                 (if kill-new-text "R" ""))\r
643               (if kill-new-text (format "eady to yank '%s'" kill-new-text) "")\r
644               )))\r
645       (unless (string= m "") (message m)))\r
646     (if kill-new-text (kill-new kill-new-text))))\r
647 \r
648 \r
649 (defun org-refer-by-number-trim-table (&optional goto-end get-parts)\r
650   "Trim reference table, only used internally"\r
651   \r
652   (let ((initial (point-marker))\r
653         field\r
654         parts\r
655         columns)\r
656 \r
657     ;; Go to heading of node\r
658     (while (not (org-at-heading-p)) (forward-line -1))\r
659     (forward-line 1)\r
660     ;; Go to table within node, but make sure we do not get into another node\r
661     (while (and (not (org-at-heading-p))\r
662                 (not (org-at-table-p))\r
663                 (not (eq (point) (point-max)))) \r
664       (forward-line 1))\r
665     ;; Check, if there really is a table\r
666     (unless (org-at-table-p)\r
667       (org-refer-by-number-report-setup-error \r
668        "Cannot find reference table within reference node" t))\r
669 \r
670     ;; Go beyond end of table\r
671     (while (org-at-table-p) (forward-line 1))\r
672 \r
673     ;; Kill all empty rows at bottom\r
674     (while (progn\r
675              (forward-line -1)\r
676              (org-table-goto-column 1)\r
677              (string= "" (org-trim (org-table-get-field 1)))\r
678              )\r
679       (org-table-kill-row)\r
680       )\r
681 \r
682     (when get-parts\r
683 \r
684       ;; Find out number of columns\r
685       (org-table-goto-column 100)\r
686       (setq columns (- (org-table-current-column) 1))\r
687 \r
688       ;; Check for right number of columns\r
689       (unless (>= columns 2)\r
690         (org-refer-by-number-report-setup-error \r
691          "Table within reference node has less than two columns" t)\r
692         )\r
693 \r
694       ;; Retrieve any decorations around the number within first field of\r
695       ;; the last row\r
696       (setq field (org-trim (org-table-get-field 1)))\r
697       (or (string-match "^\\([^0-9]*\\)\\([0-9]+\\)\\([^0-9]*\\)$" field)\r
698           (org-refer-by-number-report-setup-error \r
699            (format "Last field of reference table '%s' does not contain a number" field) t)\r
700           )\r
701 \r
702       ;; These are the decorations used within the last row of the\r
703       ;; reference table\r
704       (setq parts (list (match-string 1 field) \r
705                         (string-to-number (match-string 2 field)) \r
706                         (match-string 3 field) \r
707                         columns)))\r
708 \r
709     (unless goto-end (goto-char (marker-position initial)))\r
710     (set-marker initial nil)\r
711     \r
712     parts))\r
713 \r
714 \r
715 (defun org-refer-by-number-report-setup-error (text &optional switch-to-node)\r
716   "Report error, which might be related with incomplete setup; offer help"\r
717 \r
718   (when switch-to-node \r
719     (org-id-goto org-refer-by-number-id)\r
720     (delete-other-windows)\r
721     )\r
722   \r
723   (if (y-or-n-p (concat text \r
724                         "; "\r
725                         "the correct setup is explained in the documentation of 'org-refer-by-number-id'. " \r
726                         "Do you want to read it ? "))\r
727       (describe-variable 'org-refer-by-number-id)\r
728     )\r
729   (error "")\r
730   (setq org-refer-by-number-windowconfig nil)\r
731   (setq org-refer-by-number-last-action 'leave))\r
732 \r
733 \r
734 (provide 'org-refer-by-number)\r
735 \r
736 ;; Local Variables:\r
737 ;; fill-column: 75\r
738 ;; comment-column: 50\r
739 ;; End:\r
740 \r
741 ;;; org-refer-by-number.el ends here\r
742 \r