Small updates to org-index.el
[org-mode/org-kjn.git] / contrib / lisp / org-index.el
blob695d2445ab8ac5b12b856e299d0a664f2cfca249
1 ;;; org-index.el --- A personal index for org and beyond
3 ;; Copyright (C) 2011-2014 Free Software Foundation, Inc.
5 ;; Author: Marc Ihm <org-index@2484.de>
6 ;; Keywords: outlines, hypermedia, matching
7 ;; Requires: org
8 ;; Version: 2.4.0
10 ;; This file is not part of GNU Emacs.
12 ;;; License:
14 ;; This program is free software; you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 3, or (at your option)
17 ;; any later version.
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
27 ;;; Commentary:
29 ;; Purpose:
31 ;; Mark and find your favorite org-locations and other points of interest
32 ;; easily; create and update a lookup table of references and links. When
33 ;; searching, frequently used entries appear at the the top and entering
34 ;; some keywords narrows down to matching entries only, so that the
35 ;; right one can be spotted easily.
37 ;; References are essentially small numbers (e.g. "R237" or "-455-"),
38 ;; which are created by this package; they are well suited to be used
39 ;; outside org. Links are normal org-mode links.
42 ;; Setup:
44 ;; - Add these lines to your .emacs:
46 ;; ;; use the real path from your org-installation
47 ;; (add-to-list 'load-path "~/path/to/orgdir/contrib/lisp" t)
48 ;; (require 'org-index)
50 ;; - Restart your emacs to make these lines effective
52 ;; - Invoke `org-index', which will assist in creating your index
53 ;; table. The variable org-index-id will be persisted within your
54 ;; customization file (typically .emacs).
57 ;; Further reading:
59 ;; See the documentation of `org-index', which can also be read
60 ;; by invoking `org-index' and and choosing the help-command.
62 ;; For more documentation and working examples, see:
64 ;; http://orgmode.org/worg/org-contrib/org-index.html
67 ;; Updates:
69 ;; The latest tested version of this file can always be found at:
71 ;; http://orgmode.org/w/org-mode.git?p=org-mode.git;a=blob;f=contrib/lisp/org-index.el;hb=HEAD
73 ;;; Change Log:
75 ;; [2014-01-02 Th] Version 2.4.0:
76 ;; - New command "put" to store a nodes reference in a property
77 ;; - New functions org-index-new-line and org-index-get-line
78 ;; offer access to org-index from other lisp programs
79 ;; - New flags p,x1,x2 and x3
80 ;; - Major Code refactoring
81 ;; - Regression tests with ert
82 ;; - Lots of bugfixes
84 ;; [2013-10-04 Fr] Version 2.3.2:
85 ;; - Bugfix: index-table created by assistant is found after
86 ;; restart of emacs instead of invoking assistent again
88 ;; [2013-07-20 Sa] Version 2.3.0:
89 ;; - Renamed from "org-favtable" to "org-index"
90 ;; - Added an assistent to set up the index table
91 ;; - occur is now incremental, searching as you type
92 ;; - simplified the documentation and help-system
93 ;; - Saving keystrokes, as "+g237" is now valid input
94 ;; - Many bugfixes
96 ;; [2013-02-28 Th] Version 2.2.0:
97 ;; - Allowed shortcuts like "h237" for command "head" with argument "237"
98 ;; - Integrated with org-mark-ring-goto
100 ;; [2013-01-25 Fr] Version 2.1.0:
101 ;; - Added full support for links
102 ;; - New commands "missing" and "statistics"
103 ;; - Renamed the package from "org-reftable" to "org-favtable"
104 ;; - Additional columns are required (e.g. "link"). Error messages will
105 ;; guide you
107 ;; [2012-12-07 Fr] Version 2.0.0:
108 ;; - The format of the table of favorites has changed ! You need to bring
109 ;; your existing table into the new format by hand (which however is
110 ;; easy and explained below)
111 ;; - Reference table can be sorted after usage count or date of last access
112 ;; - Ask user explicitly, which command to invoke
113 ;; - Renamed the package from "org-refer-by-number" to "org-reftable"
115 ;; [2012-09-22 Sa] Version 1.5.0:
116 ;; - New command "sort" to sort a buffer or region by reference number
117 ;; - New commands "highlight" and "unhighlight" to mark references
119 ;; [2012-07-13 Fr] Version 1.4.0:
120 ;; - New command "head" to find a headline with a reference number
122 ;; [2012-04-28 Sa] Version 1.3.0:
123 ;; - New commands occur and multi-occur
124 ;; - All commands can now be invoked explicitly
125 ;; - New documentation
126 ;; - Many bugfixes
128 ;; [2011-12-10 Sa] Version 1.2.0:
129 ;; - Fixed a bug, which lead to a loss of newly created reference numbers
130 ;; - Introduced single and double prefix arguments
131 ;; - Started this Change Log
133 ;;; Code:
135 (require 'org-table)
136 (require 'cl)
138 (defcustom org-index-id nil
139 "Id of the Org-mode node, which contains the index table."
140 :group 'org
141 :group 'org-index)
143 ;; Variables to hold the configuration of the index table
144 (defvar org-index--maxref) ; Maximum number from reference table (e.g. "153")
145 (defvar org-index--head) ; Any header before number (e.g. "R")
146 (defvar org-index--tail) ; Tail after number (e.g. "}" or "")
147 (defvar org-index--numcols) ; Number of columns in index table
148 (defvar org-index--ref-regex) ; Regular expression to match a reference
149 (defvar org-index--has-reuse nil) ; True, if table contains a line for reuse
150 (defvar org-index--ref-format) ; Format, that can print a reference
151 (defvar org-index--columns nil) ; Columns of index-table
152 (defvar org-index--special-columns nil) ; Columns with flags
153 (defvar org-index--buffer) ; Buffer of index table
154 (defvar org-index--point) ; Position at start of headline of index table
155 (defvar org-index--below-hline) ; Position of first cell in first line below hline
156 (defvar org-index--headings) ; Headlines of index-table as a string
158 ;; Variables to hold context and state
159 (defvar org-index--last-action nil) ; Last action performed by org-index
160 (defvar org-index--text-to-yank nil) ; Text, that can be yanked after call (mostly a reference)
161 (defvar org-index--last-ref) ; Last reference created or visited
162 (defvar org-index--point-before nil) ; Point in buffer with index table
163 (defvar org-index--silent nil) ; t, if user should not be queried
164 (defvar org-index--preferred-command) ; command, that is presented first
165 (defvar org-index--active-region) ; Active region, initially. I.e. what has been marked
166 (defvar org-index--below-cursor) ; Word below cursor
167 (defvar org-index--within-node) ; True, if we are within node of the index table
168 (defvar org-index--active-window-index nil) ; Active window with index table (if any)
170 (setq org-index--commands '(occur head ref link leave put enter goto help + reorder fill sort update highlight unhighlight missing statistics)) ; list of commands available
172 (defun org-index (&optional ARG)
173 "Mark and find your favorite things and org-locations easily:
174 Create and update a lookup table of references and links. Often
175 used entries bubble to the top; entering some keywords narrows
176 down to matching entries only, so that the right one can be
177 spotted easily.
179 References are essentially small numbers (e.g. \"R237\" or \"-455-\"),
180 which are created by this package; they are well suited to be used
181 outside of org. Links are normal org-mode links.
183 This is version 2.4.0 of org-index.
185 The function `org-index' operates on a dedicated table, the index
186 table, which lives within its own Org-mode node. The table and
187 its node will be created, when you first invoke org-index.
189 Each line in the index table contains:
191 - A reference (e.g. \"R237\")
193 - An optional link to another location in org
195 - A number, counting, how often each reference has been
196 used. This number is updated automatically and the table can
197 be sorted after it, so that most frequently used references
198 appear at the top of the table and can be spotted easily.
200 - The creation date of the line
202 - Date and time of last access. This column can alternatively be
203 used to sort the table.
205 - A column for your own comments
207 The index table is found through the id of the containing
208 node; this id is stored within the variable `org-index-id'.
211 The function `org-index' is the only interactive function of this
212 package and its main entry point; it offers several commands to
213 create, find and look up line within the index table.
215 Commands known:
217 occur: Incremental search, that shows matching lines from the
218 index table, updated after every keystroke. You may enter a
219 list of words seperated by space or comma (\",\"), to select
220 lines that contain all of the given words.
222 If you supply a number (e.g. \"237\"): Apply emacs standard
223 multi-occur operation on all org-mode buffers to search for
224 this specific reference.
226 You may also read the note at the end of this help on saving
227 the keystroke RET with this frequent default command.
229 head: If invoked outside the index table, ask for a reference
230 number and search for an entry, which either has this
231 reference contained in its heading or within its property
232 org-index-ref. If invoked from within the index table dont
233 ask; rather use the reference or link from the current line.
235 ref: Create a new reference, copy any previously selected text.
236 If already within index table, fill in ref-column.
238 link: Create a new line in index table with a link to the
239 current node. Do not populate the ref column; this can later
240 be populated by calling the \"fill\" command from within the
241 index table.
243 leave: Leave the index table. If the last command has been
244 \"ref\", the new reference is copied and ready to yank. This
245 \"org-mark-ring-goto\" and can be called several times in
246 succession. If you invoke org-index with a prefix argument,
247 this command \"leave\" is executed without further questions.
249 put: Put the reference, that was created last, as the value of
250 property org-index-ref into the current node. That way it can
251 be found by a later call to \"head\".
253 enter: Just enter the node with the index table.
255 goto: Enter index table and go to a specific reference.
257 help: Show this text.
259 +: Show all commands including the less frequently used ones
260 given below. If \"+\" is followd by enough letters of such a
261 command (e.g. \"+fi\"), then this command (e.g. \"fill\") is
262 invoked directly.
264 reorder: Temporarily reorder the index table, e.g. by count,
265 reference or last access.
267 fill: If either ref or link is missing in current line of index
268 table, fill in the missing value.
270 sort: Sort a set of lines (either from the active region or the
271 whole buffer) by references found in each line.
273 update: For the given reference, update the line in the
274 index table, i.e. increment its count.
276 highlight: Highlight references in active region or buffer.
278 unhighlight: Remove those highlights.
280 missing : Search for missing reference numbers (which do not
281 appear in the reference table). If requested, add additional
282 lines for them, so that the command \"ref\" is able to reuse
283 them.
285 statistics : Show some statistics (e.g. minimum and maximum
286 reference) about index table.
290 Two ways to save keystrokes:
292 When prompting for a command, org-index puts the most likely
293 one (e.g. \"occur\" or \"ref\") in front of the list, so that
294 you may just type RET.
296 If this first command in the list of commands needs additional
297 input (like e.g. \"occur\"), you may supply this input right
298 away, although you are still beeing prompted for the command. So,
299 to do an occur for the string \"foo\", you can just enter \"foo\"
300 RET, without even typing \"occur\".
303 Another way to save keystrokes applies if you want to choose a
304 command, that requrires a reference number and would normally
305 prompt for it: In that case you may just enter enough characters
306 from your command, so that it appears first in the list of
307 matches; then immediately enter the number of the reference you
308 are searching for. So the input \"h237\" would execute the
309 command \"head\" for reference \"237\".
313 (interactive "P")
315 (let ((org-index--silent nil) ; t, if user can be asked
316 link-id ; link of starting node, if required
317 what ; what to do
318 search ; what to search for
319 guarded-search ; with guard against additional digits
320 search-ref ; search, if search is a reference
321 search-link ; search, if search is a link
322 what-adjusted ; true, if we had to adjust what
323 what-input ; Input on what question (need not necessary be "what")
324 reorder-once ; column to use for single time sorting
325 kill-new-text ; text that will be appended to kill ring
326 message-text ; text that will be issued as an explanation
327 initial-ref-or-link ; initial position in index table
332 ;; Initialize and parse
335 ;; creates index table, if necessary
336 (org-index--verify-id)
338 ;; store context information
339 (org-index--retrieve-context)
341 ;; Get configuration of index table
342 (org-index--parse-table)
346 ;; Find out, what we are supposed to do
349 (if ARG
350 (if (equal ARG '(4))
351 (setq what 'leave)
352 (if (and (symbolp ARG)
353 (memq ARG org-index--commands))
354 (setq what ARG)
355 (error "Unknown command '%s' passed as argument, valid choices are a prefix argument or any of these symbols: %s"
356 ARG (mapconcat 'symbol-name org-index--commands ","))))
358 (let ((r (org-index--read-what what))) ; query user if not from argument
359 (setq what (nth 0 r))
360 (setq what-input (nth 1 r))
361 (setq reorder-once (nth 2 r))))
365 ;; Get search, if required
368 ;; These actions need a search string:
369 (when (memq what '(goto occur head update))
370 ;; Maybe we've got a search string from the arguments
371 (setq search (org-index--get-or-read-search search what what-input))
373 (when search
374 (when (string-match org-index--ref-regex search)
375 (setq search-ref search)
376 (setq guarded-search (org-index--make-guarded-search search)))
377 (when (string-match "^[a-fA-F0-9]\\{8\\}-[a-fA-F0-9]\\{4\\}-[a-fA-F0-9]\\{4\\}-[a-fA-F0-9]\\{4\\}-[a-fA-F0-9]\\{12\\}$" search)
378 (setq search-link search))))
382 ;; Do some sanity checking before really starting
385 ;; Correct requested action, if nothing to search
386 (when (and (not search)
387 (memq what '(search head)))
388 (setq what 'enter)
389 (setq what-adjusted t))
391 ;; For a proper reference as input, we do multi-occur
392 (if (and (eq what 'occur) search-ref)
393 (setq what 'multi-occur))
395 ;; Check for invalid combinations of arguments; try to be helpful
396 (when (and (memq what '(head goto))
397 (not search-ref)
398 (not search-link))
399 (error "Can do '%s' only for a reference or link (not '%s'), try 'occur' to search for text" what search))
403 ;; Sort and enter table
406 ;; Get link if required before moving in
407 (if (eq what 'link)
408 (let ((org-id-link-to-org-use-id t))
409 (setq link-id (org-id-get-create))))
411 ;; Save initial ref or link for later return
412 (if (and org-index--within-node
413 (org-at-table-p))
414 (setq initial-ref-or-link
415 (or (org-index--get-field :ref)
416 (org-index--get-field :link))))
418 ;; These commands enter index table only temporarily
419 (when (memq what '(occur multi-occur statistics))
421 (set-buffer org-index--buffer)
422 (goto-char org-index--point)
424 ;; Sort and align
425 (org-index--sort reorder-once)
426 (org-index--align))
428 ;; These commands will leave user in index table after they are finished
429 (when (memq what '(enter ref link goto missing))
431 ;; Support orgmode-standard of going back (buffer and position)
432 (org-mark-ring-push)
434 (org-pop-to-buffer-same-window org-index--buffer)
435 (goto-char org-index--point)
436 (show-subtree)
437 (org-show-context)
439 ;; Sort and align
440 (org-index--sort reorder-once)
441 (org-index--align))
443 ;; Return to initial position
444 (when initial-ref-or-link
445 (while (and (org-at-table-p)
446 (not (or
447 (string= initial-ref-or-link (org-index--get-field :ref))
448 (string= initial-ref-or-link (org-index--get-field :link)))))
449 (forward-line))
450 ;; did not find ref, go back to top
451 (if (not (org-at-table-p)) (goto-char org-index--point)))
455 ;; Actually do, what is requested
458 (cond
461 ((eq what 'help)
463 ;; bring up help-buffer for this function
464 (describe-function 'org-index))
467 ((eq what 'multi-occur)
469 ;; Position point in index buffer on reference to search for
470 (goto-char org-index--below-hline)
471 (let (found (initial (point)))
472 (while (and (not found)
473 (forward-line)
474 (org-at-table-p))
475 (save-excursion
476 (setq found (string= search
477 (org-index--get-field :ref)))))
478 (if found
479 (org-index--update-line nil)
480 (goto-char initial)))
482 ;; Construct list of all org-buffers
483 (let (buff org-buffers)
484 (dolist (buff (buffer-list))
485 (set-buffer buff)
486 (if (string= major-mode "org-mode")
487 (setq org-buffers (cons buff org-buffers))))
489 ;; Do multi-occur
490 (multi-occur org-buffers guarded-search)
492 ;; Present results
493 (if (get-buffer "*Occur*")
494 (progn
495 (setq message-text (format "multi-occur for '%s'" search))
496 (other-window 1)
497 (toggle-truncate-lines 1))
498 (setq message-text (format "Did not find '%s'" search)))))
501 ((eq what 'head)
503 (let (link)
504 (if (and org-index--within-node
505 (org-at-table-p))
506 (setq link (org-index--get-field :link))))
508 (setq message-text (org-index--do-head search-ref search-link)))
511 ((eq what 'leave)
513 (setq kill-new-text org-index--text-to-yank)
514 (setq org-index--text-to-yank nil)
516 ;; If "leave" has been called two times in succession, make
517 ;; org-mark-ring-goto believe it has been called two times too
518 (if (eq org-index--last-action 'leave)
519 (let ((this-command nil) (last-command nil))
520 (org-mark-ring-goto 1))
521 (org-mark-ring-goto))
523 ;; Return to saved position in index buffer
524 (when org-index--point-before
525 ;; buffer displayed in window need to set point there first
526 (if (eq (window-buffer org-index--active-window-index)
527 org-index--buffer)
528 (set-window-point org-index--active-window-index org-index--point-before))
529 ;; set position in buffer in any case and second
530 (with-current-buffer org-index--buffer
531 (goto-char org-index--point-before)))
532 (setq org-index--point-before nil))
535 ((eq what 'goto)
537 ;; Go downward in table to requested reference
538 (let (found (initial (point)))
539 (goto-char org-index--below-hline)
540 (while (and (not found)
541 (forward-line)
542 (org-at-table-p))
543 (save-excursion
544 (setq found
545 (string= search
546 (org-index--get-field
547 (if search-link :link :ref))))))
548 (if found
549 (progn
550 (setq message-text (format "Found '%s'" search))
551 (org-index--update-line nil)
552 (org-table-goto-column (org-index--column-num :ref))
553 (if (looking-back " ") (backward-char))
554 ;; remember string to copy
555 (setq org-index--text-to-yank
556 (org-trim (org-table-get-field (org-index--column-num :copy)))))
557 (setq message-text (format "Did not find '%s'" search))
558 (goto-char initial)
559 (forward-line)
560 (setq what 'missed))))
563 ((eq what 'occur)
565 (org-index--do-occur what-input))
568 ((memq what '(ref link))
570 (let (new)
572 ;; add a new row (or reuse existing one)
573 (setq new (org-index--do-new-line (eq what 'ref)))
575 ;; fill special columns with standard values
576 (when (eq what 'ref)
577 (org-table-goto-column (org-index--column-num :ref))
578 (insert new)
579 (setq org-index--last-ref new))
580 (when (eq what 'link)
581 (org-table-goto-column (org-index--column-num :link))
582 (insert link-id))
584 (org-index--align)
586 ;; goto point-field or copy-field or first empty one or first field
587 (if (org-index--special-column :point)
588 (org-table-goto-column (org-index--column-num (org-index--special-column :point)))
589 (if (org-index--special-column :copy)
590 (org-table-goto-column (org-index--column-num (org-index--special-column :copy)))
591 (unless (catch 'empty
592 (dotimes (col org-index--numcols)
593 (org-table-goto-column (+ col 1))
594 (if (string= (org-trim (org-table-get-field)) "")
595 (throw 'empty t))))
596 ;; none found, goto first
597 (org-table-goto-column 1))))
599 (if org-index--active-region (setq kill-new-text org-index--active-region))
600 (if (eq what 'ref)
601 (setq message-text (format "Adding a new row with ref '%s'" new))
602 (setq message-text (format "Adding a new row linked to '%s'" link-id)))))
605 ((eq what 'put)
607 ;; put latest reference into property
610 (if org-index--last-ref
611 (progn
612 (org-entry-put (point) "org-index-ref" org-index--last-ref)
613 (message "Reference '%s' has been stored in property org-index-ref" org-index--last-ref))
614 (setq org-index--last-ref
615 (read-from-minibuffer "Reference to be stored in this node: "))
616 (unless org-index--last-ref
617 (message "No reference has been given."))
621 ((eq what 'enter)
623 ;; simply go into table
624 (goto-char org-index--below-hline)
625 (show-subtree)
626 (recenter)
627 (if what-adjusted
628 (setq message-text "Nothing to search for; at index table")
629 (setq message-text "At index table")))
632 ((eq what 'fill)
634 ;; check, if within index table
635 (unless (and org-index--within-node
636 (org-at-table-p))
637 (error "Not within index table"))
639 ;; applies to missing refs and missing links alike
640 (let ((ref (org-index--get-field :ref))
641 (link (org-index--get-field :link)))
643 (if (and (not ref)
644 (not link))
645 ;; have already checked this during parse, check here anyway
646 (error "Columns ref and link are both empty in this line"))
648 ;; fill in new ref
649 (if (not ref)
650 (progn
651 (setq kill-new-text (format "%s%d%s" org-index--head (1+ org-index--maxref) org-index--tail))
652 (org-index--get-field :ref kill-new-text)
653 ;; remember for org-mark-ring-goto
654 (setq org-index--text-to-yank kill-new-text)
655 (org-id-goto link)
656 (setq message-text "Filled field of index table with new reference"))
658 ;; fill in new link
659 (if (not link)
660 (progn
661 (setq guarded-search (org-index--make-guarded-search ref))
662 (message (format "Scanning headlines for '%s' ..." ref))
663 (let ((search (concat ".*" guarded-search))
664 link)
665 (if (catch 'found
666 (org-map-entries
667 (lambda ()
668 (when (looking-at search)
669 (setq link (org-id-get-create))
670 (throw 'found t)))
671 nil 'agenda)
672 nil)
674 (progn
675 (org-index--get-field :link link)
676 (setq message-text "Inserted link"))
678 (setq message-text (format "Did not find reference '%s'" ref)))))
680 ;; nothing is missing
681 (setq message-text "Columns ref and link are already filled; nothing to do")))))
684 ((eq what 'sort)
686 ;; sort lines according to contained reference
687 (let (begin end where)
688 (catch 'aborted
689 ;; either active region or whole buffer
690 (if (and transient-mark-mode
691 mark-active)
692 ;; sort only region
693 (progn
694 (setq begin (region-beginning))
695 (setq end (region-end))
696 (setq where "region"))
697 ;; sort whole buffer
698 (setq begin (point-min))
699 (setq end (point-max))
700 (setq where "whole buffer")
701 ;; make sure
702 (unless (y-or-n-p "Sort whole buffer ")
703 (setq message-text "Sort aborted")
704 (throw 'aborted nil)))
706 (save-excursion
707 (save-restriction
708 (goto-char (point-min))
709 (narrow-to-region begin end)
710 (sort-subr nil 'forward-line 'end-of-line
711 (lambda ()
712 (if (looking-at (concat ".*"
713 (org-index--make-guarded-search org-index--ref-regex 'dont-quote)))
714 (string-to-number (match-string 1))
715 0))))
716 (highlight-regexp org-index--ref-regex 'isearch)
717 (setq message-text (format "Sorted %s from character %d to %d, %d lines"
718 where begin end
719 (count-lines begin end)))))))
722 ((eq what 'update)
724 ;; simply update line in index table
725 (save-excursion
726 (let ((ref-or-link (if search-link "link" "reference")))
727 (beginning-of-line)
728 (if (org-index--update-line search)
729 (setq message-text (format "Updated %s '%s'" ref-or-link search))
730 (setq message-text (format "Did not find %s '%s'" ref-or-link search))))))
733 ((memq what '(highlight unhighlight))
735 (let ((where "buffer"))
736 (save-excursion
737 (save-restriction
738 (when (and transient-mark-mode
739 mark-active)
740 (narrow-to-region (region-beginning) (region-end))
741 (setq where "region"))
743 (if (eq what 'highlight)
744 (progn
745 (highlight-regexp org-index--ref-regex 'isearch)
746 (setq message-text (format "Highlighted references in %s" where)))
747 (unhighlight-regexp org-index--ref-regex)
748 (setq message-text (format "Removed highlights for references in %s" where)))))))
751 ((memq what '(missing statistics))
753 (setq message-text (org-index--do-statistics what)))
756 (t (error "This is a bug: unmatched case '%s'" what)))
759 ;; remember what we have done for next time
760 (setq org-index--last-action what)
762 ;; tell, what we have done and what can be yanked
763 (if kill-new-text (setq kill-new-text
764 (substring-no-properties kill-new-text)))
765 (if (string= kill-new-text "") (setq kill-new-text nil))
766 (let ((m (concat
767 message-text
768 (if (and message-text kill-new-text)
769 " and r"
770 (if kill-new-text "R" ""))
771 (if kill-new-text (format "eady to yank '%s'" kill-new-text) ""))))
772 (unless (string= m "") (message m)))
773 (if kill-new-text (kill-new kill-new-text))))
776 (defun org-index-new-line (&rest keys-values)
777 "Create a new line within the index table, returning its reference.
779 The function takes a varying number of arguments pairs; each pair
780 is a symbol for an existing column heading followed by its value.
781 their values.
783 Example:
785 (org-index-new-line :ref t :x1 \"foo\" :link \"7f480c3e\")
787 Passing \":ref t\" will make the function create a new reference within the new line.
791 (let ((org-index--silent t))
793 (save-excursion
794 (org-index--retrieve-context)
795 (with-current-buffer org-index--buffer
796 (goto-char org-index--point)
797 (org-index--parse-table)
799 ;; check arguments early
800 (let ((kvs keys-values)
801 k v)
802 (while kvs
803 (setq k (car kvs))
804 (setq v (cadr kvs))
805 (if (eq k :ref)
806 (unless (memq v '(t nil))
807 (error "Argument :ref accepts only t or nil"))
808 (if (or (not (symbolp k))
809 (symbolp v))
810 (error "Arguments must be alternation of key and value")))
811 (unless (> (org-index--column-num k) 0)
812 (error "Unknown column or column not defined in table: '%s'" (symbol-name k)))
813 (setq kvs (cddr kvs))))
815 (if (and (not (plist-get keys-values :ref))
816 (not (stringp (plist-get keys-values :link))))
817 (error "Need a link when not creating a ref"))
819 (let (new)
820 ;; create new line
821 (setq new (org-index--do-new-line (plist-get keys-values :ref)))
822 (plist-put keys-values :ref (or new ""))
824 ;; fill columns
825 (let ((kvs keys-values)
826 k v n)
827 (while kvs
828 (setq k (car kvs))
829 (setq v (cadr kvs))
830 (setq n (org-index--column-num k))
831 (org-table-goto-column n)
832 (insert v)
833 (setq kvs (cddr kvs))))
835 (org-index--sort)
836 new)))))
839 (defun org-index-get-line (what value)
840 "Retrieve an existing line within the index table by ref or
841 link and return its contents as a property list.
843 The function `plist-get' may be used to retrieve specific values.
845 Example:
847 (plist-get (org-index-get-line \"12\") :count)
849 retrieves the value of the count-column for reference 12.
852 (interactive)
853 (let ((org-index--silent t)
854 found)
856 ;; check arguments
857 (unless (memq what '(:ref :link))
858 (error "Argument what can only be :ref or :link"))
860 (save-excursion
861 (org-index--retrieve-context)
862 (with-current-buffer org-index--buffer
863 (goto-char org-index--point)
864 (org-index--parse-table)
866 (goto-char org-index--below-hline)
867 (while (and (not found)
868 (org-at-table-p))
869 (when (string= (org-index--get-field what)
870 value)
871 (mapc (lambda (x)
872 (if (and (numberp (cdr x))
873 (> (cdr x) 0))
874 (setq found (cons (car x) (cons (or (org-index--get-field (car x)) "") found)))
875 )) (reverse org-index--columns)))
876 (forward-line))
877 found))))
880 (defun org-index--read-what (what)
881 "Find out, what we are supposed to do"
883 (let (commands ; currently active set of selectable commands
884 trailing-digits ; any digits, that are are appended to what-input
885 reorder-once ; Column to use for single time sorting
886 what-input) ; Input on what question (need not necessary be "what")
888 ;; Set preferred action, that will be the default choice
889 (setq org-index--preferred-command
890 (if org-index--within-node
891 (if (memq org-index--last-action '(ref link))
892 'leave
893 'goto)
894 (if org-index--active-region
895 'ref
896 (if (and org-index--below-cursor (string-match org-index--ref-regex org-index--below-cursor))
897 'occur
898 nil))))
900 ;; Ask user, what to do
901 (if what
902 (setq what-input (symbol-name what))
903 ;; subset of most common commands for initial selection, ie. up to first plus
904 (setq commands (copy-list org-index--commands))
905 (let ((c commands))
906 (while (and c (not (eq (car c) '+)))
907 (setq c (cdr c)))
908 (setcdr c nil))
910 (while (let (completions starts-with-plus is-only-plus)
912 (setq what-input
913 (org-completing-read
914 "Please choose: "
915 (mapcar 'symbol-name
916 ;; Construct unique list of commands with
917 ;; preferred one at front
918 (delq nil (delete-dups
919 (append
920 (list org-index--preferred-command)
921 (copy-list commands)))))
922 nil nil))
924 ;; if input ends in digits, save them away and do completions on head of input
925 ;; this allows input like "h224" to be accepted
926 (when (string-match "^\\([^0-9]+\\)\\([0-9]+\\)\\s *$" what-input)
927 ;; remember digits
928 (setq trailing-digits (string-to-number (match-string 2 what-input)))
929 ;; and use non-digits-part to find match
930 (setq what-input (match-string 1 what-input)))
932 ;; if input starts with "+", any command (not only some) may follow
933 ;; this allows input like "+sort" to be accepted
934 (when (and (> (length what-input) 0)
935 (string= (substring what-input 0 1) "+"))
936 ;; make all commands available for selection
937 (setq commands (copy-list org-index--commands))
938 (setq what-input (substring what-input 1))
939 (setq starts-with-plus (> (length what-input) 0))
940 (setq is-only-plus (not starts-with-plus)))
942 ;; get list of possible completions for what-input; i.e.
943 ;; all commands, that start with what-input
944 (setq completions (delq nil (mapcar
945 (lambda (x)
946 (let ((where (search what-input (symbol-name x))))
947 (if (and where
948 (= where 0))
950 nil))) commands)))
952 ;; if input starts with "+" and not just "+"
953 (when starts-with-plus
954 ;; use first completion, if unambigously
955 (if (= (length completions) 1)
956 (setq what-input (symbol-name (car completions)))
957 (if completions
958 (error "Input \"+%s\" matches multiple commands: %s"
959 what-input
960 (mapconcat 'symbol-name completions ", "))
961 (error "Input \"+%s\" matches no commands" what-input))))
963 ;; if input ends in digits, use first completion, even if ambigous
964 ;; this allows input like "h224" to be accepted
965 (when (and trailing-digits completions)
966 ;; use first match as input, even if ambigously
967 (setq org-index--preferred-command (first completions))
968 (setq what-input (number-to-string trailing-digits)))
970 ;; convert to symbol
971 (setq what (intern what-input))
972 (if is-only-plus (setq what '+))
974 ;; user is not required to input one of the commands; if
975 ;; not, take the first one and use the original input for
976 ;; next question
977 (if (memq what commands)
978 ;; input matched one element of list, dont need original
979 ;; input any more
980 (setq what-input nil)
981 ;; what-input will be used for next question, use first
982 ;; command for what
983 (setq what (or org-index--preferred-command
984 (first commands)))
985 ;; remove any trailing dot, that user might have added to
986 ;; disambiguate his input
987 (if (and (> (length what-input) 0)
988 (equal (substring what-input -1) "."))
989 ;; but do this only, if dot was really necessary to
990 ;; disambiguate
991 (let ((shortened-what-input (substring what-input 0 -1)))
992 (unless (test-completion shortened-what-input
993 (mapcar 'symbol-name
994 commands))
995 (setq what-input shortened-what-input)))))
997 ;; ask for reorder in loop, because we have to ask for
998 ;; what right again
999 (if (eq what 'reorder)
1000 (setq reorder-once
1001 (intern
1002 (concat ":"
1003 (org-icompleting-read
1004 "Please choose column to reorder index table once: "
1005 (mapcar 'symbol-name '(ref count accessed))
1006 nil t)))))
1008 ;; maybe ask initial question again
1009 (memq what '(reorder +)))))
1010 (list what what-input reorder-once)))
1013 (defun org-index--get-or-read-search (search what what-input)
1014 "Get search string, maybe read from user"
1016 (let (search-from-table
1017 search-from-cursor)
1019 (unless search
1020 ;; Search string can come from several sources:
1021 ;; From link or ref columns of table
1022 (when org-index--within-node
1023 (setq search-from-table (or (org-index--get-field :link)
1024 (org-index--get-field :ref))))
1026 ;; From string below cursor
1027 (when (and (not org-index--within-node)
1028 org-index--below-cursor
1029 (string-match (concat "\\(" org-index--ref-regex "\\)")
1030 org-index--below-cursor))
1031 (setq search-from-cursor (match-string 1 org-index--below-cursor)))
1033 ;; Depending on requested action, get search from one of the sources above
1034 (cond ((eq what 'goto)
1035 (setq search (or what-input search-from-cursor)))
1036 ((memq what '(head occur))
1037 (setq search (or what-input search-from-table search-from-cursor)))))
1040 ;; If we still do not have a search string, ask user explicitly
1041 (unless search
1043 (if org-index--silent (error "Need to specify search, if silence is required"))
1045 (unless (eq what 'occur)
1047 (if what-input
1048 (setq search what-input)
1049 (setq search (read-from-minibuffer
1050 (cond ((eq what 'head)
1051 "Text or reference number to search for: ")
1052 ((eq what 'goto)
1053 "Reference number to search for, or enter \".\" for id of current node: ")
1054 ((eq what 'update)
1055 "Reference number to update: ")))))
1057 (if (string-match "^\\s *[0-9]+\\s *$" search)
1058 (setq search (format "%s%s%s" org-index--head search org-index--tail)))))
1061 ;; Clean up and examine search string
1062 (when search
1063 (setq search (org-trim search))
1064 (if (string= search "") (setq search nil))
1065 (when search
1066 (if (string-match "^[0-9]+$" search)
1067 (setq search (concat org-index--head search org-index--tail)))))
1069 ;; Check for special case
1070 (when (and (memq what '(head goto))
1071 (string= search "."))
1072 (setq search (org-id-get)))
1074 search))
1077 (defun org-index--verify-id ()
1079 ;; Check id
1080 (unless org-index-id
1081 (setq org-index-id (org-index--create-new-index
1083 (format "No index table has been created yet." org-index-id))))
1085 ;; Find node
1086 (let (marker)
1087 (setq marker (org-id-find org-index-id 'marker))
1088 (unless marker (setq org-index-id (org-index--create-new-index
1090 (format "Cannot find node with id \"%s\"" org-index-id))))
1091 ; Try again with new node
1092 (setq marker (org-id-find org-index-id 'marker))
1093 (unless marker (error "Could not create node"))
1094 (setq org-index--buffer (marker-buffer marker)
1095 org-index--point (marker-position marker))
1096 (move-marker marker nil)))
1099 (defun org-index--retrieve-context ()
1101 ;; Get the content of the active region or the word under cursor
1102 (setq org-index--active-region
1103 (if (and transient-mark-mode mark-active)
1104 (buffer-substring (region-beginning) (region-end))
1105 nil))
1106 (setq org-index--below-cursor (thing-at-point 'symbol))
1108 ;; Find out, if we are within favable or not
1109 (setq org-index--within-node (string= (org-id-get) org-index-id))
1111 ;; Check and remember, if active window contains buffer with index table
1112 (if (eq (window-buffer) org-index--buffer)
1113 (setq org-index--active-window-index (selected-window)))
1115 ;; get current position in index-buffer
1116 (with-current-buffer org-index--buffer
1117 (unless (string= (org-id-get) org-index-id)
1118 (unless org-index--point-before
1119 (setq org-index--point-before (point))))))
1122 (defun org-index--parse-table ()
1124 (let (ref-field
1125 link-field
1126 initial-point
1127 end-of-heading)
1129 (with-current-buffer org-index--buffer
1131 (setq org-index--maxref 0)
1132 (setq initial-point (point))
1133 (org-index--go-below-hline)
1134 (setq org-index--below-hline (point))
1135 (beginning-of-line)
1136 (setq end-of-heading (point))
1137 (while (org-at-table-p) (forward-line -1))
1138 (forward-line)
1139 (setq org-index--headings (buffer-substring (point) end-of-heading))
1140 (goto-char org-index--below-hline)
1143 ;; count columns
1144 (org-table-goto-column 100)
1145 (setq org-index--numcols (- (org-table-current-column) 1))
1147 ;; get contents of columns
1148 (forward-line -2)
1149 (unless (org-at-table-p)
1150 (org-index--create-new-index
1152 "Index table starts with a hline"))
1154 ;; check for optional line consisting solely of width specifications
1155 (beginning-of-line)
1156 (if (looking-at "\\s *|\\(\\(\\s *|\\)\\|\\(\\s *<[0-9]+>\\s *|\\)\\)+\\s *$")
1157 (forward-line -1))
1158 (org-table-goto-column 1)
1160 (org-index--parse-headings)
1162 ;; Go beyond end of table
1163 (while (org-at-table-p) (forward-line 1))
1165 ;; Retrieve any decorations around the number within the first nonempty ref-field
1166 (goto-char org-index--below-hline)
1167 (while (and (org-at-table-p)
1168 (not (setq ref-field (org-index--get-field :ref))))
1169 (forward-line))
1171 ;; Some Checking
1172 (unless ref-field
1173 (org-index--create-new-index
1175 "Reference column is empty"))
1177 (unless (string-match "^\\([^0-9]*\\)\\([0-9]+\\)\\([^0-9]*\\)$" ref-field)
1178 (org-index--create-new-index
1179 nil
1180 (format "First reference in index table ('%s') does not contain a number" ref-field)))
1183 ;; These are the decorations used within the first ref of index
1184 (setq org-index--head (match-string 1 ref-field))
1185 (setq org-index--tail (match-string 3 ref-field))
1186 (setq org-index--ref-regex (concat (regexp-quote org-index--head)
1187 "\\([0-9]+\\)"
1188 (regexp-quote org-index--tail)))
1189 (setq org-index--ref-format (concat org-index--head "%d" org-index--tail))
1192 ;; Go through table to find maximum number and do some checking
1193 (let ((ref 0))
1195 (while (org-at-table-p)
1197 (setq ref-field (org-index--get-field :ref))
1198 (setq link-field (org-index--get-field :link))
1200 (when (and (not ref-field)
1201 (not link-field))
1202 (org-pop-to-buffer-same-window org-index--buffer)
1203 (org-reveal)
1204 (error "Columns ref and link are both empty in this line"))
1206 (if ref-field
1207 (if (string-match org-index--ref-regex ref-field)
1208 ;; grab number
1209 (setq ref (string-to-number (match-string 1 ref-field)))
1210 (org-pop-to-buffer-same-window org-index--buffer)
1211 (org-reveal)
1212 (error "Column ref does not contain a number")))
1214 ;; check, if higher ref
1215 (if (> ref org-index--maxref) (setq org-index--maxref ref))
1217 ;; check if ref is ment for reuse
1218 (if (string= (org-index--get-field :count) ":reuse:")
1219 (setq org-index--has-reuse t))
1221 (forward-line 1)))
1223 ;; go back to initial position
1224 (goto-char initial-point))))
1227 (defun org-index--sort (&optional sort-column)
1229 (unless sort-column (setq sort-column (org-index--special-column :sort)))
1231 (let (top
1232 bottom
1233 ref-field
1234 count-field
1235 count-special)
1237 (unless buffer-read-only
1239 ;; get boundaries of table
1240 (goto-char org-index--below-hline)
1241 (forward-line 0)
1242 (setq top (point))
1243 (while (org-at-table-p) (forward-line))
1245 ;; Kill all empty rows at bottom
1246 (while (progn
1247 (forward-line -1)
1248 (org-table-goto-column 1)
1249 (and
1250 (not (org-index--get-field :ref))
1251 (not (org-index--get-field :link))))
1252 (org-table-kill-row))
1253 (forward-line 1)
1254 (setq bottom (point))
1256 (save-restriction
1257 (narrow-to-region top bottom)
1258 (goto-char top)
1259 (sort-subr t
1260 'forward-line
1261 'end-of-line
1262 (lambda ()
1263 (let (ref
1264 (ref-field (or (org-index--get-field :ref) ""))
1265 (count-field (or (org-index--get-field :count) ""))
1266 (count-special 0))
1268 ;; get reference with leading zeroes, so it can be
1269 ;; sorted as text
1270 (string-match org-index--ref-regex ref-field)
1271 (setq ref (format
1272 "%06d"
1273 (string-to-number
1274 (or (match-string 1 ref-field)
1275 "0"))))
1277 ;; find out, if special token in count-column
1278 (setq count-special (format "%d"
1279 (- 2
1280 (length (member count-field '(":missing:" ":reuse:"))))))
1282 ;; Construct different sort-keys according to
1283 ;; requested sort column; prepend count-special to
1284 ;; sort special entries at bottom of table, append ref
1285 ;; as a secondary sort key
1286 (cond
1288 ((eq sort-column :count)
1289 (concat count-special
1290 (format
1291 "%08d"
1292 (string-to-number (or (org-index--get-field :count)
1293 "")))
1294 ref))
1296 ((eq sort-column :accessed)
1297 (concat count-special
1298 (org-index--get-field :accessed)
1299 " "
1300 ref))
1302 ((eq sort-column :ref)
1303 (concat count-special
1304 ref))
1306 (t (error "This is a bug: unmatched case '%s'" sort-column)))))
1308 nil 'string<))
1310 ;; sorting has moved point below hline
1311 (org-index--go-below-hline)
1312 (setq org-index--below-hline (point)))))
1315 (defun org-index--go-below-hline ()
1317 (goto-char org-index--point)
1318 ;; go to heading of node
1319 (while (not (org-at-heading-p)) (forward-line -1))
1320 (forward-line 1)
1321 ;; go to table within node, but make sure we do not get into another node
1322 (while (and (not (org-at-heading-p))
1323 (not (org-at-table-p))
1324 (not (eq (point) (point-max))))
1325 (forward-line 1))
1327 ;; check, if there really is a table
1328 (unless (org-at-table-p)
1329 (org-index--create-new-index
1331 (format "Cannot find index table within node %s" org-index-id)))
1333 ;; go to first hline
1334 (while (and (not (org-at-table-hline-p))
1335 (org-at-table-p))
1336 (forward-line 1))
1338 ;; and check
1339 (unless (org-at-table-hline-p)
1340 (org-index--create-new-index
1341 nil
1342 "Cannot find hline within index table"))
1344 (forward-line 1)
1345 (org-table-goto-column 1))
1348 (defun org-index--align ()
1349 (unless buffer-read-only (org-table-align))
1350 (org-index--go-below-hline)
1351 (setq org-index--below-hline (point)))
1354 (defun org-index--parse-headings ()
1356 ;; Associate names of special columns with column-numbers
1357 (setq org-index--columns (copy-tree '((:ref . 0) (:link . 0) (:first . 0) (:last . 0)
1358 (:count . 0) (:x1 . 0) (:x2 . 0) (:x3 . 0))))
1360 ;; Associate names of special columns with names of columns
1361 (setq org-index--special-columns (copy-tree '((:sort . nil) (:copy . nil) (:point . nil))))
1363 ;; For each column
1364 (dotimes (col org-index--numcols)
1365 (let* (field-flags ;; raw heading, consisting of file name and maybe
1366 ;; flags (seperated by ";")
1367 field ;; field name only
1368 field-symbol ;; and as a symbol
1369 flags ;; flags from field-flags
1370 found)
1372 ;; parse field-flags into field and flags
1373 (setq field-flags (org-trim (org-table-get-field (+ col 1))))
1374 (if (string-match "^\\([^;]*\\);\\([a-z]+\\)$" field-flags)
1375 (progn
1376 (setq field (downcase (or (match-string 1 field-flags) "")))
1377 ;; get flags as list of characters
1378 (setq flags (mapcar 'string-to-char
1379 (split-string
1380 (downcase (match-string 2 field-flags))
1381 "" t))))
1382 ;; no flags
1383 (setq field field-flags))
1385 (unless (string= field "") (setq field-symbol (intern (concat ":" (downcase field)))))
1386 ;; aliases for backward compatability
1387 (if (eq field-symbol :last-accessed) (setq field-symbol :last))
1388 (if (eq field-symbol :created) (setq field-symbol :first))
1390 (if (and field-symbol
1391 (not (assoc field-symbol org-index--columns)))
1392 (error "Column %s is not a valid heading" (symbol-name field-symbol)))
1394 ;; Check, that no flags appear twice
1395 (mapc (lambda (x)
1396 (when (memq (car x) flags)
1397 (if (cdr (assoc (cdr x) org-index--columns))
1398 (org-index--create-new-index
1400 (format "More than one heading is marked with flag '%c'" (car x))))))
1401 '((?s . sort)
1402 (?c . copy)))
1404 ;; Process flags
1405 (if (memq ?s flags)
1406 (setcdr (assoc :sort org-index--special-columns) (or field-symbol (+ col 1))))
1407 (if (memq ?c flags)
1408 (setcdr (assoc :copy org-index--special-columns) (or field-symbol (+ col 1))))
1409 (if (memq ?p flags)
1410 (setcdr (assoc :point org-index--special-columns) (or field-symbol (+ col 1))))
1412 ;; Store columns in alist
1413 (setq found (assoc field-symbol org-index--columns))
1414 (when found
1415 (if (> (cdr found) 0)
1416 (org-index--create-new-index
1418 (format "'%s' appears two times as column heading" (downcase field))))
1419 (setcdr found (+ col 1)))))
1421 ;; check if all necessary informations have been specified
1422 (mapc (lambda (col)
1423 (unless (> (cdr (assoc col org-index--columns)) 0)
1424 (org-index--create-new-index
1426 (format "column '%s' has not been set" col))))
1427 (list :ref :link :count :first :last))
1429 ;; use count as a default sort-column
1430 (unless (cdr (assoc :sort org-index--special-columns))
1431 (setcdr (assoc :sort org-index--special-columns) :count)))
1434 (defun org-index--create-new-index (create-new-index reason)
1435 "Create a new empty index table with detailed explanation."
1436 (let (prompt buffer-name title firstref id)
1438 ;; cannot proceed without querying user
1439 (if org-index--silent (error "No valid index: %s" reason))
1441 (setq prompt
1442 (if create-new-index
1443 (concat "There is this problem with the existing index table:\n\n " reason "\n\nThis assistant will guide you to create a new one.\n\nDo you want to proceed ?")
1444 (concat "The existing index table contains this error:\n\n " reason "\n\nYou need to correct this error manually before trying again. However, this assistant will help you to create an new initial index table with detailed comments, so that you may fix the errors in your existing table more easily.\n\nDo you want to proceed ?")))
1445 (unless (y-or-n-p prompt)
1446 (error "Cannot proceed without a valid index table: %s" reason))
1448 (setq buffer-name (org-completing-read "Please choose the buffer, where the new node for the index table should be created; the new node will be inserted at its end.\n\nBuffer: " (mapcar 'buffer-name (org-buffer-list)) nil nil))
1450 (setq title (read-from-minibuffer "Please enter the title of the index node: "))
1452 (while (progn
1453 (setq firstref (read-from-minibuffer "Please enter your first reference-number. This is a number preceeded by some non-digit chars and optionally followed by some more non-digit chars, e.g. 'R1', '-1-' or '#1#' (and your initial number does not need to be '1'). The format of your reference-numbers only needs to make sense for yourself, so that you can spot it easily in your texts or write it on a piece of paper; it should however not already appear to frequently within your existing notes, to avoid too many false hits when searching.\n\nPlease choose: "))
1454 (let (desc)
1455 (unless (equal '(95 119) (sort (delete-dups (mapcar (lambda (x) (char-syntax x)) (concat "-1" firstref))) '<))
1456 (setq desc "Contains other characters than those allowed in symbols"))
1457 (unless (string-match "^[^0-9]+[0-9]+[^0-9]*$" firstref)
1458 ;; firstref not okay, report details
1459 (setq desc
1460 (cond ((string= firstref "") "is empty")
1461 ((not (string-match "^[^0-9]+" firstref)) "starts with a digit")
1462 ((not (string-match "^[^0-9]+[0-9]+" firstref)) "does not contain a number")
1463 ((not (string-match "^[^0-9]+[0-9]+[^0-9]*$" firstref)) "contains more than one sequence of digits")
1466 (if desc
1467 (progn
1468 (read-from-minibuffer (format "Your input '%s' does not meet the requirements because it %s. Please hit RET and try again" firstref desc))
1470 nil))))
1472 (with-current-buffer buffer-name
1473 (goto-char (point-max))
1474 (insert (format "\n\n* %s %s\n" firstref title))
1475 (insert "\n\n Below you find your initial index table, which will grow over time.\n"
1476 " Following that your may read its detailed explanation, which will help you,\n"
1477 " to adjust org-index to your needs. This however is optional reading and not\n"
1478 " required to start using org-index.\n")
1480 (setq id (org-id-get-create))
1481 (insert (format "
1483 | | | | | | comment |
1484 | ref | link | first | count;s | last | ;c |
1485 | | <4> | | | | |
1486 |-----+------+-------+---------+------+---------|
1487 | %s | %s | %s | | | %s |
1490 firstref
1492 (with-temp-buffer (org-insert-time-stamp nil nil t))
1493 "This node"))
1496 (insert "
1498 Detailed explanation:
1501 The index table above has three lines of headings above the first
1502 hline:
1504 - The first one is ignored by org-index, and you can use it to
1505 give meaningful names to columns. In the table above only one
1506 column has a name (\"comment\"). This line is optional.
1508 - The second line is the most important one, because it
1509 contains the configuration information for org-index; please
1510 read further below for its format.
1512 - The third line is again optional; it may only specify the
1513 widths of the individual columns (e.g. <4>).
1515 The columns get their meaning by the second line of headings;
1516 specifically by one of the keywords (e.g. \"ref\") or a flag
1517 seperated by a semicolon (e.g. \";s\").
1521 The keywords and flags are:
1524 - ref: This contains the reference, which consists of a decorated
1525 number, which is incremented for each new line. References are
1526 meant to be used in org-mode headlines or outside of org,
1527 e.g. within folder names.
1529 - link: org-mode link pointing to the matching location within org.
1531 - first: When has this line been first accessed (i.e. created) ?
1533 - count: How many times has this line been accessed ? The
1534 trailing flag \"s\" makes the table beeing sorted after this
1535 column this column, so that often used entries appear at the
1536 top of the table.
1538 - last: When has this line been accessed last ?
1540 - The last column above has no keyword, only the flag \"c\",
1541 which makes its content beeing copied under certain
1542 conditions. It is typically used for comments.
1544 The sequence of columns does not matter. You may reorder them any
1545 way you like. Columns are found by their name, which appears in
1546 the second line of headings.
1548 You can add further columns or even remove the last column. All
1549 other columns are required.
1552 Finally: This node needs not be a top level node; its name is
1553 completely at you choice; it is found through its ID only.
1558 (while (not (org-at-table-p)) (forward-line -1))
1559 (unless buffer-read-only (org-table-align))
1560 (while (not (org-at-heading-p)) (forward-line -1))
1562 ;; present results to user
1563 (if create-new-index
1564 (progn
1565 ;; Only show the new index
1566 (org-pop-to-buffer-same-window buffer-name)
1567 (delete-other-windows)
1568 (org-id-goto id)
1569 (org-show-context)
1570 (show-subtree)
1571 (recenter 1)
1572 (if (y-or-n-p "This is your new index table; Do you want to save its id to make it permanent ? ")
1573 (progn
1574 (customize-save-variable 'org-index-id id)
1575 (message "Saved org-index-id '%s' to %s" org-index-id custom-file))
1576 (let (sq)
1577 (setq sq (format "(setq org-index-id \"%s\")" org-index-id))
1578 (kill-new sq)
1579 (message "Did not make the id of the new index permamanent; you may want to put\n\n %s\n\ninto your own initialization; it is copied already, just yank it." sq))
1580 id))
1581 ;; we had an error with the existing index table, so present old
1582 ;; and new one together
1583 ;; show existing index
1584 (org-pop-to-buffer-same-window org-index--buffer)
1585 (goto-char org-index--point)
1586 (org-show-context)
1587 (show-subtree)
1588 (recenter 1)
1589 (delete-other-windows)
1590 ;; show new index
1591 (select-window (split-window-vertically))
1592 (org-pop-to-buffer-same-window buffer-name)
1593 (org-id-goto id)
1594 (org-show-context)
1595 (show-subtree)
1596 (recenter 1)
1597 (error "Please compare your existing index (upper window) and a temporary new one (lower window) to correct the previous error (\"%s\"); the explanations following the new index table should help." reason)))))
1600 (defun org-index--update-line (ref-or-link)
1602 (let (initial
1603 found
1604 count-field)
1606 (with-current-buffer org-index--buffer
1607 (unless buffer-read-only
1609 ;; search reference or link, if given (or assume, that we are already positioned right)
1610 (when ref-or-link
1611 (setq initial (point))
1612 (goto-char org-index--below-hline)
1613 (while (and (org-at-table-p)
1614 (not (or (string= ref-or-link (org-index--get-field :ref))
1615 (string= ref-or-link (org-index--get-field :link)))))
1616 (forward-line)))
1618 (if (not (org-at-table-p))
1619 (error "Did not find reference or link '%s'" ref-or-link)
1620 (setq count-field (org-index--get-field :count))
1622 ;; update count field only if number or empty; leave :missing: and :reuse: as is
1623 (if (or (not count-field)
1624 (string-match "^[0-9]+$" count-field))
1625 (org-index--get-field :count
1626 (number-to-string
1627 (+ 1 (string-to-number (or count-field "0"))))))
1629 ;; update timestamp
1630 (org-table-goto-column (org-index--column-num :last))
1631 (org-table-blank-field)
1632 (org-insert-time-stamp nil t t)
1634 (setq found t))
1636 (if initial (goto-char initial))
1638 found))))
1641 (defun org-index--get-field (key &optional value)
1642 (let (field)
1643 (setq field (org-trim (org-table-get-field (cdr (assoc key org-index--columns)) value)))
1644 (if (string= field "") (setq field nil))
1646 (org-no-properties field)))
1649 (defun org-index--column-num (key)
1650 (if (numberp key)
1652 (cdr (assoc key org-index--columns))))
1655 (defun org-index--special-column (key)
1656 (cdr (assoc key org-index--special-columns)))
1659 (defun org-index--make-guarded-search (ref &optional dont-quote)
1660 (concat "\\_<" (if dont-quote ref (regexp-quote ref)) "\\_>"))
1663 (defun org-index--do-statistics (what)
1664 (let ((total 0)
1665 missing
1666 ref-field
1670 message-text)
1673 ;; start with list of all references
1674 (setq missing (mapcar (lambda (x) (format "%s%d%s" org-index--head x org-index--tail))
1675 (number-sequence 1 org-index--maxref)))
1677 ;; go through table and remove all refs, that we see
1678 (goto-char org-index--below-hline)
1679 (while (org-at-table-p)
1681 ;; get ref-field and number
1682 (setq ref-field (org-index--get-field :ref))
1683 (if (and ref-field
1684 (string-match org-index--ref-regex ref-field))
1685 (setq ref (string-to-number (match-string 1 ref-field))))
1687 ;; remove existing refs from list
1688 (if ref-field (setq missing (delete ref-field missing)))
1690 ;; record min and max
1691 (if (or (not min) (< ref min)) (setq min ref))
1692 (if (or (not max) (> ref max)) (setq max ref))
1694 ;; count
1695 (setq total (1+ total))
1697 (forward-line))
1699 ;; insert them, if requested
1700 (forward-line -1)
1701 (if (eq what 'statistics)
1703 (setq message-text (format "Found %d references from %s to %s. %d references below highest do not appear in table. "
1704 total
1705 (format org-index--ref-format min)
1706 (format org-index--ref-format max)
1707 (length missing)))
1709 (if (y-or-n-p (format "Found %d missing references; do you wish to append them to the index table"
1710 (length missing)))
1711 (let (type)
1712 (setq type (org-icompleting-read
1713 "Insert new lines for reuse by command \"new\" or just as missing ? " '("reuse" "missing")))
1714 (mapc (lambda (x)
1715 (let (org-table-may-need-update) (org-table-insert-row t))
1716 (org-index--get-field :ref x)
1717 (org-index--get-field :count (format ":%s:" type)))
1718 missing)
1719 (org-index--align)
1721 (setq message-text (format "Inserted %d new lines for missing refernces" (length missing))))
1722 (setq message-text (format "%d missing references." (length missing)))))
1723 message-text))
1726 (defun org-index--do-head (ref link)
1728 (if ref (setq org-index--last-ref ref))
1730 (let (message-text)
1731 ;; Use link if available
1732 (if link
1733 (progn
1734 (org-index--update-line link)
1735 (org-id-goto link)
1736 (org-reveal)
1737 (if (eq (current-buffer) org-index--buffer)
1738 (setq org-index--point-before nil))
1739 (setq message-text "Followed link"))
1741 (message (format "Scanning headlines for '%s' ..." ref))
1742 (org-index--update-line ref)
1743 (let ((search (concat ".*" (org-index--make-guarded-search ref)))
1744 (org-trust-scanner-tags t)
1745 buffer point)
1746 (if (catch 'found
1747 (progn
1748 ;; loop over all headlines, stop on first match
1749 (org-map-entries
1750 (lambda ()
1751 (when (or (looking-at search)
1752 (eq ref (org-entry-get (point) "org-index-ref")))
1753 ;; If this is not an inlinetask ...
1754 (when (< (org-element-property :level (org-element-at-point))
1755 org-inlinetask-min-level)
1756 ;; ... remember location and bail out
1757 (setq buffer (current-buffer))
1758 (setq point (point))
1759 (throw 'found t))))
1760 nil 'agenda)
1761 nil))
1763 (progn
1764 (if (eq buffer org-index--buffer)
1765 (setq org-index--point-before nil))
1766 (setq message-text (format "Found '%s'" (or ref link)))
1767 (org-pop-to-buffer-same-window buffer)
1768 (goto-char point)
1769 (org-reveal))
1770 (setq message-text (format "Did not find '%s'" (or ref link))))))
1771 message-text))
1774 (defun org-index--do-occur (initial-search)
1775 (let ((occur-buffer-name "*org-index-occur*")
1776 (word "") ; last word to search for growing and shrinking on keystrokes
1777 (prompt "Search for: ")
1778 (hint "")
1779 words ; list of other words that must match too
1780 occur-buffer
1781 lines-to-show ; number of lines to show in window
1782 start-of-lines ; position, where lines begin
1783 start-of-help ; start of displayed help (if any)
1784 left-off-at ; stack of last positions in index table
1785 after-inserted ; in occur-buffer
1786 lines-visible ; in occur-buffer
1787 below-hline-bol ; below-hline and at bol
1788 exit-gracefully ; true if normal exit
1789 in-c-backspace ; true while processing C-backspace
1790 show-headings ; true, if headings should be shown
1791 fun-on-ret ; function to be executed, if return has been pressed
1792 ret from to key)
1794 ;; clear buffer
1795 (if (get-buffer "*org-index-occur*")
1796 (kill-buffer occur-buffer-name))
1797 (setq occur-buffer (get-buffer-create "*org-index-occur*"))
1799 ;; install keyboard-shortcuts within occur-buffer
1800 (with-current-buffer occur-buffer
1801 (let ((keymap (make-sparse-keymap)))
1803 (set-keymap-parent keymap org-mode-map)
1804 (setq fun-on-ret (lambda () (interactive)
1805 (let ((ref (org-index--get-field :ref))
1806 (link (org-index--get-field :link)))
1807 (message (org-index--do-head ref link)))))
1809 (define-key keymap (kbd "RET") fun-on-ret)
1810 (use-local-map keymap)))
1812 (with-current-buffer org-index--buffer
1813 (let ((initial (point)))
1814 (goto-char org-index--below-hline)
1815 (forward-line 0)
1816 (setq below-hline-bol (point))
1817 (goto-char initial)))
1819 (org-pop-to-buffer-same-window occur-buffer)
1820 (toggle-truncate-lines 1)
1822 (unwind-protect ; to reset cursor-shape even in case of errors
1823 (progn
1825 ;; fill in header
1826 (erase-buffer)
1827 (insert (concat "Incremental search, showing one window of matches. TAB toggles help.\n\n"))
1828 (setq start-of-lines (point))
1829 (setq start-of-help start-of-lines)
1830 (setq cursor-type 'hollow)
1832 ;; get window size of occur-buffer as number of lines to be searched
1833 (setq lines-to-show (+ (- (window-body-height) (line-number-at-pos)) 1))
1836 ;; fill initially
1837 (setq ret (org-index--get-matching-lines nil lines-to-show below-hline-bol))
1838 (when (car ret)
1839 (insert (cdr ret))
1840 (setq left-off-at (cons (car ret) nil))
1841 (setq after-inserted (cons (point) nil)))
1843 ;; read keys
1844 (while
1845 (progn
1846 (goto-char start-of-lines)
1847 (setq lines-visible 0)
1849 ;; use initial-search (if present) to simulate keyboard input
1850 (if (and initial-search
1851 (> (length initial-search) 0))
1852 (progn
1853 (setq key (string-to-char (substring initial-search 0 1)))
1854 (if (length initial-search)
1855 (setq initial-search (substring initial-search 1))))
1856 (if in-c-backspace
1857 (setq key 'backspace)
1858 (let ((search-text (mapconcat 'identity (reverse (cons word words)) ",")))
1859 (setq key (read-key
1860 (format "%s%s%s%s"
1861 prompt
1862 search-text
1863 (if (string= search-text "") "" " ")
1864 hint))))
1865 (setq hint "")
1866 (setq exit-gracefully (member key (list 'up 'down 'left 'right 'RET ?\C-g ?\C-m)))))
1868 (not exit-gracefully))
1870 (cond
1872 ((eq key 'C-backspace)
1874 (setq in-c-backspace t))
1876 ((member key (list 'backspace 'deletechar ?\C-?)) ; erase last char
1878 (if (= (length word) 0)
1880 ;; nothing more to delete from current word; try next
1881 (progn
1882 (setq word (car words))
1883 (setq words (cdr words))
1884 (setq in-c-backspace nil))
1886 ;; unhighlight longer match
1887 (let ((case-fold-search t))
1888 (unhighlight-regexp (regexp-quote word)))
1890 ;; some chars are left; shorten word
1891 (setq word (substring word 0 -1))
1892 (when (= (length word) 0) ; when nothing left, use next word from list
1893 (setq word (car words))
1894 (setq words (cdr words))
1895 (setq in-c-backspace nil))
1897 ;; remove everything, that has been added for char just deleted
1898 (when (cdr after-inserted)
1899 (setq after-inserted (cdr after-inserted))
1900 (goto-char (car after-inserted))
1901 (delete-region (point) (point-max)))
1903 ;; back up last position in index table too
1904 (when (cdr left-off-at)
1905 (setq left-off-at (cdr left-off-at)))
1907 ;; go through buffer and check, if any invisible line should now be shown
1908 (goto-char start-of-lines)
1909 (while (< (point) (point-max))
1910 (if (outline-invisible-p)
1911 (progn
1912 (setq from (line-beginning-position)
1913 to (line-beginning-position 2))
1915 ;; check for matches
1916 (when (org-index--test-words (cons word words) (buffer-substring from to))
1917 (when (<= lines-visible lines-to-show) ; show, if more lines required
1918 (outline-flag-region from to nil)
1919 (incf lines-visible))))
1921 ;; already visible, just count
1922 (incf lines-visible))
1924 (forward-line 1))
1926 ;; highlight shorter word
1927 (unless (= (length word) 0)
1928 (let ((case-fold-search t))
1929 (highlight-regexp (regexp-quote word) 'isearch)))))
1932 ((member key (list ?\s ?,)) ; space or comma: enter an additional search word
1934 ;; push current word and clear, no need to change display
1935 (setq words (cons word words))
1936 (setq word ""))
1939 ((member key (list 'TAB ?\C-i)) ; tab: toggle display of headlines
1940 (setq show-headings (not show-headings))
1941 (goto-char start-of-lines)
1942 (if show-headings
1943 (progn
1944 (forward-line -1)
1945 (kill-line)
1946 (setq start-of-help (point))
1947 (if (display-graphic-p)
1948 (insert "<backspace> and <c-backspace> erase, cursor keys move. RET finds node, C-RET all matches.\nComma seperates words, any other key adds to search word.\n\n")
1949 (insert "BACKSPACE to erase, to finish. Then cursor keys and RET to find node.\n\n"))
1950 (insert org-index--headings))
1951 (delete-region start-of-help start-of-lines)
1952 (insert "\n"))
1953 (setq start-of-lines (point)))
1956 ((and (integerp key)
1957 (aref printable-chars key)) ; any printable char: add to current search word
1959 ;; unhighlight short word
1960 (unless (= (length word) 0)
1961 (let ((case-fold-search t))
1962 (unhighlight-regexp (regexp-quote word))))
1964 ;; add to word
1965 (setq word (concat word (char-to-string key)))
1967 ;; hide lines, that do not match longer word any more
1968 (while (< (point) (point-max))
1969 (unless (outline-invisible-p)
1970 (setq from (line-beginning-position)
1971 to (line-beginning-position 2))
1973 ;; check for matches
1974 (if (org-index--test-words (list word) (buffer-substring from to))
1975 (incf lines-visible) ; count as visible
1976 (outline-flag-region from to t))) ; hide
1978 (forward-line 1))
1980 ;; duplicate top of stacks; eventually overwritten below
1981 (setq left-off-at (cons (car left-off-at) left-off-at))
1982 (setq after-inserted (cons (car after-inserted) after-inserted))
1984 ;; get new lines from index table
1985 (when (< lines-visible lines-to-show)
1986 (setq ret (org-index--get-matching-lines (cons word words)
1987 (- lines-to-show lines-visible)
1988 (car left-off-at)))
1990 (when (car ret)
1991 (insert (cdr ret))
1992 (setcar left-off-at (car ret))
1993 (setcar after-inserted (point))))
1995 ;; highlight longer word
1996 (let ((case-fold-search t))
1997 (highlight-regexp (regexp-quote word) 'isearch)))
2000 (t ; non-printable chars
2001 (setq hint (format "(cannot search for key '%s', use %s to quit)"
2002 (if (symbolp key)
2004 (key-description (char-to-string key)))
2005 (substitute-command-keys "\\[keyboard-quit]"))))))
2007 ;; search is done collect and brush up results
2008 ;; remove any lines, that are still invisible
2009 (goto-char start-of-lines)
2010 (while (< (point) (point-max))
2011 (if (outline-invisible-p)
2012 (delete-region (line-beginning-position) (line-beginning-position 2))
2013 (forward-line 1)))
2015 ;; get all the rest
2016 (when (eq key 'C-return)
2017 (message "Getting all matches ...")
2018 (setq ret (org-index--get-matching-lines (cons word words) 0 (car left-off-at)))
2019 (message "done.")
2020 (insert (cdr ret))))
2022 ;; postprocessing even for non graceful exit
2023 (setq cursor-type t)
2024 ;; replace previous heading
2025 (let ((numlines (count-lines (point) start-of-lines)))
2026 (goto-char start-of-lines)
2027 (delete-region (point-min) (point))
2028 (insert (format (concat (if exit-gracefully "Search is done;" "Search aborted;")
2029 (if (eq key 'C-return)
2030 " showing all %d matches."
2031 " showing only some matches.")
2032 " Use cursor keys to move, press RET to find node.\n\n")
2033 numlines))
2034 (if show-headings (insert "\n\n" org-index--headings)))
2035 (forward-line))
2037 (setq buffer-read-only t)
2039 ;; perform action according to last char
2040 (forward-line -1)
2041 (cond
2043 ((member key (list 'RET ?\C-m))
2044 (funcall fun-on-ret))
2046 ((eq key 'up)
2047 (forward-line -1))
2049 ((eq key 'down)
2050 (forward-line 1))
2052 ((eq key 'left)
2053 (forward-char -1))
2055 ((eq key 'right)
2056 (forward-char 1)))))
2059 (defun org-index--do-new-line (create-ref)
2060 "Do the common work for org-index-new-line and org-index"
2062 (let (new)
2064 (when create-ref
2065 ;; go through table to find first entry to be reused
2066 (when org-index--has-reuse
2067 (goto-char org-index--below-hline)
2068 ;; go through table
2069 (while (and (org-at-table-p)
2070 (not new))
2071 (when (string=
2072 (org-index--get-field :count)
2073 ":reuse:")
2074 (setq new (org-index--get-field :ref))
2075 (if new (org-table-kill-row)))
2076 (forward-line)))
2078 ;; no ref to reuse; construct new reference
2079 (unless new
2080 (setq new (format "%s%d%s" org-index--head (1+ org-index--maxref) org-index--tail)))
2082 ;; remember for org-mark-ring-goto
2083 (setq org-index--text-to-yank new))
2085 ;; insert ref or link as very first row
2086 (goto-char org-index--below-hline)
2087 (org-table-insert-row)
2089 ;; insert some of the standard values
2090 (org-table-goto-column (org-index--column-num :first))
2091 (org-insert-time-stamp nil nil t)
2092 (org-table-goto-column (org-index--column-num :count))
2093 (insert "1")
2095 new))
2098 (defun org-index--get-matching-lines (words numlines start-from)
2099 (let ((numfound 0)
2101 initial line lines)
2103 (with-current-buffer org-index--buffer
2105 ;; remember initial pos and start at requested
2106 (setq initial (point))
2107 (goto-char start-from)
2109 ;; loop over buffer until we have found enough lines
2110 (while (and (or (< numfound numlines)
2111 (= numlines 0))
2112 (org-at-table-p))
2114 ;; check each word
2115 (setq line (buffer-substring (line-beginning-position) (line-beginning-position 2)))
2116 (when (org-index--test-words words line)
2117 (setq lines (concat lines line))
2118 (incf numfound))
2119 (forward-line 1)
2120 (setq pos (point)))
2122 ;; return to initial position
2123 (goto-char initial))
2125 (unless lines (setq lines ""))
2126 (cons pos lines)))
2129 (defun org-index--test-words (words line)
2130 (let ((found-all t))
2131 (setq line (downcase line))
2132 (catch 'not-found
2133 (dolist (w words)
2134 (or (search w line)
2135 (throw 'not-found nil)))
2136 t)))
2139 (defun org-index--dump-variables ()
2140 "Dump variables of org-index; mostly for debugging"
2141 (interactive)
2142 "Dump all variables of org-index for debugging"
2143 (let ((buff (get-buffer-create "*org-index-dump-variables*"))
2144 (maxlen 0)
2145 vars name value)
2147 (with-current-buffer buff
2148 (erase-buffer)
2149 (mapatoms (lambda (s) (when (and (boundp s)
2150 (string-prefix-p "org-index-" (symbol-name s)))
2152 (setq name (symbol-name s))
2153 (setq value (symbol-value s))
2154 (setq vars (cons (cons name value) vars))
2155 (if (> (length name) maxlen)
2156 (setq maxlen (length name))))))
2157 (setq vars (sort vars (lambda (x y) (string< (car x) (car y)))))
2158 (mapc (lambda (x) (insert (format (format "%%-%ds: %%s\n" (+ maxlen 1)) (car x) (cdr x))))
2159 vars)
2160 (pop-to-buffer buff))))
2163 (defadvice org-mark-ring-goto (after org-index--advice-text-to-yank activate)
2164 "Make text from org-index available for yank."
2165 (when org-index--text-to-yank
2166 (kill-new org-index--text-to-yank)
2167 (message (format "Ready to yank '%s'" org-index--text-to-yank))
2168 (setq org-index--text-to-yank nil)))
2171 (provide 'org-index)
2173 ;; Local Variables:
2174 ;; fill-column: 75
2175 ;; comment-column: 50
2176 ;; End:
2178 ;;; org-index.el ends here