add notdeft
This commit is contained in:
parent
a7b79d5468
commit
d5f257ce5a
18
org/init.el
18
org/init.el
|
@ -106,6 +106,24 @@
|
||||||
(setq org-habit-following-days 7)
|
(setq org-habit-following-days 7)
|
||||||
(setq org-agenda-span 1)
|
(setq org-agenda-span 1)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; notdeft search
|
||||||
|
; (add-to-list 'load-path "~/.emacs.d.profiles/org/notdeft")
|
||||||
|
; (add-to-list 'load-path "~/.emacs.d.profiles/org/notdeft/extras")
|
||||||
|
; (require 'notdeft-autoloads)
|
||||||
|
; (require 'notdeft-org9)
|
||||||
|
; (setq notdeft-allow-org-property-drawers t)
|
||||||
|
; (add-hook 'org-mode-hook 'notdeft-note-mode)
|
||||||
|
; (setq notdeft-extension "org")
|
||||||
|
; (setq notdeft-secondary-extensions '("md" "txt"))
|
||||||
|
; (setq notdeft-directories '("~/org"
|
||||||
|
; "~/org/blog"
|
||||||
|
; "~/org/culinary"
|
||||||
|
; "~/org/health"
|
||||||
|
; "~/org/photography"
|
||||||
|
; "~/org/reading"
|
||||||
|
; ))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
; ui tuning
|
; ui tuning
|
||||||
(diminish 'projectile-mode)
|
(diminish 'projectile-mode)
|
||||||
|
|
14
org/notdeft/.gitignore
vendored
Normal file
14
org/notdeft/.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
*~
|
||||||
|
\#*
|
||||||
|
.\#*
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
/xapian/notdeft-xapian
|
||||||
|
*.elc
|
||||||
|
notdeft-autoloads.el
|
||||||
|
local.mk
|
||||||
|
/README.html
|
||||||
|
.dir-locals.el
|
||||||
|
/download/
|
||||||
|
/PKGNAMEVER
|
||||||
|
.notdeft-db/
|
30
org/notdeft/Makefile
Normal file
30
org/notdeft/Makefile
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
default : clean install
|
||||||
|
|
||||||
|
# Override this to add custom load paths to the required libraries, or perhaps to load the init file with "emacs --batch -l ~/.emacs".
|
||||||
|
EMACS_BATCH := emacs --batch
|
||||||
|
|
||||||
|
-include local.mk
|
||||||
|
|
||||||
|
install :
|
||||||
|
$(EMACS_BATCH) -L . -l notdeft-install -f notdeft-install-autoloads -f notdeft-install-bytecode
|
||||||
|
|
||||||
|
exe :
|
||||||
|
$(MAKE) -C xapian
|
||||||
|
|
||||||
|
clean :
|
||||||
|
-rm notdeft-autoloads.el *.elc extras/*.elc
|
||||||
|
|
||||||
|
PKGVER := $(shell date --utc +%Y%m%d.%H%M)
|
||||||
|
PKGNAMEVER := notdeft-$(PKGVER)
|
||||||
|
PKGTMPDIR := /tmp/$(PKGNAMEVER)
|
||||||
|
PKGMANIFEST := $(PKGTMPDIR)/notdeft-pkg.el
|
||||||
|
|
||||||
|
package :
|
||||||
|
mkdir -p download
|
||||||
|
-rm -r $(PKGTMPDIR)
|
||||||
|
mkdir -p $(PKGTMPDIR)
|
||||||
|
cp -ri ./ $(PKGTMPDIR)/
|
||||||
|
( cd $(PKGTMPDIR) && git clean -dxffq && rm -rf .git && rm .gitignore Makefile )
|
||||||
|
echo '(define-package "notdeft" "'$(PKGVER)'"' > $(PKGMANIFEST)
|
||||||
|
echo ' "Note manager and search engine")' >> $(PKGMANIFEST)
|
||||||
|
( tar --create --file download/$(PKGNAMEVER).tar -C /tmp $(PKGNAMEVER) )
|
54
org/notdeft/README.org
Normal file
54
org/notdeft/README.org
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#+TITLE: NotDeft
|
||||||
|
#+AUTHOR: Tero Hasu
|
||||||
|
#+OPTIONS: toc:nil
|
||||||
|
|
||||||
|
/NotDeft/ is a spin-off of [[https://jblevins.org/projects/deft/][Deft]], an “Emacs mode for quickly browsing, filtering, and editing directories of plain text notes.” NotDeft retains functionality similar to Deft's, albeit with less configurability. In addition, NotDeft features efficient, local, [[https://xapian.org/][Xapian]]-engine-based free-text search over potentially very large numbers of note files; in that respect it is like the [[https://notmuchmail.org/][Notmuch]] Emacs mode for managing email.
|
||||||
|
|
||||||
|
NotDeft is not as deft in note management as Deft. One reason for this is that NotDeft is designed to support managing of multiple directories of notes. Another complication is that locating the desired notes is a two-stage process, as it entails both searching for a set of notes by entering a query, and then further narrowing down the result set through interactive filtering.
|
||||||
|
|
||||||
|
[[file:images/notdeft-screenshot-query-and-filter.png]]
|
||||||
|
|
||||||
|
NotDeft does not aim for the user interaction simplicity of applications like Deft and Notational Velocity---instead, it intends to provide global note search and manipulation functionality, accessible from various Emacs buffers and Emacs-based applications.
|
||||||
|
|
||||||
|
* Quick Start
|
||||||
|
|
||||||
|
Open a terminal, and =cd= into your home directory. Download the source code into some directory with
|
||||||
|
: git clone https://github.com/hasu/notdeft.git
|
||||||
|
|
||||||
|
Then prepare NotDeft's Emacs Lisp files for use with the commands
|
||||||
|
: cd notdeft
|
||||||
|
: make
|
||||||
|
where =make= is assumed to invoke GNU Make.
|
||||||
|
|
||||||
|
Then build the Xapian backend by doing
|
||||||
|
: cd xapian
|
||||||
|
: make
|
||||||
|
If the =make= command fails, then you will need to ensure that you have the required libraries installed, and find the right C++ compiler incantation for building the =notdeft-xapian= program on your system. A notable library requirement for compiling the program is [[http://tclap.sourceforge.net/][TCLAP]].
|
||||||
|
|
||||||
|
To make NotDeft loadable in Emacs, add the following code to your Emacs startup file (e.g., “~/.emacs”):
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(add-to-list 'load-path "~/notdeft")
|
||||||
|
(add-to-list 'load-path "~/notdeft/extras")
|
||||||
|
(load "notdeft-example")
|
||||||
|
#+END_SRC
|
||||||
|
The [[./extras/notdeft-example.el][notdeft-example.el]] file is a sample configuration for NotDeft, which sets up NotDeft for use with Org mode based note files, also enabling some optional components of NotDeft, and setting up some keybindings. It may be a useful starting point for a personal configuration.
|
||||||
|
|
||||||
|
Create a “~/.deft” directory, and copy some “.org” files there.
|
||||||
|
|
||||||
|
Launch Emacs with
|
||||||
|
: emacs -f notdeft
|
||||||
|
|
||||||
|
Press =TAB= to enter a search query, and type characters to do further filtering of the results. Press =RET= to select a file to open. Use =C-c f1= to see other available commands.
|
||||||
|
|
||||||
|
To configure =notdeft-note-mode= minor mode for use automatically in note buffers, add the required directory local variable to “~/.deft” by entering the command =f6 a d l v=, and by saving the resulting file.
|
||||||
|
|
||||||
|
For other ways to install, configure, and use NotDeft, see the [[https://tero.hasu.is/notdeft/][documentation]].
|
||||||
|
|
||||||
|
* See Also
|
||||||
|
|
||||||
|
- https://tero.hasu.is/notdeft/ :: documentation
|
||||||
|
- https://tero.hasu.is/notdeft/download/ :: installable Emacs packages
|
||||||
|
- https://tero.hasu.is/tags/notdeft/ :: related blog posts
|
||||||
|
- https://github.com/hasu/notdeft :: source code repository
|
||||||
|
- [[./notdeft.el][“notdeft.el”]] :: some more documentation (in comments)
|
||||||
|
- [[https://xapian.org/][Xapian]] and [[https://jblevins.org/projects/deft/][Deft]] :: related software
|
112
org/notdeft/extras/notdeft-example.el
Normal file
112
org/notdeft/extras/notdeft-example.el
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
;; Autoloads for NotDeft commands.
|
||||||
|
(require 'notdeft-autoloads)
|
||||||
|
|
||||||
|
;; Full path of "notdeft-xapian" executable.
|
||||||
|
(let ((x
|
||||||
|
(let ((default-directory
|
||||||
|
(file-name-directory
|
||||||
|
(file-truename (locate-library "notdeft")))))
|
||||||
|
(file-truename "xapian/notdeft-xapian"))))
|
||||||
|
(setq notdeft-xapian-program
|
||||||
|
(and (file-executable-p x) x)))
|
||||||
|
|
||||||
|
;; As an alternative to the above, you can try building and
|
||||||
|
;; configuring "notdeft-xapian" on demand, but on most systems this
|
||||||
|
;; will not succeed out of the box.
|
||||||
|
;(add-hook 'notdeft-load-hook 'notdeft-xapian-make-program-when-uncurrent)
|
||||||
|
|
||||||
|
(defun run-local-variables-mode-hooks ()
|
||||||
|
"Run hooks for `major-mode' with locals set.
|
||||||
|
Like `run-mode-hooks', but run later, with any buffer and
|
||||||
|
directory local variables set."
|
||||||
|
(run-hooks (intern (concat (symbol-name major-mode)
|
||||||
|
"-local-variables-hook"))))
|
||||||
|
(add-hook 'hack-local-variables-hook 'run-local-variables-mode-hooks)
|
||||||
|
|
||||||
|
;; A variable determining whether to enable minor mode.
|
||||||
|
(defcustom notdeft-note-mode-auto-enable nil
|
||||||
|
"Whether to enable NotDeft Note minor mode for a buffer."
|
||||||
|
:type 'boolean
|
||||||
|
:safe 'booleanp)
|
||||||
|
(make-variable-buffer-local 'notdeft-note-mode-auto-enable)
|
||||||
|
|
||||||
|
;; Define a hook for conditionally enabling the NotDeft minor mode.
|
||||||
|
(defun default-notdeft-hook ()
|
||||||
|
"Conditionally enable `notdeft-note-mode'.
|
||||||
|
Enable when the buffer local variable
|
||||||
|
`notdeft-note-mode-auto-enable' is set to a non-nil value."
|
||||||
|
(when notdeft-note-mode-auto-enable
|
||||||
|
(notdeft-note-mode 1)))
|
||||||
|
|
||||||
|
;; Have Org mode files respect the flag. A hook like this should be
|
||||||
|
;; set for all NotDeft note file types, and no others.
|
||||||
|
(add-hook 'org-mode-local-variables-hook 'default-notdeft-hook)
|
||||||
|
|
||||||
|
(defun my-notdeft-add-directory-local-variables ()
|
||||||
|
"Add `notdeft-note-mode-auto-enable' flag.
|
||||||
|
Add it for all `notdeft-directories'."
|
||||||
|
(interactive)
|
||||||
|
(require 'notdeft) ;; for `notdeft-directories'
|
||||||
|
(dolist (dir notdeft-directories)
|
||||||
|
(make-directory dir t)
|
||||||
|
(let ((default-directory dir))
|
||||||
|
(add-dir-local-variable nil 'notdeft-note-mode-auto-enable t))))
|
||||||
|
|
||||||
|
;; Org mode "deft:" and "notdeft:" link support.
|
||||||
|
(eval-after-load 'org
|
||||||
|
(lambda ()
|
||||||
|
(let ((ver (ignore-errors
|
||||||
|
(car (version-to-list org-version)))))
|
||||||
|
(require (if (and ver (< ver 9))
|
||||||
|
'notdeft-org8
|
||||||
|
'notdeft-org9)))))
|
||||||
|
|
||||||
|
;; Add global bindings for NotDeft. To do that, bind a custom keymap
|
||||||
|
;; that inherits from NotDeft's, one that we can use to override and
|
||||||
|
;; add to the predefined set of bindings.
|
||||||
|
(require 'notdeft-global)
|
||||||
|
(defvar my-notdeft-global-map
|
||||||
|
(let ((map (make-sparse-keymap)))
|
||||||
|
(define-key map [(a) (d) (l) (v)]
|
||||||
|
#'my-notdeft-add-directory-local-variables)
|
||||||
|
(define-key map [(l)] #'notdeft-org-link-existing-note) ;; l for link
|
||||||
|
(define-key map [(n)] #'notdeft-org-link-new-file) ;; n for new
|
||||||
|
(define-key map [(s)] #'org-store-link) ;; s for store
|
||||||
|
(define-key map [(S)] #'notdeft-org-store-deft-link) ;; s for store
|
||||||
|
(define-key map [(*)] #'notdeft-org-open-heading-as-query)
|
||||||
|
(set-keymap-parent map 'notdeft-global-map)
|
||||||
|
map)
|
||||||
|
"Custom keymap for accessing NotDeft functionality.
|
||||||
|
|
||||||
|
\\{my-notdeft-global-map}")
|
||||||
|
(fset 'my-notdeft-global-map my-notdeft-global-map)
|
||||||
|
(global-set-key [f6] 'my-notdeft-global-map)
|
||||||
|
|
||||||
|
;; Add Org-specific bindings that are also usable in a NotDeft buffer.
|
||||||
|
(add-hook 'notdeft-load-hook
|
||||||
|
(lambda ()
|
||||||
|
(define-key notdeft-mode-map (kbd "C-c S")
|
||||||
|
#'notdeft-org-store-deft-link)))
|
||||||
|
|
||||||
|
(require 'hydra nil t)
|
||||||
|
(when (featurep 'hydra)
|
||||||
|
;; Augment `notdeft-mode' bindings with a hydra.
|
||||||
|
(autoload 'notdeft-mode-hydra/body "notdeft-mode-hydra" nil t)
|
||||||
|
(add-hook 'notdeft-load-hook
|
||||||
|
(lambda ()
|
||||||
|
(define-key notdeft-mode-map (kbd "C-c h")
|
||||||
|
#'notdeft-mode-hydra/body)))
|
||||||
|
|
||||||
|
;; Augment the global NotDeft keymap with a hydra also.
|
||||||
|
(autoload 'notdeft-global-hydra/body "notdeft-global-hydra" nil t)
|
||||||
|
(define-key my-notdeft-global-map [(h)] #'notdeft-global-hydra/body))
|
||||||
|
|
||||||
|
(require 'ivy nil t)
|
||||||
|
(when (featurep 'ivy)
|
||||||
|
;; Do minibuffer note selection by search and then Ivy choice list.
|
||||||
|
(require 'notdeft-ivy)
|
||||||
|
(add-to-list 'ivy-re-builders-alist
|
||||||
|
'(notdeft-ivy-completing-read . ivy--regex-ignore-order))
|
||||||
|
(setq notdeft-completing-read-function 'notdeft-ivy-completing-read)
|
||||||
|
(setq notdeft-select-note-file-by-search t)
|
||||||
|
(setq notdeft-select-note-file-all t))
|
BIN
org/notdeft/images/notdeft-screenshot-query-and-filter.png
Normal file
BIN
org/notdeft/images/notdeft-screenshot-query-and-filter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
52
org/notdeft/notdeft-global.el
Normal file
52
org/notdeft/notdeft-global.el
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
;;; notdeft-global.el --- Global NotDeft keymap -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; Copyright (C) 2017 by the author.
|
||||||
|
;; All rights reserved.
|
||||||
|
;; Author: Tero Hasu <tero@hasu.is>
|
||||||
|
;; See "notdeft.el" for licensing information.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; A keymap of NotDeft commands usable from outside `notdeft-mode'. It is
|
||||||
|
;; bound both as a variable and a function, to the name
|
||||||
|
;; `notdeft-global-map'.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(defvar notdeft-global-map
|
||||||
|
(let ((map (make-sparse-keymap)))
|
||||||
|
;; file management
|
||||||
|
(define-key map (kbd "C-n") #'notdeft-new-file)
|
||||||
|
(define-key map (kbd "C-m") #'notdeft-new-file-named)
|
||||||
|
(define-key map (kbd "C-x C-f") #'notdeft-find-file)
|
||||||
|
(define-key map (kbd "C-x C-w") #'notdeft-save-buffer)
|
||||||
|
(define-key map (kbd "C-d") #'notdeft-delete-file)
|
||||||
|
(define-key map (kbd "C-r") #'notdeft-rename-file)
|
||||||
|
(define-key map (kbd "C-v") #'notdeft-move-file)
|
||||||
|
(define-key map (kbd "C-x s") #'notdeft-move-into-subdir)
|
||||||
|
(define-key map (kbd "C-x e") #'notdeft-change-file-extension)
|
||||||
|
(define-key map (kbd "C-a") #'notdeft-archive-file)
|
||||||
|
(define-key map (kbd "C-i") #'notdeft-show-file-directory)
|
||||||
|
(define-key map (kbd "C-x d") #'notdeft-open-in-deft)
|
||||||
|
;; state
|
||||||
|
(define-key map (kbd "C-j") #'notdeft-chdir)
|
||||||
|
(define-key map (kbd "C-x g") #'notdeft-refresh)
|
||||||
|
(define-key map (kbd "C-x c") #'notdeft-gc)
|
||||||
|
(define-key map (kbd "C-x r") #'notdeft-reindex)
|
||||||
|
;; search
|
||||||
|
(define-key map (kbd "C-o") #'notdeft-open-query)
|
||||||
|
(define-key map (kbd "C-f") #'notdeft-query-select-find-file)
|
||||||
|
(define-key map (kbd "C-x o") #'notdeft-lucky-find-file)
|
||||||
|
;; movement
|
||||||
|
(define-key map (kbd "C-x b") #'notdeft-switch-to-note-buffer)
|
||||||
|
(define-key map (kbd "C-x B") #'notdeft-switch-to-buffer)
|
||||||
|
;; other
|
||||||
|
(define-key map (kbd "C-c") #'notdeft)
|
||||||
|
map)
|
||||||
|
"Global keymap for NotDeft.
|
||||||
|
|
||||||
|
\\{notdeft-global-map}")
|
||||||
|
(fset 'notdeft-global-map notdeft-global-map)
|
||||||
|
|
||||||
|
(provide 'notdeft-global)
|
||||||
|
|
||||||
|
;;; notdeft-global.el ends here
|
68
org/notdeft/notdeft-install.el
Normal file
68
org/notdeft/notdeft-install.el
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
;;; notdeft-install.el --- NotDeft installer -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; Copyright (C) 2020 by the authors.
|
||||||
|
;; All rights reserved.
|
||||||
|
;; Author: Tero Hasu <tero@hasu.is>
|
||||||
|
;; See "notdeft.el" for licensing information.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Functionality for setting up NotDeft from its source distribution,
|
||||||
|
;; without using a package manager.
|
||||||
|
;;
|
||||||
|
;; Suggested use:
|
||||||
|
;; (require 'notdeft-install)
|
||||||
|
;; (notdeft-install)
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'autoload)
|
||||||
|
(require 'bytecomp)
|
||||||
|
|
||||||
|
(declare-function notdeft-xapian-make-program "notdeft-xapian-make")
|
||||||
|
|
||||||
|
(defun notdeft-install-autoloads ()
|
||||||
|
"Generate NotDeft autoloads and load them."
|
||||||
|
(let ((home (file-name-directory
|
||||||
|
(locate-library "notdeft-install"))))
|
||||||
|
(let ((generated-autoload-file
|
||||||
|
(expand-file-name "notdeft-autoloads.el" home)))
|
||||||
|
(update-directory-autoloads home))
|
||||||
|
(load "notdeft-autoloads.el" nil nil t)))
|
||||||
|
|
||||||
|
(defun notdeft-install-bytecode (&optional force)
|
||||||
|
"Generate NotDeft Emacs Lisp \".elc\" files.
|
||||||
|
Optionally FORCE byte-compilation even when existing bytecode
|
||||||
|
files appear to be up-to-date."
|
||||||
|
(let ((dir (file-name-directory
|
||||||
|
(locate-library "notdeft-install"))))
|
||||||
|
(notdeft-install--byte-compile dir "./" t force)
|
||||||
|
(notdeft-install--byte-compile dir "extras/" nil force)))
|
||||||
|
|
||||||
|
(defun notdeft-install--byte-compile (dir subdir must force)
|
||||||
|
"Byte-compile NotDeft sources in DIR SUBDIR.
|
||||||
|
If so indicated, the directory MUST exist. Optionally FORCE the
|
||||||
|
compilation."
|
||||||
|
(let ((home (expand-file-name subdir dir)))
|
||||||
|
(when (or must (file-exists-p home))
|
||||||
|
(let ((files (directory-files home nil "^notdeft.*\\.el$")))
|
||||||
|
(dolist (file files)
|
||||||
|
(unless (member file '("notdeft-autoloads.el"
|
||||||
|
"notdeft-example.el"))
|
||||||
|
(let ((file (concat home file)))
|
||||||
|
(byte-recompile-file file force 0))))))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-install (&optional force)
|
||||||
|
"Generate NotDeft autoloads and binaries.
|
||||||
|
Optionally FORCE byte-compilation even when existing bytecode
|
||||||
|
files appear to be up-to-date."
|
||||||
|
(interactive "P")
|
||||||
|
(notdeft-install-autoloads)
|
||||||
|
(require 'notdeft-autoloads)
|
||||||
|
(notdeft-install-bytecode force)
|
||||||
|
(require 'notdeft-xapian-make)
|
||||||
|
(notdeft-xapian-make-program force))
|
||||||
|
|
||||||
|
(provide 'notdeft-install)
|
||||||
|
|
||||||
|
;;; notdeft-install.el ends here
|
227
org/notdeft/notdeft-org.el
Normal file
227
org/notdeft/notdeft-org.el
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
;;; notdeft-org.el --- some support for Org format NotDeft notes -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; Copyright (C) 2017-2020 by the author.
|
||||||
|
;; All rights reserved.
|
||||||
|
;; Author: Tero Hasu <tero@hasu.is>
|
||||||
|
;; See "notdeft.el" for licensing information.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Some NotDeft-specific support for `org-mode'. For Org mode version
|
||||||
|
;; 8 and higher.
|
||||||
|
;;
|
||||||
|
;; This feature requires no specific setup, as the public commands and
|
||||||
|
;; functions of this feature are autoloadable. However, see also
|
||||||
|
;; `notdeft-org8' and `notdeft-org9', which are optional extensions to
|
||||||
|
;; this feature, and do require setting up for use.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'org)
|
||||||
|
(require 'notdeft)
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-org-open-deft-link (link)
|
||||||
|
"Visit the NotDeft note specified by LINK.
|
||||||
|
The argument is a non-directory filename, possibly followed by
|
||||||
|
search options (see the fourth argument of `org-open-file'). This
|
||||||
|
function defines the opening of Org \"deft:\" links."
|
||||||
|
(let ((name link) search)
|
||||||
|
(save-match-data
|
||||||
|
(when (string-match "::\\(.+\\)\\'" link)
|
||||||
|
(setq search (match-string 1 link)
|
||||||
|
name (substring link 0 (match-beginning 0)))))
|
||||||
|
(let ((path (notdeft-file-by-basename name)))
|
||||||
|
(if (not path)
|
||||||
|
(message "No NotDeft note %S" name)
|
||||||
|
(org-open-file path t nil search)))))
|
||||||
|
|
||||||
|
(defun notdeft-org-read-deft-link-name ()
|
||||||
|
"Query for a \"deft:\" link name.
|
||||||
|
Do so interactively. Return the name component of a link, without
|
||||||
|
the \"deft:\" prefix."
|
||||||
|
(let ((name-lst (notdeft-make-basename-list)))
|
||||||
|
;; `ido` has been a part of Emacs since version 22
|
||||||
|
(when name-lst
|
||||||
|
(ido-completing-read "NotDeft note: " name-lst))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-org-complete-deft-link (&optional prefix)
|
||||||
|
"Define completion for Org \"deft:\" links.
|
||||||
|
The optional PREFIX argument is ignored."
|
||||||
|
(ignore prefix)
|
||||||
|
(let* ((file (notdeft-select-note-file))
|
||||||
|
(name (when file
|
||||||
|
(file-name-nondirectory file))))
|
||||||
|
(concat "deft:" (or name ""))))
|
||||||
|
|
||||||
|
(defvar notdeft-describe-link 'notdeft-title-from-file-content
|
||||||
|
"Function to determine NotDeft note file link description.
|
||||||
|
The function is given the file name as its sole argument.
|
||||||
|
Used by `notdeft-select-make-org-link'.")
|
||||||
|
|
||||||
|
(defun notdeft-org-read-link-description (&optional desc)
|
||||||
|
"Read a link description, interactively.
|
||||||
|
If DESC is provided, it is used as the initial input. Returns a
|
||||||
|
string, or nil if no non-whitespace description was provided."
|
||||||
|
(notdeft-chomp-nullify
|
||||||
|
(read-string "Description: " desc nil nil t)))
|
||||||
|
|
||||||
|
(defun notdeft-make-deft-link (name &optional desc)
|
||||||
|
"Turn NAME and DESC into a \"deft:\" link.
|
||||||
|
NAME should be a non-directory file name with extension."
|
||||||
|
(org-make-link-string (concat "deft:" name) desc))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-org-store-deft-link ()
|
||||||
|
"Store a \"deft:\" link for the current note.
|
||||||
|
Like `org-store-link', store the link into `org-stored-links'."
|
||||||
|
(interactive)
|
||||||
|
(let ((old-file (notdeft-current-filename t t)))
|
||||||
|
(when old-file
|
||||||
|
(let* ((name (file-name-nondirectory old-file))
|
||||||
|
(link (concat "deft:" name))
|
||||||
|
(desc (notdeft-title-from-file-content old-file)))
|
||||||
|
(push (list link desc) org-stored-links)
|
||||||
|
(message "Stored: %s" (or desc link))))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-org-link-existing-note (notename &optional desc region)
|
||||||
|
"Create a \"deft:\" link to an existing note.
|
||||||
|
Link to a note by NOTENAME, inserting a link description if DESC
|
||||||
|
is non-nil. Insert the created link at point, unless REGION in
|
||||||
|
specified \(as a list of two positions), in which case replace
|
||||||
|
that region. When called interactively: offer a list of notes
|
||||||
|
from which to choose the link target; query for a note
|
||||||
|
description, offering to use the text of any active region as the
|
||||||
|
title, or the result of calling `notdeft-describe-link'
|
||||||
|
otherwise; use any active region as REGION; if one
|
||||||
|
\\[universal-argument] is given, then insert a link without DESC;
|
||||||
|
and if two \\[universal-argument]s are given, use the title of
|
||||||
|
any note as the description. If multiple notes have the same
|
||||||
|
NOTENAME, pick any one of them for deriving a description."
|
||||||
|
(interactive
|
||||||
|
(progn
|
||||||
|
(barf-if-buffer-read-only)
|
||||||
|
(let* ((pfx (prefix-numeric-value current-prefix-arg))
|
||||||
|
(region (when mark-active
|
||||||
|
(list (region-beginning) (region-end))))
|
||||||
|
(desc (and region (= pfx 1)
|
||||||
|
(apply #'buffer-substring-no-properties region)))
|
||||||
|
(file
|
||||||
|
;; Select note before prompting for any description.
|
||||||
|
;; Provide any region text as a selection hint.
|
||||||
|
(let ((notdeft-select-note-file-query desc)
|
||||||
|
(notdeft-xapian-order-by-time nil))
|
||||||
|
(notdeft-select-note-file)))
|
||||||
|
(desc
|
||||||
|
(when (and file (/= pfx 4))
|
||||||
|
(notdeft-org-read-link-description
|
||||||
|
(or desc
|
||||||
|
(pcase pfx
|
||||||
|
(1 (notdeft-chomp-nullify
|
||||||
|
(funcall notdeft-describe-link file)))
|
||||||
|
(16 (notdeft-title-from-file-content file)))))))
|
||||||
|
(notename (when file
|
||||||
|
(file-name-nondirectory file))))
|
||||||
|
(list notename desc region))))
|
||||||
|
(when notename
|
||||||
|
(when region
|
||||||
|
(apply #'delete-region region))
|
||||||
|
(insert (notdeft-make-deft-link notename desc))))
|
||||||
|
|
||||||
|
(defalias 'notdeft-insert-org-link
|
||||||
|
#'notdeft-org-link-existing-note
|
||||||
|
"Deprecated. Use `notdeft-org-link-existing-note'.")
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-org-link-new-file (&optional dir notename ext data desc region)
|
||||||
|
"Create a \"deft:\" link to a new note.
|
||||||
|
Return the filename of the created file. The arguments DIR,
|
||||||
|
NOTENAME, EXT, and DATA are as for `notdeft-create-file'. Use
|
||||||
|
DESC, if any, as the link description. Insert an Org \"deft:\"
|
||||||
|
link to the newly created note at point, except if REGION is
|
||||||
|
non-nil, in which case replace that buffer region \(specified as
|
||||||
|
a list of two position values) with the link. When called
|
||||||
|
interactively: query for a note title, offering to use the text
|
||||||
|
of any active region as the title; use any active region as
|
||||||
|
REGION; derive a NOTENAME based on the title, as usual; use the
|
||||||
|
default filename extension as EXT; if one \\[universal-argument]
|
||||||
|
is given, then insert a link without DESC; if two
|
||||||
|
\\[universal-argument]s are given, the query for a target DIR for
|
||||||
|
the new note."
|
||||||
|
(interactive
|
||||||
|
(progn
|
||||||
|
(barf-if-buffer-read-only)
|
||||||
|
(let* ((pfx (prefix-numeric-value current-prefix-arg))
|
||||||
|
(region (when mark-active
|
||||||
|
(list (region-beginning) (region-end))))
|
||||||
|
(title
|
||||||
|
(notdeft-chomp-nullify
|
||||||
|
(read-string "Title: "
|
||||||
|
(when region
|
||||||
|
(notdeft-chomp
|
||||||
|
(apply #'buffer-substring-no-properties region)))
|
||||||
|
nil nil t)))
|
||||||
|
(desc (unless (= pfx 4)
|
||||||
|
(notdeft-org-read-link-description title))))
|
||||||
|
(list (and (= pfx 16) 'dir) ;; dir
|
||||||
|
(and title `(title, title)) ;; notename
|
||||||
|
nil ;; ext
|
||||||
|
title ;; data
|
||||||
|
desc
|
||||||
|
region))))
|
||||||
|
(let* ((buf (current-buffer))
|
||||||
|
(name (file-name-nondirectory
|
||||||
|
(notdeft-create-file dir notename ext data))))
|
||||||
|
(switch-to-buffer buf)
|
||||||
|
(when region
|
||||||
|
(apply #'delete-region region))
|
||||||
|
(insert (notdeft-make-deft-link name desc))))
|
||||||
|
|
||||||
|
(defalias 'notdeft-link-new-file
|
||||||
|
#'notdeft-org-link-new-file
|
||||||
|
"Deprecated. Use `notdeft-org-link-new-file'.")
|
||||||
|
|
||||||
|
(eval-when-compile
|
||||||
|
(defvar notdeft-xapian-query))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-org-open-notdeft-link (query)
|
||||||
|
"Open the NotDeft search specified by QUERY.
|
||||||
|
This defines the opening of Org \"notdeft:\" links."
|
||||||
|
(notdeft-open-query query))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-org-store-notdeft-link ()
|
||||||
|
"Store the current NotDeft search as an Org link.
|
||||||
|
Use `org-store-link' to invoke this function in a `notdeft-mode'
|
||||||
|
buffer. Return nil if not in `notdeft-mode', or if there is no
|
||||||
|
current query."
|
||||||
|
(when (and (eq major-mode 'notdeft-mode)
|
||||||
|
notdeft-xapian-query)
|
||||||
|
(org-store-link-props
|
||||||
|
:type "notdeft"
|
||||||
|
:link (concat "notdeft:" notdeft-xapian-query))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-org-open-heading-as-query (&optional rank negate)
|
||||||
|
"Query for current Org heading text.
|
||||||
|
The RANK and NEGATE arguments are as for `notdeft-open-query'.
|
||||||
|
When called interactively, any prefix arguments are also
|
||||||
|
interpreted in the `notdeft-open-query' sense."
|
||||||
|
(interactive
|
||||||
|
(let ((prefix current-prefix-arg))
|
||||||
|
(list (equal prefix 1)
|
||||||
|
(equal prefix '(4)))))
|
||||||
|
(let ((title
|
||||||
|
(save-excursion
|
||||||
|
(org-back-to-heading t)
|
||||||
|
(nth 4 (org-heading-components)))))
|
||||||
|
(when title
|
||||||
|
(let ((title (notdeft-chomp title)))
|
||||||
|
(unless (string-equal title "")
|
||||||
|
(notdeft-open-phrase-as-query title rank negate))))))
|
||||||
|
|
||||||
|
(provide 'notdeft-org)
|
||||||
|
|
||||||
|
;;; notdeft-org.el ends here
|
36
org/notdeft/notdeft-org8.el
Normal file
36
org/notdeft/notdeft-org8.el
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
;;; notdeft-org8.el --- Org link support for NotDeft notes -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; Copyright (C) 2019 by the author.
|
||||||
|
;; All rights reserved.
|
||||||
|
;; Author: Tero Hasu <tero@hasu.is>
|
||||||
|
;; See "notdeft.el" for licensing information.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Support for "deft:" and "notdeft:" links for `org-mode' version 8.
|
||||||
|
;; The `org-add-link-type' API is obsolete since Org version 9.
|
||||||
|
;;
|
||||||
|
;; Suggested use:
|
||||||
|
;; (eval-after-load 'org (lambda () (require 'notdeft-org8)))
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'org)
|
||||||
|
|
||||||
|
(org-add-link-type
|
||||||
|
"deft"
|
||||||
|
#'notdeft-org-open-deft-link) ;; follow
|
||||||
|
|
||||||
|
(defun org-deft-complete-link ()
|
||||||
|
"Complete a \"deft:\" link.
|
||||||
|
Just call `notdeft-org-complete-deft-link'. Defined for
|
||||||
|
`org-link-try-special-completion', which expects a specific name
|
||||||
|
for the link-type-specific completion function."
|
||||||
|
(notdeft-org-complete-deft-link))
|
||||||
|
|
||||||
|
(org-add-link-type
|
||||||
|
"notdeft"
|
||||||
|
#'notdeft-org-open-notdeft-link) ;; follow
|
||||||
|
|
||||||
|
(provide 'notdeft-org8)
|
||||||
|
|
||||||
|
;;; notdeft-org8.el ends here
|
32
org/notdeft/notdeft-org9.el
Normal file
32
org/notdeft/notdeft-org9.el
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
;;; notdeft-org9.el --- Org link support for NotDeft notes -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; Copyright (C) 2017 by the author.
|
||||||
|
;; All rights reserved.
|
||||||
|
;; Author: Tero Hasu <tero@hasu.is>
|
||||||
|
;; See "notdeft.el" for licensing information.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Support for "deft:" and "notdeft:" links for `org-mode' version 9.
|
||||||
|
;; The `org-link-set-parameters' API is available since Org version 9,
|
||||||
|
;; in the `org' feature.
|
||||||
|
;;
|
||||||
|
;; Suggested use:
|
||||||
|
;; (eval-after-load 'org (lambda () (require 'notdeft-org9)))
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'org)
|
||||||
|
|
||||||
|
(org-link-set-parameters
|
||||||
|
"deft"
|
||||||
|
:follow #'notdeft-org-open-deft-link
|
||||||
|
:complete #'notdeft-org-complete-deft-link)
|
||||||
|
|
||||||
|
(org-link-set-parameters
|
||||||
|
"notdeft"
|
||||||
|
:follow #'notdeft-org-open-notdeft-link
|
||||||
|
:store #'notdeft-org-store-notdeft-link)
|
||||||
|
|
||||||
|
(provide 'notdeft-org9)
|
||||||
|
|
||||||
|
;;; notdeft-org9.el ends here
|
79
org/notdeft/notdeft-path.el
Normal file
79
org/notdeft/notdeft-path.el
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
;;; notdeft-path.el --- NotDeft directory dynamic resolution -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; Copyright (C) 2018 by the author.
|
||||||
|
;; All rights reserved.
|
||||||
|
;; Author: Tero Hasu <tero@hasu.is>
|
||||||
|
;; See "notdeft.el" for licensing information.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; A system for resolving `notdeft-directories' dynamically, based on
|
||||||
|
;; a configurable `notdeft-path' specification. Might be useful when
|
||||||
|
;; storing some NotDeft directories on removable filesystems, allowing
|
||||||
|
;; the command `notdeft-refresh' to be used to update the available
|
||||||
|
;; `notdeft-directories' list. The function
|
||||||
|
;; `notdeft-refresh-directories' should be called where necessary to
|
||||||
|
;; ensure that the list is kept up to date.
|
||||||
|
;;
|
||||||
|
;; Suggested use:
|
||||||
|
;; (require 'notdeft-path)
|
||||||
|
;; (notdeft-refresh-directories)
|
||||||
|
;; (add-hook 'notdeft-pre-refresh-hook 'notdeft-refresh-directories)
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'cl-lib)
|
||||||
|
|
||||||
|
(defcustom notdeft-path '("~/.deft/")
|
||||||
|
"NotDeft directory search path.
|
||||||
|
A list of strings, or a function returning a list of strings. The
|
||||||
|
strings should name directories, which may or may not exist."
|
||||||
|
:type '(choice
|
||||||
|
(repeat (string :tag "Directory"))
|
||||||
|
(function :tag "Function"))
|
||||||
|
:safe (lambda (lst) (cl-every #'stringp lst))
|
||||||
|
:group 'notdeft)
|
||||||
|
|
||||||
|
(defvar notdeft-directories-changed-hook nil
|
||||||
|
"Hook run after each refresh of `notdeft-directories'.
|
||||||
|
It is called by `notdeft-refresh-directories'.")
|
||||||
|
|
||||||
|
(defun notdeft-existing-directories (dirs)
|
||||||
|
"Return a list of existing directories DIRS."
|
||||||
|
(mapcar #'file-name-as-directory
|
||||||
|
(cl-remove-if-not #'file-directory-p dirs)))
|
||||||
|
|
||||||
|
(defun notdeft-resolve-directories ()
|
||||||
|
"Resolve directories from `notdeft-path'.
|
||||||
|
Return the result as a list of strings that are syntactically
|
||||||
|
directory names, and name existing directories."
|
||||||
|
(let ((lst (if (functionp notdeft-path)
|
||||||
|
(funcall notdeft-path)
|
||||||
|
notdeft-path)))
|
||||||
|
(unless (listp lst)
|
||||||
|
(error "Expected a list: %S" lst))
|
||||||
|
(dolist (elem lst)
|
||||||
|
(unless (stringp elem)
|
||||||
|
(error "Expected a string: %S" elem)))
|
||||||
|
(notdeft-existing-directories lst)))
|
||||||
|
|
||||||
|
(eval-when-compile
|
||||||
|
(defvar notdeft-directories)
|
||||||
|
(defvar notdeft-directory))
|
||||||
|
|
||||||
|
(defun notdeft-refresh-directories ()
|
||||||
|
"Update `notdeft-directories' based on `notdeft-path'.
|
||||||
|
Only include existing directories. Also clear `notdeft-directory'
|
||||||
|
if it is no longer one of the `notdeft-directories'."
|
||||||
|
(setq notdeft-directories (notdeft-resolve-directories))
|
||||||
|
(when (and (boundp 'notdeft-directory) notdeft-directory)
|
||||||
|
(unless (and (file-directory-p notdeft-directory)
|
||||||
|
(cl-some (lambda (dir)
|
||||||
|
(file-equal-p notdeft-directory dir))
|
||||||
|
notdeft-directories))
|
||||||
|
(setq notdeft-directory nil)))
|
||||||
|
(run-hooks 'notdeft-directories-changed-hook)
|
||||||
|
notdeft-directories)
|
||||||
|
|
||||||
|
(provide 'notdeft-path)
|
||||||
|
|
||||||
|
;;; notdeft-path.el ends here
|
150
org/notdeft/notdeft-xapian-make.el
Normal file
150
org/notdeft/notdeft-xapian-make.el
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
;;; notdeft-xapian-make.el --- Xapian backend auto-installer -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; Copyright (C) 2020 by the authors.
|
||||||
|
;; All rights reserved.
|
||||||
|
;; Author: MaxSt <max@stoerchle.at>
|
||||||
|
;; Author: Tero Hasu <tero@hasu.is>
|
||||||
|
;; See "notdeft.el" for licensing information.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Functionality for compiling the C++ code for the NotDeft Xapian
|
||||||
|
;; backend, automatically or otherwise.
|
||||||
|
;;
|
||||||
|
;; Instead of setting the `notdeft-xapian-program' variable yourself,
|
||||||
|
;; you may instead load this `notdeft-xapian-make' feature to have the
|
||||||
|
;; program built and configured automatically. You may additionally
|
||||||
|
;; need to set the `notdeft-xapian-program-compile-command-format' to
|
||||||
|
;; something that produces a suitable compiler invocation for your
|
||||||
|
;; platform.
|
||||||
|
;;
|
||||||
|
;; Suggested use:
|
||||||
|
;; (add-hook 'notdeft-load-hook 'notdeft-xapian-make-program-when-uncurrent)
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(defcustom notdeft-xapian-program-compile-command-format
|
||||||
|
"c++ -o %s %s -std=c++11 -Wall `pkg-config --cflags --libs tclap` `xapian-config --cxxflags --libs`"
|
||||||
|
"Compilation shell command.
|
||||||
|
Can be a `format' string with two \"%s\" directives, or a
|
||||||
|
function of two arguments, with the first directive or argument
|
||||||
|
specifying the executable path for the program, and the second
|
||||||
|
specifying the C++ source file for the notdeft-xapian program. If
|
||||||
|
it is a function, it should do any necessary shell escaping of
|
||||||
|
the arguments."
|
||||||
|
:type '(choice
|
||||||
|
(string :tag "Format string")
|
||||||
|
(function :tag "Function"))
|
||||||
|
:group 'notdeft)
|
||||||
|
|
||||||
|
(defcustom notdeft-xapian-program-install-path
|
||||||
|
"notdeft-xapian"
|
||||||
|
"Path for the notdeft-xapian executable to build.
|
||||||
|
If the path is not absolute, it is considered relative to
|
||||||
|
`notdeft-xapian-home'."
|
||||||
|
:type 'string
|
||||||
|
:safe #'stringp
|
||||||
|
:group 'notdeft)
|
||||||
|
|
||||||
|
(defvar notdeft-xapian-home
|
||||||
|
(expand-file-name "xapian/"
|
||||||
|
(file-name-directory
|
||||||
|
(file-truename (locate-library "notdeft"))))
|
||||||
|
"Directory path for notdeft-xapian sources.
|
||||||
|
Must specify an absolute path.")
|
||||||
|
|
||||||
|
(defvar notdeft-xapian-compile-buffer-name "*Compile notdeft-xapian*"
|
||||||
|
"Name of the buffer used for compiling notdeft-xapian.")
|
||||||
|
|
||||||
|
(defun notdeft-xapian-program-current-p (&optional program)
|
||||||
|
"Whether the notdeft-xapian PROGRAM is current.
|
||||||
|
It is uncurrent if it does not exist as an executable, or if its
|
||||||
|
source file is newer. PROGRAM defaults to
|
||||||
|
`notdeft-xapian-program-install-path'."
|
||||||
|
(let ((exe-file (expand-file-name
|
||||||
|
(or program notdeft-xapian-program-install-path)
|
||||||
|
notdeft-xapian-home)))
|
||||||
|
(when (file-executable-p exe-file)
|
||||||
|
(let ((cxx-file (expand-file-name
|
||||||
|
"notdeft-xapian.cc"
|
||||||
|
notdeft-xapian-home)))
|
||||||
|
(when (file-exists-p cxx-file)
|
||||||
|
(not (time-less-p
|
||||||
|
(nth 5 (file-attributes exe-file))
|
||||||
|
(nth 5 (file-attributes cxx-file)))))))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-xapian-compile-program (&optional program)
|
||||||
|
"Compile the notdeft-xapian program.
|
||||||
|
Use notdeft-xapian sources in `notdeft-xapian-home', and build
|
||||||
|
the PROGRAM, which defaults to `notdeft-xapian-program-install-path'.
|
||||||
|
On success, return the path of the built executable."
|
||||||
|
(interactive)
|
||||||
|
(unless (file-directory-p notdeft-xapian-home)
|
||||||
|
(error "Cannot locate notdeft-xapian sources"))
|
||||||
|
(let* ((exe-file (expand-file-name
|
||||||
|
(or program notdeft-xapian-program-install-path)
|
||||||
|
notdeft-xapian-home))
|
||||||
|
(cxx-file (expand-file-name
|
||||||
|
"notdeft-xapian.cc"
|
||||||
|
notdeft-xapian-home))
|
||||||
|
(compile-command
|
||||||
|
(if (functionp notdeft-xapian-program-compile-command-format)
|
||||||
|
(funcall notdeft-xapian-program-compile-command-format
|
||||||
|
exe-file cxx-file)
|
||||||
|
(format notdeft-xapian-program-compile-command-format
|
||||||
|
(shell-quote-argument exe-file)
|
||||||
|
(shell-quote-argument cxx-file))))
|
||||||
|
(buffer (get-buffer-create notdeft-xapian-compile-buffer-name)))
|
||||||
|
(pop-to-buffer notdeft-xapian-compile-buffer-name)
|
||||||
|
(let ((exit-code
|
||||||
|
(call-process
|
||||||
|
"sh" nil buffer t "-c" compile-command)))
|
||||||
|
(unless (zerop exit-code)
|
||||||
|
(error "Compilation of notdeft-xapian failed: %s (%d)"
|
||||||
|
compile-command exit-code))
|
||||||
|
(unless (file-executable-p exe-file)
|
||||||
|
(error (concat "Compilation of notdeft-xapian failed: "
|
||||||
|
"Executable %S not created")
|
||||||
|
exe-file))
|
||||||
|
(message "Compilation of notdeft-xapian succeeded: %S" exe-file)
|
||||||
|
exe-file)))
|
||||||
|
|
||||||
|
(defun notdeft-xapian-make-program (&optional force)
|
||||||
|
"Compile notdeft-xapian program.
|
||||||
|
Only do that if the source directory `notdeft-xapian-home'
|
||||||
|
exists, and the target path `notdeft-xapian-program-install-path'
|
||||||
|
is non-nil. In that case generate the executable with the target
|
||||||
|
path, but only if any existing executable appears to be
|
||||||
|
uncurrent, or if the FORCE flag is non-nil. Return the absolute
|
||||||
|
target path if it is known, even if the program could not be
|
||||||
|
compiled."
|
||||||
|
(when notdeft-xapian-program-install-path
|
||||||
|
(when (and notdeft-xapian-home
|
||||||
|
(file-directory-p notdeft-xapian-home))
|
||||||
|
(let ((exe-file (expand-file-name
|
||||||
|
notdeft-xapian-program-install-path
|
||||||
|
notdeft-xapian-home)))
|
||||||
|
(when (or force (not (notdeft-xapian-program-current-p exe-file)))
|
||||||
|
(notdeft-xapian-compile-program exe-file))
|
||||||
|
exe-file))))
|
||||||
|
|
||||||
|
(eval-when-compile
|
||||||
|
(defvar notdeft-xapian-program))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun notdeft-xapian-make-program-when-uncurrent ()
|
||||||
|
"Compile notdeft-xapian program when it is uncurrent.
|
||||||
|
Do that as for `notdeft-xapian-make-program', but fail silently
|
||||||
|
if compilation fails. Set `notdeft-xapian-program' to the
|
||||||
|
program's absolute path, or to nil if the program does not exist
|
||||||
|
even after any compilation attempt."
|
||||||
|
(setq notdeft-xapian-program
|
||||||
|
(let ((exe-file
|
||||||
|
(ignore-errors
|
||||||
|
(notdeft-xapian-make-program nil))))
|
||||||
|
(when (and exe-file (file-executable-p exe-file))
|
||||||
|
exe-file))))
|
||||||
|
|
||||||
|
(provide 'notdeft-xapian-make)
|
||||||
|
|
||||||
|
;;; notdeft-xapian-make.el ends here
|
196
org/notdeft/notdeft-xapian.el
Normal file
196
org/notdeft/notdeft-xapian.el
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
;;; notdeft-xapian.el --- Xapian backend for NotDeft -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; Copyright (C) 2017 by the author.
|
||||||
|
;; All rights reserved.
|
||||||
|
;; Author: Tero Hasu <tero@hasu.is>
|
||||||
|
;; See "notdeft.el" for licensing information.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Xapian-specific functionality for NotDeft.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(defcustom notdeft-xapian-program nil
|
||||||
|
"Xapian backend's executable program path.
|
||||||
|
Specified as an absolute path. When nil, the Xapian backend is
|
||||||
|
disabled, and filtering does not concern search results, but all
|
||||||
|
notes in `notdeft-directories'."
|
||||||
|
:type '(choice (const :tag "None" nil)
|
||||||
|
(file :tag "Path"))
|
||||||
|
:safe #'string-or-null-p
|
||||||
|
:group 'notdeft)
|
||||||
|
|
||||||
|
(defcustom notdeft-xapian-max-results 100
|
||||||
|
"Maximum number of Xapian query results.
|
||||||
|
\(I.e., '--max-count' for `notdeft-xapian-program'.)
|
||||||
|
No limit if 0."
|
||||||
|
:type 'integer
|
||||||
|
:safe #'integerp
|
||||||
|
:group 'notdeft)
|
||||||
|
|
||||||
|
(defcustom notdeft-xapian-language "en"
|
||||||
|
"Stemming language to use in Xapian indexing and searching.
|
||||||
|
See Xapian documentation for a list of supported language names
|
||||||
|
and abbreviations. May be specified as \"none\" for no stemming.
|
||||||
|
The language identifier may be followed by the string \":cjk\" to
|
||||||
|
enable generation of n-grams from CJK text. The CJK option is
|
||||||
|
ignored unless a recent enough version of Xapian is used."
|
||||||
|
:type 'string
|
||||||
|
:safe #'stringp
|
||||||
|
:group 'notdeft)
|
||||||
|
|
||||||
|
(defcustom notdeft-xapian-order-by-time t
|
||||||
|
"Whether to order file list by decreasing modification time.
|
||||||
|
Otherwise order by decreasing relevance, unless overridden by
|
||||||
|
a query modifier."
|
||||||
|
:type 'boolean
|
||||||
|
:safe #'booleanp
|
||||||
|
:group 'notdeft)
|
||||||
|
|
||||||
|
(defcustom notdeft-xapian-boolean-any-case t
|
||||||
|
"Whether to allow query operators in any case.
|
||||||
|
That is, whether the operator syntax also allows
|
||||||
|
lowercase characters (e.g., \"and\" and \"or\")."
|
||||||
|
:type 'boolean
|
||||||
|
:safe #'booleanp
|
||||||
|
:group 'notdeft)
|
||||||
|
|
||||||
|
(defcustom notdeft-xapian-pure-not t
|
||||||
|
"Whether to allow \"NOT\" in queries.
|
||||||
|
Using such queries is costly on performance."
|
||||||
|
:type 'boolean
|
||||||
|
:safe #'booleanp
|
||||||
|
:group 'notdeft)
|
||||||
|
|
||||||
|
(defface notdeft-xapian-query-face
|
||||||
|
'((t :inherit font-lock-string-face :bold t))
|
||||||
|
"Face for NotDeft Xapian queries."
|
||||||
|
:group 'notdeft-faces)
|
||||||
|
|
||||||
|
(defvar notdeft-xapian-query-history nil
|
||||||
|
"Xapian query string history.
|
||||||
|
Not cleared between invocations of `notdeft-mode'.")
|
||||||
|
|
||||||
|
(defun notdeft-xapian-read-query (&optional initial)
|
||||||
|
"Read a Xapian query string, interactively.
|
||||||
|
Use and update `notdeft-xapian-query-history' in querying.
|
||||||
|
Optionally fill in the specified INITIAL input. Return the read
|
||||||
|
string, or nil if no query is given."
|
||||||
|
(let* ((hist (if (not initial)
|
||||||
|
'notdeft-xapian-query-history
|
||||||
|
(setq notdeft-xapian-query-history
|
||||||
|
(cons initial
|
||||||
|
notdeft-xapian-query-history))
|
||||||
|
'(notdeft-xapian-query-history . 1)))
|
||||||
|
(s (read-from-minibuffer
|
||||||
|
"Query: " ;; PROMPT
|
||||||
|
initial nil nil ;; INITIAL-CONTENTS KEYMAP READ
|
||||||
|
hist ;; HIST
|
||||||
|
nil ;; DEFAULT-VALUE
|
||||||
|
t ;; INHERIT-INPUT-METHOD
|
||||||
|
)))
|
||||||
|
(when (and s (not (string= s "")))
|
||||||
|
s)))
|
||||||
|
|
||||||
|
(eval-when-compile
|
||||||
|
(defvar notdeft-extension)
|
||||||
|
(defvar notdeft-secondary-extensions)
|
||||||
|
(defvar notdeft-allow-org-property-drawers))
|
||||||
|
|
||||||
|
(defun notdeft-xapian-index-dirs (dirs &optional recreate)
|
||||||
|
"Create or update a Xapian index for DIRS.
|
||||||
|
Each element of DIRS must be either a directory path string, or a
|
||||||
|
list of the form (directory-path . relative-file-path-list). With
|
||||||
|
RECREATE, truncate any existing index files."
|
||||||
|
(with-temp-buffer
|
||||||
|
(dolist (dir dirs)
|
||||||
|
(if (stringp dir)
|
||||||
|
(insert ":idir\n" (file-relative-name dir "~") "\n")
|
||||||
|
(let ((dir (car dir))
|
||||||
|
(files (cdr dir)))
|
||||||
|
(insert ":ifiles\n" (file-relative-name dir "~") "\n")
|
||||||
|
(insert (format "%d\n" (length files)))
|
||||||
|
(dolist (file files)
|
||||||
|
(insert file "\n")))))
|
||||||
|
(let ((ret
|
||||||
|
(apply
|
||||||
|
#'call-process-region
|
||||||
|
(point-min) ;; START
|
||||||
|
(point-max) ;; END
|
||||||
|
notdeft-xapian-program ;; PROGRAM
|
||||||
|
t ;; DELETE (delete input)
|
||||||
|
t ;; BUFFER (output to current buffer)
|
||||||
|
nil ;; DISPLAY (do not refresh)
|
||||||
|
`("index"
|
||||||
|
"--chdir" ,(expand-file-name "." "~")
|
||||||
|
,@(when recreate '("--recreate"))
|
||||||
|
,@(apply #'append
|
||||||
|
(mapcar
|
||||||
|
(lambda (ext)
|
||||||
|
`("--extension" ,(concat "." ext)))
|
||||||
|
(cons notdeft-extension
|
||||||
|
notdeft-secondary-extensions)))
|
||||||
|
"--lang" ,(or notdeft-xapian-language "none")
|
||||||
|
,@(when notdeft-allow-org-property-drawers
|
||||||
|
'("--allow-org-property-drawers"))
|
||||||
|
"--input"))))
|
||||||
|
(when (/= 0 ret)
|
||||||
|
(error "Index generation failed: %s (%d): %s"
|
||||||
|
notdeft-xapian-program ret (buffer-string))))))
|
||||||
|
|
||||||
|
(defun notdeft-xapian-search (dirs &optional query)
|
||||||
|
"On the Xapian indexes in DIRS, perform the search QUERY.
|
||||||
|
I.e., perform the query in terms of the Xapian indexes in the
|
||||||
|
specified DIRS. Where a query is not specified, use a query that
|
||||||
|
matches any file, and in that case consider
|
||||||
|
`notdeft-xapian-order-by-time' to be true. Return at most
|
||||||
|
`notdeft-xapian-max-results' results, as pathnames of the
|
||||||
|
matching files. Sort by relevance, modification time, or
|
||||||
|
non-directory filename, all descending, based on the
|
||||||
|
`notdeft-xapian-order-by-time' setting and any query modifiers."
|
||||||
|
(let ((time-sort (if query notdeft-xapian-order-by-time t))
|
||||||
|
(max-results notdeft-xapian-max-results)
|
||||||
|
name-sort)
|
||||||
|
(when query
|
||||||
|
(save-match-data
|
||||||
|
(while (string-match "^ *!\\([[:alpha:]]+\\)\\>" query)
|
||||||
|
(let ((opt (match-string 1 query)))
|
||||||
|
(setq query (substring query (match-end 0)))
|
||||||
|
(pcase (downcase opt)
|
||||||
|
("time" (setq time-sort t))
|
||||||
|
("rank" (setq time-sort nil))
|
||||||
|
("all" (setq max-results 0))
|
||||||
|
("file" (setq name-sort t)))))))
|
||||||
|
(let* ((query (notdeft-chomp-nullify query))
|
||||||
|
(s (shell-command-to-string
|
||||||
|
(concat
|
||||||
|
(shell-quote-argument notdeft-xapian-program) " search"
|
||||||
|
(if name-sort " --name-sort" "")
|
||||||
|
(if time-sort " --time-sort" "")
|
||||||
|
" --lang " (shell-quote-argument
|
||||||
|
(or notdeft-xapian-language "none"))
|
||||||
|
(if notdeft-xapian-boolean-any-case
|
||||||
|
" --boolean-any-case" "")
|
||||||
|
(if notdeft-xapian-pure-not
|
||||||
|
" --pure-not" "")
|
||||||
|
(if (> max-results 0)
|
||||||
|
(format " --max-count %d" max-results)
|
||||||
|
"")
|
||||||
|
(if query
|
||||||
|
(concat " --query " (shell-quote-argument query))
|
||||||
|
"")
|
||||||
|
" " (mapconcat
|
||||||
|
(lambda (dir)
|
||||||
|
(shell-quote-argument
|
||||||
|
(expand-file-name dir "~")))
|
||||||
|
dirs " "))))
|
||||||
|
(files
|
||||||
|
(mapcar
|
||||||
|
(lambda (file)
|
||||||
|
(expand-file-name file "~"))
|
||||||
|
(split-string s "\n" t))))
|
||||||
|
files)))
|
||||||
|
|
||||||
|
(provide 'notdeft-xapian)
|
||||||
|
|
||||||
|
;;; notdeft-xapian.el ends here
|
2890
org/notdeft/notdeft.el
Normal file
2890
org/notdeft/notdeft.el
Normal file
File diff suppressed because it is too large
Load diff
792
org/notdeft/web/notdeft-homepage.org
Normal file
792
org/notdeft/web/notdeft-homepage.org
Normal file
|
@ -0,0 +1,792 @@
|
||||||
|
# -*- mode:org; mode:notdeft-note -*-
|
||||||
|
#+TITLE: NotDeft (homepage)
|
||||||
|
#+KEYWORDS: website terohasu
|
||||||
|
#+VIEW_ACTION: export_hugo_page
|
||||||
|
#+CTIME: Wed, 23 Aug 2017 01:00:08 +0300
|
||||||
|
#+MTIME: Sun, 09 May 2021 23:13:34 +0200
|
||||||
|
#+PAGE_META: title = "NotDeft"
|
||||||
|
#+PAGE_META: url = "/notdeft/"
|
||||||
|
#+PAGE_META: aliases = ["/deft/"]
|
||||||
|
#+PAGE_META: tags = ["Emacs", "Lisp", "NotDeft", "Org", "software"]
|
||||||
|
#+OPTIONS: toc:nil
|
||||||
|
|
||||||
|
/NotDeft/ is an [[https://www.gnu.org/software/emacs/][Emacs]]-based manager and local search engine for directories of plain text notes. NotDeft features a [[https://xapian.org/][Xapian]] backend for efficient free-text search over potentially very large numbers of note files; in that respect it is like the [[https://notmuchmail.org/][Notmuch]] Emacs mode for managing email. NotDeft is a spin-off of the [[https://jblevins.org/projects/deft/][Deft]] note manager, and retains similar functionality for browsing, filtering, and managing note collections. While NotDeft inherits its user interface from Deft, that interface is used for managing search result sets of notes, rather than directory contents. When used together with [[https://orgmode.org/][Org mode]] and its support for document linking, NotDeft can also function as a “desktop wiki,” such that documents can also be found by following links, and not just by searching and filtering.
|
||||||
|
|
||||||
|
The NotDeft source code repository can be found at\\
|
||||||
|
https://github.com/hasu/notdeft
|
||||||
|
|
||||||
|
#+BEGIN_EXPORT html
|
||||||
|
<p class="text-align-center">
|
||||||
|
<img src="/notdeft/notdeft-query-filter.gif" />
|
||||||
|
</p>
|
||||||
|
#+END_EXPORT
|
||||||
|
|
||||||
|
#+TOC: headlines 2
|
||||||
|
|
||||||
|
* Quick Start
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: quick-start
|
||||||
|
:END:
|
||||||
|
|
||||||
|
For the impatient, this section outlines one way of downloading, installing and setting up NotDeft.
|
||||||
|
|
||||||
|
Open a terminal, and =cd= into your home directory. Download the source code into some directory with
|
||||||
|
: git clone https://github.com/hasu/notdeft.git
|
||||||
|
|
||||||
|
Then prepare and byte-compile Emacs Lisp files with the commands
|
||||||
|
: cd notdeft
|
||||||
|
: make
|
||||||
|
where =make= is assumed to invoke GNU Make.
|
||||||
|
|
||||||
|
Then build the Xapian backend by doing
|
||||||
|
: cd xapian
|
||||||
|
: make
|
||||||
|
If the =make= command fails, then you will need to ensure that you have the required libraries installed, and find the right C++ compiler incantation for building the =notdeft-xapian= program on your system. A notable library requirement for compiling the program is [[http://tclap.sourceforge.net/][TCLAP]].
|
||||||
|
|
||||||
|
To make NotDeft loadable in Emacs, add the following code to your Emacs startup file (e.g., “~/.emacs”):
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(add-to-list 'load-path "~/notdeft")
|
||||||
|
(add-to-list 'load-path "~/notdeft/extras")
|
||||||
|
(load "notdeft-example")
|
||||||
|
#+END_SRC
|
||||||
|
The “notdeft-example.el” file is a sample configuration for NotDeft, which sets up NotDeft for use with Org mode based note files, also enabling some optional components of NotDeft, and setting up some keybindings. It may be a useful starting point for a personal configuration.
|
||||||
|
|
||||||
|
Create a “~/.deft” directory, and copy some “.org” files there.
|
||||||
|
|
||||||
|
Launch Emacs with
|
||||||
|
: emacs -f notdeft
|
||||||
|
|
||||||
|
*Press =TAB= to enter a search query*, and type characters to do further filtering of the results. Press =RET= to select a file to open. Use =C-c f1= to see other available commands.
|
||||||
|
|
||||||
|
To configure =notdeft-note-mode= minor mode for use automatically in note buffers, add the required directory local variable to “~/.deft” by entering the command =f6 a d l v=, and by saving the resulting file.
|
||||||
|
|
||||||
|
For other ways to install, configure, and use NotDeft, see the rest of this page.
|
||||||
|
|
||||||
|
* NotDeft's Deft Origins
|
||||||
|
|
||||||
|
NotDeft is derived from Deft version 0.3, but differs in several notable ways:
|
||||||
|
1. Rather than supporting a single, customizable =deft-directory= (tree) of note files, NotDeft supports a customizable =notdeft-directories= search path of directory trees.
|
||||||
|
- If the search path includes multiple directories, then many file creation and management operations involve choosing a directory, making NotDeft not so deft.
|
||||||
|
2. NotDeft supports (optional) invocation of a =notdeft-xapian-program=, which uses the Xapian library to implement free-text search across note files, with convenient query syntax. The search is performed across all =notdeft-directories=, and further narrowing down of the result set can then be done incrementally by typing in a search string for purposes of filtering (as in Deft).
|
||||||
|
- That is, when =notdeft-xapian-program= is set, the file browser of NotDeft lists Xapian search results instead of listing directory contents.
|
||||||
|
3. NotDeft includes multiple functions and commands intended to be usable from outside =notdeft-mode=, leading to more complex modalities of use than with Deft's list view centric operation.
|
||||||
|
4. NotDeft supports the existence of multiple =notdeft-mode= buffers within a single Emacs instance, so that one can view and operate on multiple search result sets of notes simultaneously.
|
||||||
|
|
||||||
|
NotDeft is not Deft---it aims for wide utility rather than the user experience of a Notational Velocity type application. The added complication of having two stages (query, then filter) to the process of looking for interesting files makes NotDeft less simple and intuitive than Deft, as does having multiple directories, modalities, and buffers.
|
||||||
|
|
||||||
|
* NotDeft Installation
|
||||||
|
|
||||||
|
NotDeft can be installed either manually, or as a package.
|
||||||
|
|
||||||
|
** Manual Installation from Source
|
||||||
|
|
||||||
|
First download the source code with the command
|
||||||
|
: git clone https://github.com/hasu/notdeft.git
|
||||||
|
|
||||||
|
Go to the “notdeft” directory, and prepare the Emacs Lisp code for use with the command
|
||||||
|
: make
|
||||||
|
|
||||||
|
Add the directory containing those files to the Emacs search path by adding
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(add-to-list 'load-path "/path/to/repo/of/notdeft")
|
||||||
|
#+END_SRC
|
||||||
|
to your Emacs startup file (e.g., “~/.emacs”). Also add
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(require 'notdeft-autoloads)
|
||||||
|
#+END_SRC
|
||||||
|
to the Emacs startup file, to make NotDeft available for on-demand loading.
|
||||||
|
|
||||||
|
With the above setup work done, NotDeft is available for launching from within Emacs with the command
|
||||||
|
: M-x notdeft
|
||||||
|
|
||||||
|
While the above commands acquire, build, and set up NotDeft's Emacs Lisp code, they do not build and configure the C++-based Xapian backend; see [[*Building the Xapian Backend][Building the Xapian Backend]] and [[*Configuring the Xapian Backend][Configuring the Xapian Backend]].
|
||||||
|
|
||||||
|
** Installation as a Package with straight.el
|
||||||
|
|
||||||
|
The [[https://github.com/raxod502/straight.el][straight.el]] package manager is able to install NotDeft as a package directly from its source repository. If you have that manager correctly installed, then you can install NotDeft with the command
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(straight-use-package
|
||||||
|
'(notdeft
|
||||||
|
:type git :host github :repo "hasu/notdeft"
|
||||||
|
:files ("*.el" "xapian")))
|
||||||
|
#+END_SRC
|
||||||
|
which should download NotDeft, generate its autoloads, and handle Emacs Lisp file byte-compilation.
|
||||||
|
|
||||||
|
While that command downloads and unpacks the Xapian backend source code, it does not build it or configure it; see [[*Building the Xapian Backend][Building the Xapian Backend]] and [[*Configuring the Xapian Backend][Configuring the Xapian Backend]].
|
||||||
|
|
||||||
|
** Installation from a Package File
|
||||||
|
|
||||||
|
Installing from Git is recommended where you wish to be sure that you are installing the most recent available version. Still, installation from a downloadable package file is also an option.
|
||||||
|
|
||||||
|
To install NotDeft as a package, first [[./download/][download]] the (chosen version's) package, and then install the downloaded TAR file with
|
||||||
|
: M-x package-install-file
|
||||||
|
|
||||||
|
You can check whether the package has been installed by evaluating
|
||||||
|
: (package-installed-p 'notdeft)
|
||||||
|
If so, information about the installation can be shown with
|
||||||
|
: (describe-package 'notdeft)
|
||||||
|
No documentation is shown by that command, but it does show the location of the package's files, allowing navigation to the documentation.
|
||||||
|
|
||||||
|
One might also implement a command for opening something in the package. For example, the readme file can be opened with
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun notdeft-open-readme ()
|
||||||
|
(interactive)
|
||||||
|
(find-file
|
||||||
|
(expand-file-name
|
||||||
|
"README.org"
|
||||||
|
(package-desc-dir
|
||||||
|
(cadr (assq 'notdeft package-alist))))))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
While installing the package does unpack the Xapian backend source code, it does not build it or configure it; see [[*Building the Xapian Backend][Building the Xapian Backend]] and [[*Configuring the Xapian Backend][Configuring the Xapian Backend]].
|
||||||
|
|
||||||
|
** Building the Xapian Backend
|
||||||
|
|
||||||
|
To enable Xapian search queries, you should build the =notdeft-xapian= C++ program in the “xapian” directory. On some systems simply going into that directory and typing
|
||||||
|
: make
|
||||||
|
should do the trick, provided that the required tools and libraries have already been installed. Other systems may require more work not only on satisfying the dependencies, but also in finding the right C++ compiler incantation for building the program. (On some systems building it may not be feasible at all, and NotDeft's functionality will be more limited.)
|
||||||
|
|
||||||
|
Once a working compiler invocation command has been found, and the necessary C++ libraries have been installed, it is also possible to build the C++ program from within Emacs by using the included =notdeft-xapian-make= Emacs Lisp feature. To use it, set the variable =notdeft-xapian-program-compile-command-format= with the appropriate format string for the compilation command. In that format string the path of the executable comes first, and the path of the source file comes second. For example:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(setq notdeft-xapian-program-compile-command-format "g++ -o %s %s -std=c++11 -Wall `pkg-config --cflags --libs tclap` `xapian-config --cxxflags --libs`")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
With the feature appropriately configured you can then try issuing the command
|
||||||
|
: M-x notdeft-xapian-compile-program
|
||||||
|
which should display any build errors if the executable cannot be built.
|
||||||
|
|
||||||
|
*** Building the Backend Automatically
|
||||||
|
|
||||||
|
The =notdeft-xapian-make= Emacs Lisp feature also includes a mechanism for building the Xapian backend program whenever it is out of date with respect to its sources, or does not exist at all. This can be particularly useful if you =git pull= a new version of “notdeft-xapian.cc”, and do not wish to worry about manually rebuilding the latest version.
|
||||||
|
|
||||||
|
For example, we might try to build =notdeft-xapian= on NotDeft startup as necessary:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(add-hook 'notdeft-load-hook 'notdeft-xapian-make-program-when-uncurrent)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The =notdeft-xapian-make-program-when-uncurrent= function automatically sets the =notdeft-xapian-program= to the path of a successfully built program, so that it no longer needs to be specified otherwise.
|
||||||
|
|
||||||
|
* NotDeft Configuration
|
||||||
|
|
||||||
|
Once the =notdeft= feature has been loaded, you can see and edit all of its configuration options and their documentation with
|
||||||
|
: (customize-group "notdeft")
|
||||||
|
That command is also callable interactively as
|
||||||
|
: M-x customize-group RET notdeft RET
|
||||||
|
|
||||||
|
The most essential settings are
|
||||||
|
- =notdeft-directories= :: to specify the location(s) of your notes
|
||||||
|
- =notdeft-xapian-program= :: to specify the path of the Xapian search tool
|
||||||
|
|
||||||
|
** Specifying Note File Locations
|
||||||
|
|
||||||
|
In a simple case you would have a single directory (tree) of note file, specified by the =notdeft-directories= configuration variable, which you can configure with the command
|
||||||
|
: M-x customize-variable RET notdeft-directories RET
|
||||||
|
For example:
|
||||||
|
: (setq notdeft-directories '("~/all-my-notes"))
|
||||||
|
|
||||||
|
You can have multiple directories, which makes NotDeft use a bit harder, as you may at times get asked for a target directory for some file operations.
|
||||||
|
: (setq notdeft-directories '("~/some-notes" "~/some-more"))
|
||||||
|
|
||||||
|
If your notes are not in a fixed directory, but you'd rather discover the directories programmatically, it may be convenient to set =notdeft-directories= in your startup file. For example:
|
||||||
|
: (setq notdeft-directories (cons "~/notes" (file-expand-wildcards "~/*/notes")))
|
||||||
|
|
||||||
|
*** Sparse Directories
|
||||||
|
|
||||||
|
If you wish to include some additional text files into your searches, you may also explicitly specify files that reside outside any of the =notdeft-directories=. You must still specify a directory for a search index covering those files. In effect, you specify a /sparse directory/, since it is not scanned, but rather only explicitly specified files are considered to be NotDeft notes, if they exist.
|
||||||
|
|
||||||
|
To specify the index directories and any files within them, use the =notdeft-sparse-directories= configuration variable to specify directories and their file lists. For example:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(setq notdeft-sparse-directories
|
||||||
|
'(("~" .
|
||||||
|
("projects/magnolisp/web/magnolisp-homepage.org"
|
||||||
|
"projects/notdeft/web/notdeft-homepage.org"))))
|
||||||
|
#+END_SRC
|
||||||
|
where all note file paths are specified relative to the search index containing directory, which should be a parent directory of all the specified notes.
|
||||||
|
|
||||||
|
The usual note manipulation operations (renaming, deleting, etc.) are not available for notes in sparse directories, which are not managed by NotDeft as such. The facility exists merely to support cases where you have important note files spread around project-specific directories, ones that you want to make accessible from within NotDeft. If you have a standard naming convention for such files, you can certainly resolve the list value programmatically:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(setq notdeft-sparse-directories
|
||||||
|
`(("~" . ,(mapcar
|
||||||
|
(lambda (file) (file-relative-name file "~"))
|
||||||
|
(file-expand-wildcards "~/projects/*/web/*-homepage.org")))))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** Choosing the Note File Format
|
||||||
|
|
||||||
|
The default is to have the note filename =notdeft-extension= set to "org" to indicate the Org format. If you prefer some other note format, you should change that setting, which can be done with
|
||||||
|
: M-x customize-variable RET notdeft-extension RET
|
||||||
|
|
||||||
|
The configured =notdeft-extension= is used by default when creating new notes, but a note collection can also use other extensions. There are none by default, but you can define such secondary extensions with
|
||||||
|
: M-x customize-variable RET notdeft-secondary-extensions RET
|
||||||
|
|
||||||
|
For example, one might set these as
|
||||||
|
: (setq notdeft-extension "txt")
|
||||||
|
: (setq notdeft-secondary-extensions '("md" "org" "scrbl"))
|
||||||
|
|
||||||
|
** Configuring the File Naming Convention
|
||||||
|
|
||||||
|
When creating a note file with the =notdeft-new-file-named= command, NotDeft automatically derives a name for the file based on the title that is provided for the note. The configuration option =notdeft-notename-function= determines how the name is derived.
|
||||||
|
|
||||||
|
The default setting is to use the =notdeft-default-title-to-notename= function to translate the title to a file basename. For example, the title “Rust (programming language)” translates into
|
||||||
|
: rust-programming-language
|
||||||
|
|
||||||
|
The default implementation is suitable for titles with ASCII letters, and you probably want to pick a different implementation if your titles do not tend to use the English alphabet.
|
||||||
|
|
||||||
|
Where necessary, the configuration option =notdeft-new-file-data-function= provides even more control over the naming (and content) of new notes. Such a function could, for example, add some metadata to every new note's file name and/or content.
|
||||||
|
|
||||||
|
** Configuring the Xapian Backend
|
||||||
|
|
||||||
|
To have NotDeft use the =notdeft-xapian= program you've built, you will have to specify its full path in the =notdeft-xapian-program= variable. You could use =M-x customize-variable= to set it, or simply
|
||||||
|
: (setq notdeft-xapian-program "/path/to/notdeft-xapian")
|
||||||
|
|
||||||
|
Set the variable to the program's full absolute path, without any shorthands, as no shell expansion is performed on the path name---you may explicitly expand it using Emacs' =expand-file-name= function instead.
|
||||||
|
|
||||||
|
If you installed as a package, and built the =notdeft-xapian= executable in that location, then the appropriate setting may be
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(setq notdeft-xapian-program
|
||||||
|
(expand-file-name
|
||||||
|
"xapian/notdeft-xapian"
|
||||||
|
(package-desc-dir
|
||||||
|
(cadr (assq 'notdeft package-alist)))))
|
||||||
|
#+END_SRC
|
||||||
|
Such code must appear after
|
||||||
|
: (package-initialize)
|
||||||
|
|
||||||
|
See the other =notdeft-xapian-*= customization variables for configuring the Xapian indexing and searching behavior. Most notably:
|
||||||
|
- The configuration variable =notdeft-xapian-max-results= controls the maximum number of files to list in a =notdeft-mode= buffer. You may set it to 0 to always have all results displayed.
|
||||||
|
- The default is to order the results so that most recently edited files are listed first, but you may change this behavior by setting =notdeft-xapian-order-by-time= to =nil=, in which case Xapian's ranking mechanism is used instead.
|
||||||
|
|
||||||
|
* NotDeft Mode
|
||||||
|
|
||||||
|
Running the =notdeft= command switches to a =*NotDeft*= buffer, creating one as necessary. Such a buffer's major mode is =notdeft-mode=. Buffers with that mode are read only, and cannot be edited directly, although most keys without modifiers do cause editing of the filter string.
|
||||||
|
|
||||||
|
Roughly, there are three kinds of things one can do in a =*NotDeft*= buffer:
|
||||||
|
1. set a query string to define a result set of notes
|
||||||
|
2. filter the result set by interactively editing a filter string
|
||||||
|
3. manipulate note files though NotDeft's commands
|
||||||
|
|
||||||
|
That is, finding an interesting set of notes is a two-step process: (1) enter a query to define a “topic area” of interest, using the /Xapian query syntax/; and then (2) narrow down that set interactively by typing in a /list of substrings/ (in any order) that should match. It is possible to edit the query without modifying the filter string, and vice versa.
|
||||||
|
|
||||||
|
#+CAPTION: Querying and filtering in a =*NotDeft*= buffer.
|
||||||
|
[[./notdeft-screenshot-query-and-filter.png]]
|
||||||
|
|
||||||
|
The NotDeft Mode interface is optimized for editing the filter string. You can append characters to the filter by pressing regular symbol keys without modifiers. Other available commands include =DEL=, =M-DEL=, =C-y=, with familiar Emacs style behavior.
|
||||||
|
|
||||||
|
To enter a query, press =TAB= (or =C-c C-o=) to open a prompt for typing in the query. The query is then executed when you press =RET=.
|
||||||
|
|
||||||
|
To clear a query, you can
|
||||||
|
1. press =TAB= and enter the empty string, or
|
||||||
|
2. press =S-TAB=, or
|
||||||
|
3. =C-u C-c C-c= also works for clearing the query in addition to any filter string.
|
||||||
|
|
||||||
|
To manage the notes that are listed in the NotDeft Mode buffer, you can use mode-specific command, which are bound to the mode's =C-c= keymap. There are commands for renaming, deleting, and moving notes, for example. Press =C-c f1= to see a full list of the commands bound to =C-c=.
|
||||||
|
|
||||||
|
To open a =*NotDeft*= buffer directly with a particular search query, use the command =notdeft-open-query= from any buffer.
|
||||||
|
|
||||||
|
** Displaying Individual Filter String Matches
|
||||||
|
|
||||||
|
The filter string “emacs org mode” narrows a =*NotDeft*= buffer file list down only to the files that contain all of the substrings “emacs”, “org”, and “mode”. To see each of the matching positions within those files, consider entering the command =C-c g= (or =M-x notdeft-grep-for-filter=) to display the matching strings with highlighting. That command invokes the shell command =grep= (through the Emacs command =grep=), and displays the results in a separate buffer. This may fail to work if you system does not have a compatible =grep= executable on the search path.
|
||||||
|
|
||||||
|
** Using Multiple NotDeft Mode Buffers
|
||||||
|
|
||||||
|
NotDeft allows multiple =notdeft-mode= buffers to exist at once, which may be useful if one wants to explore multiple sets of search results at once. Each NotDeft buffer has its own state, including a search query, filter string, default directory for creating new notes, etc.
|
||||||
|
|
||||||
|
Normally, executing the =notdeft= command only creates a new =*NotDeft*= buffer if one does not already exist---otherwise the command merely switches to an existing =*NotDeft*= buffer. It is possible to have the command always create a new =notdeft-mode= buffer by invoking it with a prefix argument, i.e., =C-u M-x notdeft=.
|
||||||
|
|
||||||
|
The =notdeft-open-query= command also accepts a prefix argument, to arrange for the search results to be listed in a new buffer. This behavior can also be made the default for that command by setting the configuration parameter =notdeft-open-query-in-new-buffer= to =t=. With that parameter set, the prefix argument's meaning is inverted, so that =C-u M-x notdeft-open-query= does /not/ create an additional buffer.
|
||||||
|
|
||||||
|
The question of whether to create a new buffer does not apply to other search commands. Within a NotDeft buffer, the commands =notdeft-query-edit= and =notdeft-query-clear= merely replace the buffer's search result set, whereas the commands =notdeft-lucky-find-file= and =notdeft-query-select-find-file= do not use a NotDeft buffer for displaying their results.
|
||||||
|
|
||||||
|
For dealing with existing =notdeft-mode= buffers, there is a =notdeft-switch-to-buffer= command for interactively selecting a buffer and switching to it. It presents a choice list of buffer names in the minibuffer, and shows any query and filter strings associated with those buffers for better informed selection.
|
||||||
|
|
||||||
|
As for closing NotDeft buffers, the =notdeft-quit= command is bound to =C-c C-q=, and it can be invoked in three ways:
|
||||||
|
1. Without a prefix argument, it buries the current buffer.
|
||||||
|
2. With one prefix argument, it kills the current buffer.
|
||||||
|
3. With two prefix arguments, it kills /all/ =notdeft-mode= buffers.
|
||||||
|
|
||||||
|
#+CAPTION: Four Emacs “windows” with different NotDeft buffers.
|
||||||
|
[[./multiple-buffers.png]]
|
||||||
|
|
||||||
|
** Displaying File Path Information
|
||||||
|
|
||||||
|
By default, NotDeft does not show any note directory or file names in its list view, but this behavior can be controlled by specifying a =notdeft-file-display-function=.
|
||||||
|
|
||||||
|
For example, we can display the name of each note's containing NotDeft (root) directory, with abbreviations for long directory names:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(setq notdeft-file-display-function
|
||||||
|
(lambda (file w)
|
||||||
|
(when (> w 30)
|
||||||
|
(let* ((s (file-name-nondirectory
|
||||||
|
(directory-file-name
|
||||||
|
(notdeft-dir-of-file file))))
|
||||||
|
(s (pcase s
|
||||||
|
("bibliography-notes" "bib")
|
||||||
|
("homepage-notes" "hp")
|
||||||
|
(_ s)))
|
||||||
|
(s (if (> (string-width s) 12)
|
||||||
|
(truncate-string-to-width s 12)
|
||||||
|
s)))
|
||||||
|
(concat " " s)))))
|
||||||
|
#+END_SRC
|
||||||
|
We refrain from displaying any directory information in cases where the Emacs window is very narrow (as indicated by the =w= argument), as otherwise there will be little space left for the note titles.
|
||||||
|
|
||||||
|
#+CAPTION: NotDeft mode with directory indicators.
|
||||||
|
[[./directory-indicator.png]]
|
||||||
|
|
||||||
|
* NotDeft Note Mode
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: notdeft-note-mode
|
||||||
|
:END:
|
||||||
|
|
||||||
|
Invoking the =notdeft= command opens an Emacs buffer whose major mode is =notdeft-mode=. That mode displays a list of notes, and if you want the list to be automatically updated when a note file gets saved, you may want to enable the =notdeft-note-mode= minor mode for those files' buffers.
|
||||||
|
|
||||||
|
The sole purpose of =notdeft-note-mode= is to take care of keeping NotDeft's knowledge of the note collection up to date. Whenever a note file is saved, =notdeft-note-mode= sees to it that the search index is updated with the new file contents. NotDeft does not itself do anything to enable that mode, but rather the user should arrange for that to happen in some suitable way (see below for some suggestions). The benefit of this approach is that even if a note file then is open using a regular Emacs command (e.g., =find-file=), the editing buffer will notify NotDeft of any changes.
|
||||||
|
|
||||||
|
** Enabling NotDeft Note Mode based on Major Mode
|
||||||
|
|
||||||
|
The simple approach is to always enable =notdeft-note-mode= for the major mode(s) that you use for editing notes. For example:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(add-hook 'org-mode-hook 'notdeft-note-mode)
|
||||||
|
#+END_SRC
|
||||||
|
This approach should be safe in that changes to files not residing in =notdeft-directories= get ignored by NotDeft. Still, the approach has the disadvantage that the minor mode indicator “¬D” does not tell you whether a note is actually a NotDeft note.
|
||||||
|
|
||||||
|
** Enabling NotDeft Note Mode Locally to a Directory
|
||||||
|
|
||||||
|
Another solution is to try enabling =notdeft-note-mode= for every NotDeft /directory/ in terms of [[https://www.gnu.org/software/emacs/manual/html%255Fnode/emacs/Directory-Variables.html][per-directory local variables]]. For example, have your “.dir-locals.el” file state
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
((org-mode . ((mode . org)
|
||||||
|
(mode . notdeft-note))))
|
||||||
|
#+END_SRC
|
||||||
|
This way of declaring both a major and minor =mode= appears to work at least in some versions of Emacs, although it may rely on undefined behavior.
|
||||||
|
|
||||||
|
** Enabling NotDeft Note Mode based on a Directory-Local Variable
|
||||||
|
|
||||||
|
If enabling =notdeft-note-mode= directly in “.dir-locals.el” does not work or appeal to you, then it's possible to do the same thing indirectly, by using an actual per-directory local variable to indicate if the minor mode should be enabled. That is, you can have the “.dir-locals.el” file contain
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
((nil . ((notdeft-note-mode-auto-enable . t))))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The variable can be declared as
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defcustom notdeft-note-mode-auto-enable nil
|
||||||
|
"Whether to enable NotDeft note mode for a buffer."
|
||||||
|
:type 'boolean
|
||||||
|
:safe 'booleanp)
|
||||||
|
(make-variable-buffer-local 'notdeft-note-mode-auto-enable)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
To set that variable for a note directory, we can use the Emacs command
|
||||||
|
: M-x add-dir-local-variable RET nil RET notdeft-note-mode-auto-enable RET t RET
|
||||||
|
|
||||||
|
Or, if we want to programmatically set the variable for all our =notdeft-directories=, we can use the code
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(dolist (dir notdeft-directories)
|
||||||
|
(let ((default-directory dir))
|
||||||
|
(add-dir-local-variable nil 'notdeft-note-mode-auto-enable t)))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Defining and setting the variable alone does not enable the mode, which we want to do only for specific file types, reflecting our =notdeft-extension= and =notdeft-secondary-extensions= configuration. If we only supported =org-mode= files, we would like to say something like
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(add-hook
|
||||||
|
'org-mode-local-variables-hook
|
||||||
|
(lambda ()
|
||||||
|
(when notdeft-note-mode-auto-enable
|
||||||
|
(notdeft-note-mode 1))))
|
||||||
|
#+END_SRC
|
||||||
|
We cannot just use =org-mode-hook=, as directory locals are not yet set at the time when the mode is enabled. What is needed is a later hook, which in the above is called =org-mode-local-variables-hook=.
|
||||||
|
|
||||||
|
We also have to get such hooks to run. Borrowing code from “phils” at Stack Overflow, we can get our =org-mode-local-variables-hook= run by defining and registering a new kind of hook as
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun run-local-variables-mode-hooks ()
|
||||||
|
"Run hooks for `major-mode' with locals set.
|
||||||
|
Like `run-mode-hooks', but run later, with any buffer and
|
||||||
|
directory local variables set."
|
||||||
|
(run-hooks (intern (concat (symbol-name major-mode)
|
||||||
|
"-local-variables-hook"))))
|
||||||
|
(add-hook 'hack-local-variables-hook 'run-local-variables-mode-hooks)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The above solution gives us a “proper” way to enable the NotDeft note minor mode, and to do it only within directories that have a persistent NotDeft “signature” (in a “.dir-locals.el” file), and only for our chosen note-editing major modes.
|
||||||
|
|
||||||
|
* Using NotDeft from Non-NotDeft Modes
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: outside-notdeft-commands
|
||||||
|
:END:
|
||||||
|
|
||||||
|
Several of NotDeft's commands are autoloadable, and may be invoked from outside a =*NotDeft*= buffer. For example, to quickly find relevant notes when in another buffer, you might use
|
||||||
|
: M-x notdeft-open-query
|
||||||
|
which then interactively asks for a search query for opening up in a NotDeft buffer. That command can of course be bound to a key.
|
||||||
|
|
||||||
|
A command similar to =notdeft-open-query= is
|
||||||
|
: M-x notdeft-lucky-find-file
|
||||||
|
which also asks for a search query, but then proceeds to open up the most highly ranked result file directly, without going via a =*NotDeft*= buffer. This command is similar to =find-file= in Emacs, but avoids having to specify the path of the file you're interested in; instead, this approach to “file finding” relies on sufficiently unique titling or tagging of the notes involved.
|
||||||
|
|
||||||
|
NotDeft commands that are usable from outside =notdeft-mode= might be bound to key combinations for convenient access. To facilitate this, NotDeft provides a =notdeft-global= feature, which exports a keymap for such commands. That keymap can be bound to a prefix key. For example:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(require 'notdeft-global)
|
||||||
|
(global-set-key [f6] 'notdeft-global-map)
|
||||||
|
#+END_SRC
|
||||||
|
after which the command =[f6] o= should invoke the =notdeft-open-query= command in any mode that does not override the binding for F6 with something else.
|
||||||
|
|
||||||
|
** Access from NotDeft Note Buffers
|
||||||
|
|
||||||
|
Some of NotDeft's commands have specific support for use from within NotDeft note buffers. For example, the =notdeft-rename-file= command can be useful for renaming a note file that was perhaps created without a proper name (e.g., by using =C-c C-n=). Having written a note in a current buffer, issue the command
|
||||||
|
: M-x notdeft-rename-file
|
||||||
|
to enter a new basename for the file of that buffer. Any =C-u= prefix causes the default value to be derived from the title of the note, as extracted from the buffer contents. (The same command also works in a =*NotDeft*= buffer, affecting the currently selected file.)
|
||||||
|
|
||||||
|
** Programmatic NotDeft Access
|
||||||
|
|
||||||
|
You might also implement additional commands in terms of the globally accessible commands and Emacs Lisp functions, for example for quickly listing documents tagged in a certain way:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun my-open-todo-notes ()
|
||||||
|
(interactive)
|
||||||
|
(notdeft-open-query "tag:todo"))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
An intended use case for NotDeft is to support other applications that wish to locate files in terms of search queries instead of path names. For example, suppose we are using an =org-contacts= command to look for contacts by =name=, and that command expects the =org-contacts-files= list to be set. In that scenario we might set that variable for it based on a suitable NotDeft search query:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(setq org-contacts-files
|
||||||
|
(notdeft-list-files-by-query
|
||||||
|
"!all ext:Org AND Email"))
|
||||||
|
(org-contacts name)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Similarly, we might use =org-agenda='s =org-todo-list= command to list to-do entries, but resolving the =org-agenda-files= list on demand by looking for the “TODO” and “DONE” keywords in any Org files in our collection:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(setq org-agenda-files
|
||||||
|
(notdeft-list-files-by-query
|
||||||
|
"!all ext:Org AND (Todo OR Done)"))
|
||||||
|
(org-todo-list)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
* NotDeft Note Syntax
|
||||||
|
|
||||||
|
NotDeft does not have much of a note syntax, although a subset of Org's syntax is supported in the form of [[https://orgmode.org/manual/In_002dbuffer-settings.html][in-buffer settings]]. The supported Org keywords are
|
||||||
|
- =#+TITLE=
|
||||||
|
- =#+FILETAGS=
|
||||||
|
|
||||||
|
A NotDeft-specific keyword is
|
||||||
|
- =#+KEYWORDS=
|
||||||
|
which is intended for tagging notes with keywords, in a way that does not set any tags for Org.
|
||||||
|
|
||||||
|
As for Org, the keyword names are case insensitive, so that one can write =#+title= instead of =#+TITLE=.
|
||||||
|
|
||||||
|
You can have in-buffer settings even if you do not use Org for your notes---the syntax for in-buffer settings is the same regardless of the markup language used in notes. Even in a plain “.txt” file, you can still specify =#+KEYWORDS=, for example.
|
||||||
|
|
||||||
|
** Example Notes
|
||||||
|
|
||||||
|
No special markup is necessarily required:
|
||||||
|
#+BEGIN_SRC org
|
||||||
|
this is a title
|
||||||
|
|
||||||
|
This is body text.
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Comments can be included, and they are ignored when searching:
|
||||||
|
#+BEGIN_SRC org
|
||||||
|
# this is a comment
|
||||||
|
this is a title
|
||||||
|
|
||||||
|
This is body text.
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Org mode's =#+TITLE= syntax is supported:
|
||||||
|
#+BEGIN_SRC org
|
||||||
|
# this is a comment
|
||||||
|
,#+TITLE: this is a title
|
||||||
|
# this is a comment
|
||||||
|
|
||||||
|
This is body text.
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
A note can be tagged, e.g., with the tags “some” and “tags”:
|
||||||
|
#+BEGIN_SRC org
|
||||||
|
,#+TITLE: this is a title
|
||||||
|
,#+KEYWORDS: some tags
|
||||||
|
|
||||||
|
This is body text.
|
||||||
|
#+END_SRC
|
||||||
|
Instead of the =#+KEYWORDS= syntax, we can use the Org standard =#+FILETAGS= syntax:
|
||||||
|
#+BEGIN_SRC org
|
||||||
|
,#+FILETAGS: :some:tags:
|
||||||
|
this is a title
|
||||||
|
|
||||||
|
This is body text.
|
||||||
|
#+END_SRC
|
||||||
|
Stemming is used also on tags, and so the query “tag:tag” will find these two notes (assuming English stemming---see =notdeft-xapian-language=).
|
||||||
|
|
||||||
|
Whitespace is considered as a separator for tags, as are the delimiters “:”, “;”, and “,”. This means that the keyword declaration
|
||||||
|
#+BEGIN_SRC org
|
||||||
|
,#+KEYWORDS: helsinki-vantaa places
|
||||||
|
#+END_SRC
|
||||||
|
is not matched by the search phrase “tag:"vantaa places"”. However, a hyphen still separates words, so that “tag:helsinki” and “tag:vantaa” and “tag:helsinki-vantaa” all match the first tag, which is semantically appropriate at least in this case.
|
||||||
|
|
||||||
|
* Search Query Syntax
|
||||||
|
|
||||||
|
The usual Xapian search [[https://xapian.org/docs/queryparser.html][query syntax]] is available for NotDeft queries, with some additional /query modifiers/ (see below). Operators such as =AND=, =OR=, and =XOR= are available, and they may also be written in lowercase (or mixed case) if =notdeft-xapian-boolean-any-case= is set to =t=. The =NOT= operator is also available if =notdeft-xapian-pure-not= is =t=. It is possible to query for a phrase by quoting the phrase (e.g., "Deft for Emacs"). To look for a search term without stemming, give it capitalized (e.g., "Abstract" will not match “abstraction”). Wildcards in search terms are not supported (trailing wildcards /are/ supported by Xapian, but not enabled in NotDeft).
|
||||||
|
|
||||||
|
** Prefixes
|
||||||
|
|
||||||
|
The following prefixes are supported by NotDeft:
|
||||||
|
- =file:= :: Indicates that the search term must appear in the (non-directory, non-extension) filename.
|
||||||
|
- =ext:= :: Indicates the string that must be the filename extension of the file (without the ".").
|
||||||
|
- =path:= :: Indicates that the search term must appear in the non-directory part of the file pathname, where the pathname is relative to the user's home directory.
|
||||||
|
- =title:= :: Indicates that the search term must appear in the title.
|
||||||
|
- Title is specified either as the first non-empty non-comment line, or as the file property (or Org mode “in-buffer setting”) =#+TITLE=. (Multiple =#+TITLE= lines are not supported.)
|
||||||
|
- =tag:= :: Indicates that the search term must appear among the tags given to the document.
|
||||||
|
- The tags for a note are specified either with the standard Org file property =#+FILETAGS=, or the custom file property =#+KEYWORDS=. (Org headline tags do not qualify.)
|
||||||
|
|
||||||
|
** Query Modifiers
|
||||||
|
|
||||||
|
The following custom query syntax is supported:
|
||||||
|
- =!time= :: Prefix a query with =!time= to have the results sorted by decreasing file modification time, even if the =notdeft-xapian-order-by-time= configuration option is disabled.
|
||||||
|
- =!rank= :: Prefix a query with =!rank= to have the results sorted by decreasing relevance, regardless of the =notdeft-xapian-order-by-time= setting.
|
||||||
|
- =!file= :: Prefix a query with =!file= to have results sorted by (non-directory) file name, alphabetically, in decreasing order. Overrides all of the other sorting settings and modifiers.
|
||||||
|
- =!all= :: Prefix a query with =!all= to show /all/ matching results. Note that unless you specify this modifier, the contents of a query result list may differ depending on how the results are sorted, since less highly ranked notes may get excluded.
|
||||||
|
|
||||||
|
A space character must be used to separate the above keywords from the rest of the query string.
|
||||||
|
|
||||||
|
The =!file= modifier might be useful for instance when you have file names such as “2017-01-01-0001.txt” and “2017-09-19-0123.txt”, and you would like to see them in chronological order by “creation time”, even if some of the files have been edited, and consequently have had their modification times changed.
|
||||||
|
|
||||||
|
** Example Search Queries
|
||||||
|
|
||||||
|
It is simple to find all notes containing both the words Emacs and Org:
|
||||||
|
: Emacs AND Org
|
||||||
|
|
||||||
|
If you have a lot of notes about Org mode, and few about other Emacs matters, it may be interesting to use
|
||||||
|
: Emacs AND NOT Org
|
||||||
|
which works if the =notdeft-xapian-pure-not= option is set.
|
||||||
|
|
||||||
|
While you're often likely to be more interested in recent (or best maintained) notes, sorting by relevance can be useful particularly when there are multiple search terms: you may be more interested in seeing notes that contain /all/ the terms instead of just /one/ of them. You may use “!rank” to enable relevance-based ranking for a specific query:
|
||||||
|
: !rank Emacs Org Deft
|
||||||
|
|
||||||
|
If, on the other hand, you use a single, common search term, and have a lot of documents, you may run into your =notdeft-xapian-max-results= limit, and miss out on some documents. In this case, you might use
|
||||||
|
: !all Emacs
|
||||||
|
to list /all/ documents mentioning Emacs.
|
||||||
|
|
||||||
|
If, unlike in the above case, you just want to see all documents that are about Emacs specifically, you may get more useful results with the query
|
||||||
|
: title:Emacs
|
||||||
|
to only find documents whose title indicates that they concern Emacs. Or, to be more thorough, you might want to make sure you also find notes with the word Emacs in the filename:
|
||||||
|
: title:Emacs OR file:Emacs
|
||||||
|
|
||||||
|
You can combine prefixes and “bracketed subexpressions”:
|
||||||
|
: title:(Ayn AND Rand)
|
||||||
|
which will match both “Ayn Rand” and “Rand, Ayn” in a title.
|
||||||
|
|
||||||
|
Phrase searches are allowed for tags, and
|
||||||
|
: tag:helsinki-vantaa
|
||||||
|
: tag:"helsinki vantaa"
|
||||||
|
: tag:(helsinki AND vantaa)
|
||||||
|
all match the tag “helsinki-vantaa”.
|
||||||
|
|
||||||
|
Filename extensions can be capitalized to avoid any stemming. For example, to find all “.org” documents that may contain open to-do entries, we might query with
|
||||||
|
: !all ext:Org AND TODO
|
||||||
|
|
||||||
|
* Command Popup Buffers
|
||||||
|
|
||||||
|
If it seems hard to remember the various NotDeft commands, one may wish to have a command selection dialog, similar to the one in [[https://magit.vc/][Magit]]. For implementing such “helpful key bindings,” one can use [[https://magit.vc/manual/magit-popup.html][Magit-Popup]] or [[https://github.com/abo-abo/hydra][Hydra]], for instance. As an example, the “extras” directory of NotDeft's source repository contains a predefined hydra for NotDeft's mode-agnostic commands, provided by the =notdeft-global-hydra= feature. To bind =[f6]= to the hydra (instead of the =notdeft-global-map= keymap directly), one can use the configuration code
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(autoload 'notdeft-global-hydra/body "notdeft-global-hydra" nil t)
|
||||||
|
(global-set-key [f6] 'notdeft-global-hydra/body)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
There is also an optional hydra for =notdeft-mode=, which can be made available with code such as
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(autoload 'notdeft-mode-hydra/body "notdeft-mode-hydra")
|
||||||
|
(eval-after-load "notdeft"
|
||||||
|
'(define-key notdeft-mode-map (kbd "C-c h") 'notdeft-mode-hydra/body))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+CAPTION: A NotDeft command “hydra” invoked from Org mode.
|
||||||
|
[[./global-hydra.png]]
|
||||||
|
|
||||||
|
* Org Mode Integration
|
||||||
|
|
||||||
|
NotDeft is somewhat specialized for managing notes in the Org format. If you do use Org mode for editing your notes, there are some Org-specific NotDeft commands available (for autoloading) in the =notdeft-org= feature.
|
||||||
|
|
||||||
|
Additionally, depending on your Org version, you may want to
|
||||||
|
: (require 'notdeft-org8)
|
||||||
|
or
|
||||||
|
: (require 'notdeft-org9)
|
||||||
|
in your Org startup code, to set up support for “deft:” and “notdeft:” links in =org-mode=. A “deft:” link names a note by its non-directory filename, whereas a “notdeft:” link contains a NotDeft Xapian search expression.
|
||||||
|
|
||||||
|
Org mode's =org-store-link= command may be used to capture any Xapian search in a NotDeft buffer, to be later inserted with =org-insert-link=. The =notdeft-org= feature also defines NotDeft-specific =notdeft-org-link-existing-note= and =notdeft-org-link-new-file= commands for inserting “deft:” links, either to an existing note or a new one.
|
||||||
|
|
||||||
|
The =notdeft-org= feature also defines a =notdeft-org-store-deft-link= command, which functions similarly to =org-store-link=, but stores a "deft:" link to the current note. In a NotDeft buffer, it stores a link to any selected note; and in a NotDeft note buffer, it stores a link to that buffer's note.
|
||||||
|
|
||||||
|
NotDeft allows a "deft:" link to also include a search option, which follows the filename, separated by =::=. Search options are specified in the same way as for "file:" links. For example:
|
||||||
|
: [[deft:notdeft-homepage.org::*Note Archival]]
|
||||||
|
: [[deft:notdeft-homepage.org::#capture-protocol]]
|
||||||
|
|
||||||
|
NotDeft has some optional support for the Org [[https://orgmode.org/manual/Property-Syntax.html][property syntax]], which can be enabled by setting the variable =notdeft-allow-org-property-drawers= to a non-nil value. Enabling that option makes it so that any top-level =PROPERTIES= [[https://orgmode.org/manual/Drawers.html][drawer]] appearing at the beginning of a note is treated as a comment. The title of the note
|
||||||
|
#+BEGIN_SRC org
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: my-custom-id
|
||||||
|
:END:
|
||||||
|
Note title
|
||||||
|
Note body.
|
||||||
|
#+END_SRC
|
||||||
|
can be either ":PROPERTIES:" or "Note title", depending on whether NotDeft is configured to recognize =PROPERTIES= drawers.
|
||||||
|
|
||||||
|
** Using NotDeft and Org Mode as a Desktop Wiki Engine
|
||||||
|
|
||||||
|
It is “deft:” links in particular that allow NotDeft to be used as a desktop wiki, linking documents by topic, where a topic is named by the non-directory name of a note file. For “deft:” links to consistently resolve to the same note, you should name your note files uniquely.
|
||||||
|
|
||||||
|
For example, when following the link
|
||||||
|
: [[deft:notdeft.org]]
|
||||||
|
NotDeft will look for a “notdeft.org” file anywhere in the note collection, and open the first match.
|
||||||
|
|
||||||
|
A benefit of that “deft:” link semantics is that using the command
|
||||||
|
: M-x notdeft-move-file
|
||||||
|
to move a note file into a different directory does not cause any “deft:” link to break, whereas regular “file:” links may break.
|
||||||
|
|
||||||
|
To conveniently create a dedicated note for a given topic in an Org-mode buffer, and also link to that note at the same time, highlight the title (and link description) of that topic so that it becomes the active region, and then issue the command
|
||||||
|
: M-x notdeft-link-new-file
|
||||||
|
For example, if you've highlighted the text “desktop wikis”, the command will offer to create a note of the same title, derive a filename for it based on the title, and replace the region with a “deft:” link to it. (The command is defined by the =notdeft-org= feature.)
|
||||||
|
|
||||||
|
* Quick Note Capture
|
||||||
|
|
||||||
|
To quickly create a new note file from any buffer, you can use
|
||||||
|
: M-x notdeft-new-file
|
||||||
|
That command is also bound to =C-n= in =notdeft-global-map=, and if that keymap is bound to the prefix =[f6]=, for example, then you can create a new note with the key combination =[f6] C-n=.
|
||||||
|
|
||||||
|
Org mode has its own “capture” mechanism, and you can certainly configure capturing into a file that resides in a NotDeft directory. For example:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(setq org-directory "~/notes") ;; default Org files location
|
||||||
|
(setq notdeft-directories (list org-directory)) ;; NotDeft search path
|
||||||
|
(setq org-default-notes-file (concat org-directory "/notes.org"))
|
||||||
|
(global-set-key [f7] 'org-capture)
|
||||||
|
#+END_SRC
|
||||||
|
which defines "~/notes" as the sole NotDeft directory, and has the key F7 initiate an =org-capture=, by default into the file "~/notes/notes.org". After completing capture, you can go back to the previously captured item with
|
||||||
|
: C-u C-u M-x org-capture
|
||||||
|
The capture facility supports the definition and use of =org-capture-templates= for different purposes.
|
||||||
|
|
||||||
|
A caveat with Org capturing is that unless you have already opened the capture file under NotDeft, any newly captured items may not immediately get noticed by NotDeft. To ensure that NotDeft is aware of any changes, one might arrange for the capture file to include file variables for enabling the =notdeft-note-mode= minor mode for any buffers opened for that file. Setting directory local variables are another option.
|
||||||
|
|
||||||
|
A more involved option is to write custom commands which enable the minor mode for the capture file, for example with
|
||||||
|
: (notdeft-register-file org-default-notes-file)
|
||||||
|
Note that different =org-capture-templates= may define different capture locations. Consequently, it may be appropriate for the templates themselves to embed code for performing the registration (e.g., as shown in the [[*=capture= from Firefox][=capture= from Firefox]] section).
|
||||||
|
|
||||||
|
* Adding Attachments to Notes
|
||||||
|
|
||||||
|
NotDeft has a simple mechanism to support “attaching” files to notes, one that is agnostic to the note file format. If you have a note file
|
||||||
|
: ~/notes/deft-for-emacs.txt
|
||||||
|
you can use the command =C-c S= to move the file into a subdirectory of the same name, so that the file's pathname becomes
|
||||||
|
: ~/notes/deft-for-emacs/deft-for-emacs.txt
|
||||||
|
Now you can copy/move/link any attachments for the note into that subdirectory, and it is convenient to move the note together with its attachments using a regular file manager.
|
||||||
|
|
||||||
|
To move a note from within =*NotDeft*=, the command =C-u C-c m= can be used to move it under another NotDeft root directory, where the prefix =C-u= assures NotDeft that the file really is to be moved together with its subdirectory.
|
||||||
|
|
||||||
|
When the attachments reside in the same directory as the note itself, in Org mode it is then easy to add a “file:” link to any attachment with the command =C-u C-c C-l=. For example, if the attachment directory contains a file named “2017-01-01-0001.JPG”, then a “file:” link to it would be simply
|
||||||
|
: [[file:2017-01-01-0001.JPG]]
|
||||||
|
and the command =C-c C-x C-v= can be used to toggle inline display of images.
|
||||||
|
|
||||||
|
Org itself has its own attachment management mechanism, whose action menu is bound to =C-c C-a=. This mechanism allows an attachment directory to be associated with an Org heading (as identified by information stored within the heading's properties), and thus the NotDeft note file itself can reside directly within a NotDeft root directory. Org has no command for moving an Org file together with its attachments, however.
|
||||||
|
|
||||||
|
To make the Org mechanism compatible with the NotDeft mechanism, one can store the attachments in the same (sub)directory as the note file itself, by specifying that directory with the =ATTACH_DIR= property. For example:
|
||||||
|
#+BEGIN_SRC org
|
||||||
|
,* Bergen, Norway :ATTACH:
|
||||||
|
:PROPERTIES:
|
||||||
|
:ATTACH_DIR: ./
|
||||||
|
:Attachments: 2017-01-01-0001.JPG 2017-09-19-0123.JPG
|
||||||
|
:END:
|
||||||
|
#+END_SRC
|
||||||
|
This way it is still convenient to move a note together with its attachments, and Org commands such as =C-c C-a o= (for opening the attachments) can still be used.
|
||||||
|
|
||||||
|
* Note Archival
|
||||||
|
|
||||||
|
To archive away a note so that its contents will no longer be included in a search, one can press =C-c C-a= from within =*NotDeft*=. This is a note format agnostic archival method, as the entire note file gets moved into a =notdeft-archive-directory=, with the default name of
|
||||||
|
: "_archive"
|
||||||
|
meaning that a note file whose original path is
|
||||||
|
: ~/notes/deft-for-emacs.txt
|
||||||
|
would get moved to
|
||||||
|
: ~/notes/_archive/deft-for-emacs.txt
|
||||||
|
Any directories whose names begin with an underscore will be excluded from Xapian searches, and thus such an archived note will no longer clutter search results.
|
||||||
|
|
||||||
|
In Org mode one can use Org's own [[http://orgmode.org/manual/Archiving.html][archival mechanism]] to archive just a part of a note document subtree, and the archival file will also be excluded from Xapian searches, provided that its filename extension is not =notdeft-extension= or one of the =notdeft-secondary-extensions=. Org's default extension is
|
||||||
|
: org_archive
|
||||||
|
which by default is not an extension recognized by NotDeft.
|
||||||
|
|
||||||
|
* Capturing Data from External Applications
|
||||||
|
|
||||||
|
The =org-protocol= feature of Org mode provides a way for external applications to interface with Emacs and Org, and that mechanism can also be adopted for capturing data into NotDeft. For example, data can be sent from Firefox to NotDeft using the predefined =store-link= and =capture= protocols.
|
||||||
|
|
||||||
|
The mechanism works by the external application invoking =emacsclient=, and for this to work you should have an Emacs server running in the Emacs instance you want to use to receive data into NotDeft. A server can be started by evaluating
|
||||||
|
: (server-start)
|
||||||
|
|
||||||
|
** =org-protocol= Content Type in Firefox
|
||||||
|
|
||||||
|
To configure Firefox to support the =org-protocol:= scheme, first open =about:config=, and add a =boolean= property
|
||||||
|
: network.protocol-handler.expose.org-protocol false
|
||||||
|
|
||||||
|
Then craft an HTML file such as
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<a href="org-protocol://store-link?url=URL&title=TITLE">link</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
#+END_SRC
|
||||||
|
and open that file in Firefox, and click the link, after which a “Launch Application” dialog is presented. “Choose other Application”, tick the box “Remember my choice for org-protocol links”, and specify =emacsclient= as the executable.
|
||||||
|
|
||||||
|
That application selection can later be modified from Firefox “Preferences” / “Applications”. If required, the “Content Type” should be removable at least by editing the “mimeTypes.rdf” file in the Firefox profile.
|
||||||
|
|
||||||
|
** =store-link= from Firefox
|
||||||
|
|
||||||
|
There is nothing NotDeft specific about the =store-link= Org protocol, as it merely stores a link to the Emacs =kill-ring= for yanking. To configure Firefox to support the protocol, just add a suitable bookmarklet (e.g., to the “Bookmarks Toolbar”). The bookmark “Location” can be specified as
|
||||||
|
#+BEGIN_SRC javascript
|
||||||
|
javascript:location.href='org-protocol://store-link?url='+encodeURIComponent(document.location)+'&title='+encodeURIComponent(document.title);void(0);
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
By selecting that bookmark a link to the current page can be sent to Emacs. Its URL can then be inserted in Emacs with =C-y=. A full Org link in turn can be inserted with
|
||||||
|
: M-x org-insert-link
|
||||||
|
which is bound to =C-c C-l= in Org.
|
||||||
|
|
||||||
|
** =capture= from Firefox
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: capture-protocol
|
||||||
|
:END:
|
||||||
|
|
||||||
|
The =capture= protocol, in turn, allows for web page content and metadata to be captured from Firefox into Emacs. Configuring the =capture= protocol for use with NotDeft is slightly more involved than in the case of =store-link=, as we must choose what page data to store, and where in our NotDeft note collection to store it.
|
||||||
|
|
||||||
|
Suppose we wish to store any currently selected text, along with the URL of the containing page, and a capture timestamp. Suppose also that we wish to store it into a file whose name is derived from the page title, so that if we capture multiple times from the same page, then all of the captured text snippets will end up in the same note file.
|
||||||
|
|
||||||
|
In that case the Firefox bookmarklet for sending over the required information can for example be
|
||||||
|
#+BEGIN_SRC javascript
|
||||||
|
javascript:location.href='org-protocol://capture?template=w&url='+encodeURIComponent(document.location)+'&title='+encodeURIComponent(document.title)+'&body='+encodeURIComponent(window.getSelection());void(0);
|
||||||
|
#+END_SRC
|
||||||
|
where we have given the name “w” for the Org capture template.
|
||||||
|
|
||||||
|
We must also define that template as one of our =org-capture-templates=, and the definition can be
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(require 'org-protocol)
|
||||||
|
|
||||||
|
(setq org-capture-templates
|
||||||
|
'(("w" "capture selection into NotDeft" plain
|
||||||
|
(file (lambda ()
|
||||||
|
(notdeft-switch-to-file-named
|
||||||
|
(plist-get org-store-link-plist :description))))
|
||||||
|
"%l\non %u\n\n%i"
|
||||||
|
:empty-lines-before 1)))
|
||||||
|
#+END_SRC
|
||||||
|
This definition assumes that the link =:description= is available from =org-store-link-plist=, and that it corresponds to the =document.title=; this may be undocumented functionality, but works in Org mode 9.1.1. The =notdeft-switch-to-file-named= derives a filename from the description, creates that file if it doesn't yet exist, and returns the complete =file= name.
|
||||||
|
|
||||||
|
* Troubleshooting
|
||||||
|
** When Search Queries Are Not Yielding Expected Results
|
||||||
|
|
||||||
|
Try doing the following in a =*NotDeft*= buffer:
|
||||||
|
1. Press =TAB= (or =M-x notdeft-query-edit=) to be prompted for a Xapian query.
|
||||||
|
2. If nothing happens when you press =TAB=, then you have probably not configured a value for =notdeft-xapian-program=. Assign a value to that variable.
|
||||||
|
3. Having pressed =TAB=, enter a query string at the prompt, one that should match some notes, and press =RET=.
|
||||||
|
4. If that reports "Found no notes", or an unexpected set of notes, then your search index may not be up-to-date, perhaps due to filesystem changes outside of NotDeft. Invoke the command =M-x notdeft-refresh= (i.e., =C-c C-x g=) to refresh the search index.
|
||||||
|
5. If you suspect that your search index may be corrupt or incompatible in some way, you may invoke the command =M-x notdeft-reindex= (i.e., =C-c C-x r=) to fully rebuild the search index, instead of just refreshing it.
|
||||||
|
6. If you see unexpected behavior after setting a search query, ensure that the =notdeft-xapian-program= variable names the complete and fully expanded path of a working executable. It may be worth trying to run the program directly, and seeing what it says. For example:
|
||||||
|
: /path/to/notdeft-xapian search -q 'Emacs OR Vi' ~/.deft
|
||||||
|
7. If your search query includes prefix terms such as “title:Emacs”, and you do not get all the expected matches, then make sure that any lines before any =#+TITLE= (or, =#+KEYWORDS=, etc.) are either whitespace only or begin with “#”. While the Org markup language allows in-buffer settings to appear anywhere in a file, NotDeft only scans the beginning of each file for such settings.
|
||||||
|
8. If all else fails, a tool such as =xapian-delve= may be used to inspect the contents of the search index to see which terms it actually contains.
|
||||||
|
|
||||||
|
* See Also
|
||||||
|
|
||||||
|
#+BEGIN_EXPORT html
|
||||||
|
{{< taggedpagelistexceptself "notdeft" >}}
|
||||||
|
#+END_EXPORT
|
339
org/notdeft/xapian/GPL-2
Normal file
339
org/notdeft/xapian/GPL-2
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
16
org/notdeft/xapian/Makefile
Normal file
16
org/notdeft/xapian/Makefile
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
PROGRAM := notdeft-xapian
|
||||||
|
|
||||||
|
default : build
|
||||||
|
|
||||||
|
-include local.mk
|
||||||
|
|
||||||
|
build : $(PROGRAM)
|
||||||
|
|
||||||
|
$(PROGRAM) : notdeft-xapian.cc
|
||||||
|
c++ -o $@ $< -std=c++11 -Wall `pkg-config --cflags --libs tclap` `xapian-config --cxxflags --libs`
|
||||||
|
|
||||||
|
clean :
|
||||||
|
-rm $(PROGRAM)
|
||||||
|
|
||||||
|
install-deps :
|
||||||
|
sudo aptitude install pkg-config libtclap-dev libxapian-dev
|
805
org/notdeft/xapian/notdeft-xapian.cc
Normal file
805
org/notdeft/xapian/notdeft-xapian.cc
Normal file
|
@ -0,0 +1,805 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <tclap/CmdLine.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <xapian.h>
|
||||||
|
|
||||||
|
#define __STDC_FORMAT_MACROS
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#if !defined(XAPIAN_AT_LEAST)
|
||||||
|
#define XAPIAN_AT_LEAST(x,y,z) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if XAPIAN_AT_LEAST(1,3,4) || XAPIAN_AT_LEAST(1,2,2) && !XAPIAN_AT_LEAST(1,3,0)
|
||||||
|
#define TG_CJK (Xapian::TermGenerator::FLAG_CJK_NGRAM)
|
||||||
|
#define QP_CJK (Xapian::QueryParser::FLAG_CJK_NGRAM)
|
||||||
|
#else
|
||||||
|
#define TG_CJK ((Xapian::TermGenerator::flags)0)
|
||||||
|
#define QP_CJK (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace NotDeft {
|
||||||
|
struct ReadError {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Serializes in a sorting friendly way, similarly to
|
||||||
|
`Xapian::sortable_serialise`. Should be quite portable when the
|
||||||
|
argument is coerced from `time_t`, although C does not actually
|
||||||
|
even guarantee an integer type in that case. */
|
||||||
|
static string time_serialize(const int64_t v) {
|
||||||
|
char buf[16+1];
|
||||||
|
// format in hexadecimal, zero padded, 64/4 digits
|
||||||
|
if (snprintf(buf, sizeof buf, "%016" PRIx64, v) != 16) {
|
||||||
|
// POSIX requires `errno` to be set, but C does not
|
||||||
|
throw Xapian::AssertionError("unexpected snprintf failure", errno);
|
||||||
|
}
|
||||||
|
return string(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The inverse of `time_serialize`. */
|
||||||
|
static int64_t time_deserialize(const string& s) {
|
||||||
|
int64_t v;
|
||||||
|
if (sscanf(s.c_str(), "%" SCNx64, &v) != 1) {
|
||||||
|
throw Xapian::InvalidArgumentError("bad time_deserialize arg", errno);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the length of any note header marker such as "#" or "%#"
|
||||||
|
* or "@;#". If the string is not a header string, returns 0. */
|
||||||
|
static size_t string_header_marker_len(const string& s) {
|
||||||
|
const size_t len = s.length();
|
||||||
|
if (len >= 1) {
|
||||||
|
if (s[0] == '#')
|
||||||
|
return 1;
|
||||||
|
if (len >= 2) {
|
||||||
|
if ((s[1] == '#') && (s[0] == '%'))
|
||||||
|
return 2;
|
||||||
|
if (len >= 3) {
|
||||||
|
if ((s[2] == '#') && (s[0] == '@') && (s[1] == ';'))
|
||||||
|
return 3;
|
||||||
|
if (len >= 5) {
|
||||||
|
if ((s[4] == '#') && (s[0] == '<') && (s[1] == '!') &&
|
||||||
|
(s[2] == '-') && (s[3] == '-'))
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool line_skip_marker(const string& s, size_t& pos) {
|
||||||
|
const size_t len = string_header_marker_len(s);
|
||||||
|
if (len == 0)
|
||||||
|
return false;
|
||||||
|
pos = len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the lowercased string 's' matches 'pfx' starting at
|
||||||
|
* position 'pos'. If so, increment 'pos' to index the position after
|
||||||
|
* 'pfx'. */
|
||||||
|
static bool string_lc_skip_keyword(const string& s,
|
||||||
|
size_t& pos,
|
||||||
|
const string& pfx) {
|
||||||
|
auto pfx_len = pfx.length();
|
||||||
|
auto epos = pos + pfx_len;
|
||||||
|
if (s.length() < epos)
|
||||||
|
return false;
|
||||||
|
for (size_t i = 0; i < pfx_len; ++i) {
|
||||||
|
if (tolower(s[pos + i]) != pfx[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pos += pfx_len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool string_ends_with(const string& s, const string& sfx) {
|
||||||
|
const int pos = s.length() - sfx.length();
|
||||||
|
return (pos >= 0) && (s.compare(pos, sfx.length(), sfx) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool string_ends_with_one_of(const string& s,
|
||||||
|
const vector<string>& sfxs) {
|
||||||
|
for (const string& sfx : sfxs) {
|
||||||
|
if (string_ends_with(s, sfx)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool drop_substring(string& s, const string& sub) {
|
||||||
|
auto found = s.rfind(sub);
|
||||||
|
if (found == string::npos)
|
||||||
|
return false;
|
||||||
|
s.replace(found, sub.length(), "");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool whitespace_p(const string& s) {
|
||||||
|
for (auto p = s.c_str(); *p; p++)
|
||||||
|
if (!isspace(*p))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool org_drawer_line_p(const string& s,
|
||||||
|
const char* kw = nullptr,
|
||||||
|
bool req_ws = false) {
|
||||||
|
auto p = s.c_str();
|
||||||
|
while (isblank(*p)) p++;
|
||||||
|
if (*p++ != ':') return false;
|
||||||
|
|
||||||
|
if (kw) {
|
||||||
|
/* Skip specified keyword, e.g., "END". */
|
||||||
|
auto len = strlen(kw);
|
||||||
|
if (strncmp(p, kw, len) != 0)
|
||||||
|
return false;
|
||||||
|
p += len;
|
||||||
|
} else {
|
||||||
|
/* Require a property name of at least one non-whitespace. */
|
||||||
|
if (!(*p && *p != ':' && !isspace(*p)))
|
||||||
|
return false;
|
||||||
|
p++;
|
||||||
|
while (*p && *p != ':' && !isspace(*p))
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*p != ':') return false;
|
||||||
|
|
||||||
|
if (req_ws) {
|
||||||
|
while (*++p)
|
||||||
|
if (!isspace(*p))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string downcase(const string& s) {
|
||||||
|
string data;
|
||||||
|
data.resize(s.length());
|
||||||
|
std::transform(s.begin(), s.end(), data.begin(), ::tolower);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool file_directory_p(const string& file) {
|
||||||
|
struct stat sb;
|
||||||
|
return (stat(file.c_str(), &sb) == 0) && S_ISDIR(sb.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns an empty list on failure. */
|
||||||
|
static vector<string> ls(const string& file) {
|
||||||
|
vector<string> lst;
|
||||||
|
DIR* dir = opendir(file.c_str());
|
||||||
|
if (dir == NULL)
|
||||||
|
return lst;
|
||||||
|
struct dirent* entry;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
string name(entry->d_name);
|
||||||
|
if (name.length() > 0
|
||||||
|
&& name[0] != '.'
|
||||||
|
&& name[0] != '_'
|
||||||
|
&& name[0] != '#'
|
||||||
|
&& name.find('/') == string::npos) {
|
||||||
|
lst.push_back(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
return lst;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string file_join(const string& x, const string& y) {
|
||||||
|
if (x == ".")
|
||||||
|
return y;
|
||||||
|
if (string_ends_with(x, "/"))
|
||||||
|
return x + y;
|
||||||
|
return x + "/" + y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the pathname of the parent directory of `s`, or return ""
|
||||||
|
if `s` has no directory components, or if `s` is "/". */
|
||||||
|
static string file_directory_path(const string& s) {
|
||||||
|
auto found = s.find_last_of('/');
|
||||||
|
if ((found == string::npos) || (found == 0))
|
||||||
|
return "";
|
||||||
|
return string(s.substr(0, found));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the non-directory component of pathname `s`, or return `s`
|
||||||
|
itself if `s` has no directory components. */
|
||||||
|
static string file_non_directory(const string& s) {
|
||||||
|
auto found = s.find_last_of('/');
|
||||||
|
if (found == string::npos)
|
||||||
|
return s;
|
||||||
|
return string(s.substr(found + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the non-directory component of `s`, with its last extension
|
||||||
|
(if any) removed. A filename that is "all extension" has no
|
||||||
|
extension. */
|
||||||
|
static string file_basename(const string& s) {
|
||||||
|
auto basename = file_non_directory(s);
|
||||||
|
size_t found = basename.find_last_of('.');
|
||||||
|
if ((found == 0) || (found == string::npos))
|
||||||
|
return basename;
|
||||||
|
return string(basename.substr(0, found));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the last filename extension of `s`, with its leading ".",
|
||||||
|
or return "" if `s` has no extension. A filename that is "all
|
||||||
|
extension" has no extension. */
|
||||||
|
static string file_extension(const string& s) {
|
||||||
|
auto basename = file_non_directory(s);
|
||||||
|
size_t found = basename.find_last_of('.');
|
||||||
|
if ((found == 0) || (found == string::npos))
|
||||||
|
return "";
|
||||||
|
return string(basename.substr(found));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ls_org(vector<string>& res, const string& root,
|
||||||
|
const string& dir, const vector<string>& exts) {
|
||||||
|
auto absDir = file_join(root, dir);
|
||||||
|
for (const string& file : ls(absDir)) {
|
||||||
|
auto relFile = file_join(dir, file);
|
||||||
|
auto absFile = file_join(absDir, file);
|
||||||
|
bool isDir = file_directory_p(absFile);
|
||||||
|
if (string_ends_with_one_of(file, exts)) {
|
||||||
|
if (!isDir)
|
||||||
|
res.push_back(relFile);
|
||||||
|
} else if (isDir) {
|
||||||
|
ls_org(res, root, relFile, exts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool uni_keyword_separator_p(const unsigned ch) {
|
||||||
|
return (ch == ':') || (ch == ';') || (ch == ',')
|
||||||
|
|| Xapian::Unicode::is_whitespace(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Expects an UTF-8 encoded line as the argument `s`,
|
||||||
|
but reverts to octets for the remaining input if
|
||||||
|
non-UTF-8 encoding is detected. */
|
||||||
|
static void uni_index_keywords(Xapian::TermGenerator& indexer,
|
||||||
|
const string& s) {
|
||||||
|
Xapian::Utf8Iterator q(s);
|
||||||
|
for (;;) {
|
||||||
|
while (q.left() && uni_keyword_separator_p(*q)) q++;
|
||||||
|
if (!q.left()) break;
|
||||||
|
const char* const p = q.raw();
|
||||||
|
while (q.left() && !uni_keyword_separator_p(*q)) q++;
|
||||||
|
const string kw(p, q.raw());
|
||||||
|
indexer.index_text(kw, 0, "K");
|
||||||
|
indexer.increase_termpos();
|
||||||
|
if (!q.left()) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Op {
|
||||||
|
bool whole_dir;
|
||||||
|
string dir;
|
||||||
|
vector<string> files;
|
||||||
|
Op() {}
|
||||||
|
explicit Op(const string& d) : whole_dir(true), dir(d) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool parse_ops(istream& in, vector<Op>& lst) {
|
||||||
|
string opcode;
|
||||||
|
while (getline(in, opcode)) {
|
||||||
|
if (opcode == ":idir") {
|
||||||
|
string dir;
|
||||||
|
if (getline(in, dir)) {
|
||||||
|
lst.push_back(Op(dir));
|
||||||
|
} else {
|
||||||
|
return false; // expected directory name
|
||||||
|
}
|
||||||
|
} else if (opcode == ":ifiles") {
|
||||||
|
string dir;
|
||||||
|
if (!getline(in, dir))
|
||||||
|
return false; // expected directory name
|
||||||
|
string count_s;
|
||||||
|
if (!getline(in, count_s))
|
||||||
|
return false; // expected file count
|
||||||
|
int count = std::stoi(count_s);
|
||||||
|
if (count < 0)
|
||||||
|
return false; // expected non-negative integer
|
||||||
|
Op op;
|
||||||
|
op.whole_dir = false;
|
||||||
|
op.dir = dir;
|
||||||
|
string file;
|
||||||
|
for ( ; count > 0; count--) {
|
||||||
|
if (!getline(in, file))
|
||||||
|
return false; // expected count filenames
|
||||||
|
op.files.push_back(file);
|
||||||
|
}
|
||||||
|
lst.push_back(op);
|
||||||
|
} else {
|
||||||
|
return false; // unknown command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usage()
|
||||||
|
{
|
||||||
|
cerr << "notdeft-xapian" << endl;
|
||||||
|
cerr << "USAGE:" << endl;
|
||||||
|
cerr << "To build/refresh search indices" << endl;
|
||||||
|
cerr << "(for specified directories):" << endl;
|
||||||
|
cerr << " notdeft-xapian index [options] directory..." << endl;
|
||||||
|
cerr << "To find text documents" << endl;
|
||||||
|
cerr << "(matching the specified query):" << endl;
|
||||||
|
cerr << " notdeft-xapian search [options] directory..." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Xapian::valueno DOC_MTIME = 0;
|
||||||
|
static constexpr Xapian::valueno DOC_FILENAME = 1;
|
||||||
|
|
||||||
|
static int doIndex(vector<string> subArgs) {
|
||||||
|
TCLAP::CmdLine cmdLine
|
||||||
|
("Specify any indexing commands via STDIN."
|
||||||
|
" For each command, specify its database index directory."
|
||||||
|
" All paths are used and stored as given."
|
||||||
|
" Search results are reported with the stored paths,"
|
||||||
|
" regardless of the search-time working directory.");
|
||||||
|
TCLAP::ValueArg<string>
|
||||||
|
langArg("l", "lang", "stemming language (e.g., 'en' or 'fi')",
|
||||||
|
false, "en", "language");
|
||||||
|
cmdLine.add(langArg);
|
||||||
|
TCLAP::MultiArg<string>
|
||||||
|
extArg("x", "extension", "filename extension (default: '.org')",
|
||||||
|
false, "extension");
|
||||||
|
cmdLine.add(extArg);
|
||||||
|
TCLAP::ValueArg<string>
|
||||||
|
chdirArg("c", "chdir", "change working directory first",
|
||||||
|
false, ".", "directory");
|
||||||
|
cmdLine.add(chdirArg);
|
||||||
|
TCLAP::SwitchArg
|
||||||
|
resetArg("r", "recreate", "recreate database", false);
|
||||||
|
cmdLine.add(resetArg);
|
||||||
|
TCLAP::ValueArg<int>
|
||||||
|
titleArg("t", "title-wdf", "title importance (default: 10)",
|
||||||
|
false, 10, "wdf_inc");
|
||||||
|
cmdLine.add(titleArg);
|
||||||
|
TCLAP::SwitchArg
|
||||||
|
verboseArg("v", "verbose", "be verbose", false);
|
||||||
|
cmdLine.add(verboseArg);
|
||||||
|
TCLAP::SwitchArg
|
||||||
|
inputArg("i", "input", "read instructions from STDIN", false);
|
||||||
|
cmdLine.add(inputArg);
|
||||||
|
TCLAP::SwitchArg
|
||||||
|
skipDrawersArg("", "allow-org-property-drawers",
|
||||||
|
"allow Org :PROPERTIES: drawers in header", false);
|
||||||
|
cmdLine.add(skipDrawersArg);
|
||||||
|
TCLAP::UnlabeledMultiArg<string>
|
||||||
|
dirsArg("directory...", "index specified dirs", false, "directory");
|
||||||
|
cmdLine.add(dirsArg);
|
||||||
|
cmdLine.parse(subArgs);
|
||||||
|
|
||||||
|
if (chdirArg.getValue() != ".") {
|
||||||
|
if (chdir(chdirArg.getValue().c_str()) == -1) {
|
||||||
|
auto e = errno;
|
||||||
|
cerr << "could not change into directory " <<
|
||||||
|
chdirArg.getValue() << " (errno: " << e << ")" << endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<string> exts = extArg.getValue();
|
||||||
|
if (exts.empty())
|
||||||
|
exts.push_back(".org");
|
||||||
|
|
||||||
|
auto verbose = verboseArg.getValue();
|
||||||
|
|
||||||
|
string lang(langArg.getValue());
|
||||||
|
bool cjk = drop_substring(lang, ":cjk");
|
||||||
|
|
||||||
|
vector<Op> opList;
|
||||||
|
{
|
||||||
|
auto dirs = dirsArg.getValue();
|
||||||
|
for (auto dir : dirs) {
|
||||||
|
opList.push_back(Op(dir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inputArg.getValue()) {
|
||||||
|
if (!parse_ops(cin, opList)) {
|
||||||
|
cerr << "option -i / --input given, "
|
||||||
|
"but failed to parse instructions from STDIN" << endl;
|
||||||
|
if (verbose) { // print out parsed instructions
|
||||||
|
cerr << "successfully parsed:" << endl;
|
||||||
|
ostream& out(cerr);
|
||||||
|
for (auto op : opList) {
|
||||||
|
out << op.dir;
|
||||||
|
if (op.whole_dir) {
|
||||||
|
out << endl << " (ALL)" << endl;
|
||||||
|
} else {
|
||||||
|
for (auto file : op.files) {
|
||||||
|
out << endl << " " << file;
|
||||||
|
}
|
||||||
|
out << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Xapian::TermGenerator indexer;
|
||||||
|
Xapian::Stem stemmer(lang);
|
||||||
|
indexer.set_stemmer(stemmer);
|
||||||
|
indexer.set_stemming_strategy(Xapian::TermGenerator::STEM_SOME);
|
||||||
|
if (cjk)
|
||||||
|
indexer.set_flags(TG_CJK);
|
||||||
|
|
||||||
|
for (auto op : opList) {
|
||||||
|
auto dir = op.dir;
|
||||||
|
|
||||||
|
struct stat sb;
|
||||||
|
// Whether a readable and writable directory.
|
||||||
|
if ((stat(dir.c_str(), &sb) == 0) && S_ISDIR(sb.st_mode) &&
|
||||||
|
(access(dir.c_str(), R_OK|W_OK) != -1)) {
|
||||||
|
if (verbose) {
|
||||||
|
cerr << "indexing directory " << dir << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
string dbFile(file_join(dir, ".notdeft-db"));
|
||||||
|
Xapian::WritableDatabase db(dbFile,
|
||||||
|
resetArg.getValue() ?
|
||||||
|
Xapian::DB_CREATE_OR_OVERWRITE :
|
||||||
|
Xapian::DB_CREATE_OR_OPEN);
|
||||||
|
|
||||||
|
map<string, int64_t> fsFiles; // mtimes for files in file system
|
||||||
|
map<string, int64_t> dbFiles; // mtimes for files in database
|
||||||
|
map<string, Xapian::docid> dbIds;
|
||||||
|
|
||||||
|
vector<string> orgFiles;
|
||||||
|
if (op.whole_dir) {
|
||||||
|
ls_org(orgFiles, dir, ".", exts);
|
||||||
|
} else {
|
||||||
|
// Sparse directory paths must be specified relative to
|
||||||
|
// their database root.
|
||||||
|
orgFiles = op.files;
|
||||||
|
}
|
||||||
|
for (const string& file : orgFiles) { // `dir` relative `file`
|
||||||
|
auto filePath = file_join(dir, file);
|
||||||
|
struct stat sb;
|
||||||
|
if (stat(filePath.c_str(), &sb) == 0) {
|
||||||
|
fsFiles[filePath] = sb.st_mtime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.begin_transaction(false);
|
||||||
|
|
||||||
|
for (Xapian::PostingIterator it = db.postlist_begin("");
|
||||||
|
it != db.postlist_end(""); ++it) {
|
||||||
|
auto docId = *it;
|
||||||
|
auto doc = db.get_document(docId);
|
||||||
|
auto filePath = doc.get_data();
|
||||||
|
auto t = time_deserialize(doc.get_value(DOC_MTIME));
|
||||||
|
// Overwrites any existing value of the same key, and thus
|
||||||
|
// there will be no dupes in `dbFiles`, even if the database
|
||||||
|
// should have some.
|
||||||
|
dbFiles[filePath] = t;
|
||||||
|
dbIds[filePath] = docId;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto makeDoc = [&] (const pair<string, int64_t>& x) {
|
||||||
|
const string& filePath = x.first;
|
||||||
|
ifstream infile(filePath);
|
||||||
|
Xapian::Document doc;
|
||||||
|
doc.set_data(filePath);
|
||||||
|
doc.add_value(DOC_MTIME, time_serialize(x.second));
|
||||||
|
const string fileNonDir = file_non_directory(filePath);
|
||||||
|
doc.add_value(DOC_FILENAME, fileNonDir);
|
||||||
|
indexer.set_document(doc);
|
||||||
|
{
|
||||||
|
const string fileDir = file_directory_path(filePath);
|
||||||
|
indexer.index_text(fileDir, 1, "P");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const string fileBase = file_basename(fileNonDir);
|
||||||
|
indexer.index_text(fileBase, 1, "F");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
/* As for Omega, lowercase, without dot, and just "E"
|
||||||
|
for the no extension case. */
|
||||||
|
string fileExt = file_extension(fileNonDir);
|
||||||
|
if (!fileExt.empty()) {
|
||||||
|
fileExt = downcase(fileExt.substr(1));
|
||||||
|
}
|
||||||
|
//doc.add_boolean_term("E" + fileExt);
|
||||||
|
indexer.index_text_without_positions(fileExt, 0, "E");
|
||||||
|
//cerr << "ext: '" << fileExt << "'" << endl;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
string line;
|
||||||
|
bool titleDone = false;
|
||||||
|
size_t pos = 0;
|
||||||
|
while (getline(infile, line)) {
|
||||||
|
if (whitespace_p(line)) {
|
||||||
|
// skip blank line
|
||||||
|
} else if (skipDrawersArg.getValue() &&
|
||||||
|
org_drawer_line_p(line, "PROPERTIES", true)) {
|
||||||
|
while (getline(infile, line)) {
|
||||||
|
// skip Org drawer
|
||||||
|
if (org_drawer_line_p(line, "END"))
|
||||||
|
break;
|
||||||
|
if (!org_drawer_line_p(line))
|
||||||
|
break; // unclosed drawer
|
||||||
|
}
|
||||||
|
} else if (!line_skip_marker(line, pos)) {
|
||||||
|
// non Org header mode
|
||||||
|
if (!titleDone) {
|
||||||
|
indexer.index_text(line, 1, "S");
|
||||||
|
indexer.index_text(line, titleArg.getValue());
|
||||||
|
indexer.increase_termpos();
|
||||||
|
} else {
|
||||||
|
indexer.index_text(line);
|
||||||
|
}
|
||||||
|
while (getline(infile, line)) {
|
||||||
|
//cerr << "body line: '" << line << "'" << endl;
|
||||||
|
indexer.index_text(line);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (string_lc_skip_keyword(line, pos, "+title:")) {
|
||||||
|
const string s = line.substr(pos);
|
||||||
|
indexer.index_text(s, 1, "S");
|
||||||
|
indexer.index_text(s, titleArg.getValue());
|
||||||
|
indexer.increase_termpos();
|
||||||
|
titleDone = true;
|
||||||
|
} else if (string_lc_skip_keyword(line, pos, "+keywords:") ||
|
||||||
|
string_lc_skip_keyword(line, pos, "+filetags:")) {
|
||||||
|
const string s = line.substr(pos);
|
||||||
|
uni_index_keywords(indexer, s);
|
||||||
|
indexer.index_text(s);
|
||||||
|
indexer.increase_termpos();
|
||||||
|
} else {
|
||||||
|
// skip comment (or unknown property) line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!infile.eof())
|
||||||
|
throw NotDeft::ReadError();
|
||||||
|
return doc;
|
||||||
|
}; // end makeDoc
|
||||||
|
|
||||||
|
auto addFile = [&] (const pair<string, int64_t>& x) {
|
||||||
|
if (verbose)
|
||||||
|
cerr << "indexing file " << x.first << endl;
|
||||||
|
try {
|
||||||
|
Xapian::Document doc = makeDoc(x);
|
||||||
|
db.add_document(doc);
|
||||||
|
} catch (const NotDeft::ReadError& e) {
|
||||||
|
// File not (fully) readable, so don't index.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto updateFile = [&] (const pair<string, int64_t>& x,
|
||||||
|
Xapian::docid docId) {
|
||||||
|
if (verbose)
|
||||||
|
cerr << "re-indexing file " << x.first << endl;
|
||||||
|
try {
|
||||||
|
Xapian::Document doc = makeDoc(x);
|
||||||
|
db.replace_document(docId, doc);
|
||||||
|
} catch (const NotDeft::ReadError& e) {
|
||||||
|
// File no longer (fully) readable, so remove from index.
|
||||||
|
db.delete_document(docId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto rmFile = [&] (const pair<string, int64_t>& x) {
|
||||||
|
if (verbose)
|
||||||
|
cerr << "de-indexing file " << x.first << endl;
|
||||||
|
auto docId = dbIds[x.first];
|
||||||
|
db.delete_document(docId);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto fi = fsFiles.cbegin();
|
||||||
|
auto di = dbFiles.cbegin();
|
||||||
|
for (;;) {
|
||||||
|
if (fi == fsFiles.cend()) {
|
||||||
|
// The remaining files have been deleted.
|
||||||
|
for ( ; di != dbFiles.cend(); ++di) {
|
||||||
|
rmFile(*di);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (di == dbFiles.cend()) {
|
||||||
|
// The remaining files are new.
|
||||||
|
for ( ; fi != fsFiles.cend(); ++fi) {
|
||||||
|
addFile(*fi);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if ((*fi).first == (*di).first) {
|
||||||
|
if ((*fi).second != (*di).second) {
|
||||||
|
// The file has been modified.
|
||||||
|
updateFile(*fi, dbIds[(*di).first]);
|
||||||
|
}
|
||||||
|
fi++;
|
||||||
|
di++;
|
||||||
|
} else if ((*fi).first < (*di).first) {
|
||||||
|
// The file has been added.
|
||||||
|
addFile(*fi);
|
||||||
|
fi++;
|
||||||
|
} else if ((*fi).first > (*di).first) {
|
||||||
|
// The file has been deleted.
|
||||||
|
rmFile(*di);
|
||||||
|
di++;
|
||||||
|
} else {
|
||||||
|
throw Xapian::AssertionError("unexpected condition");
|
||||||
|
}
|
||||||
|
} // end `for`
|
||||||
|
}
|
||||||
|
|
||||||
|
db.commit_transaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const Xapian::Error &e) {
|
||||||
|
cerr << e.get_description() << endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int doSearch(vector<string> subArgs) {
|
||||||
|
TCLAP::CmdLine cmdLine("Specify a query expression as a string.");
|
||||||
|
TCLAP::ValueArg<string>
|
||||||
|
langArg("l", "lang", "stemming language (e.g., 'en' or 'fi')",
|
||||||
|
false, "en", "language");
|
||||||
|
cmdLine.add(langArg);
|
||||||
|
TCLAP::ValueArg<string>
|
||||||
|
queryArg("q", "query", "specifies a query string", false, "", "string");
|
||||||
|
cmdLine.add(queryArg);
|
||||||
|
TCLAP::ValueArg<int>
|
||||||
|
countArg("c", "max-count", "maximum number of results", false, 0, "number");
|
||||||
|
cmdLine.add(countArg);
|
||||||
|
TCLAP::SwitchArg
|
||||||
|
timeArg("t", "time-sort", "sort by modification time", false);
|
||||||
|
cmdLine.add(timeArg);
|
||||||
|
TCLAP::SwitchArg
|
||||||
|
nameArg("f", "name-sort", "sort by file name (overrides '-t')", false);
|
||||||
|
cmdLine.add(nameArg);
|
||||||
|
TCLAP::SwitchArg
|
||||||
|
verboseArg("v", "verbose", "be verbose", false);
|
||||||
|
cmdLine.add(verboseArg);
|
||||||
|
TCLAP::SwitchArg
|
||||||
|
flag_pure_not("n", "pure-not", "allow NOT", false);
|
||||||
|
cmdLine.add(flag_pure_not);
|
||||||
|
TCLAP::SwitchArg
|
||||||
|
flag_boolean_any_case("a", "boolean-any-case",
|
||||||
|
"allow lowercase operators", false);
|
||||||
|
cmdLine.add(flag_boolean_any_case);
|
||||||
|
TCLAP::UnlabeledMultiArg<string>
|
||||||
|
dirsArg("dir...", "specifies directories to search", false, "directory");
|
||||||
|
cmdLine.add(dirsArg);
|
||||||
|
cmdLine.parse(subArgs);
|
||||||
|
|
||||||
|
auto maxDocCount = countArg.getValue();
|
||||||
|
bool nameSort = nameArg.getValue();
|
||||||
|
bool timeSort = timeArg.getValue();
|
||||||
|
auto verbose = verboseArg.getValue();
|
||||||
|
|
||||||
|
string lang(langArg.getValue());
|
||||||
|
bool cjk = drop_substring(lang, ":cjk");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Xapian::Database db;
|
||||||
|
auto dirs = dirsArg.getValue();
|
||||||
|
int numDbFiles = 0;
|
||||||
|
for (auto dir : dirs) {
|
||||||
|
string dbFile(file_join(dir, ".notdeft-db"));
|
||||||
|
if (access(dbFile.c_str(), R_OK) != -1) {
|
||||||
|
Xapian::Database dirDb(dbFile);
|
||||||
|
db.add_database(dirDb);
|
||||||
|
numDbFiles++;
|
||||||
|
//cout << "Added database: " << db.get_description() << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (numDbFiles == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
Xapian::Enquire enquire(db);
|
||||||
|
if (nameSort) // by filename, descending
|
||||||
|
enquire.set_sort_by_value(DOC_FILENAME, true);
|
||||||
|
else if (timeSort) // by modification time, descending
|
||||||
|
enquire.set_sort_by_value(DOC_MTIME, true);
|
||||||
|
|
||||||
|
Xapian::QueryParser qp;
|
||||||
|
qp.add_prefix("path", "P");
|
||||||
|
qp.add_prefix("file", "F");
|
||||||
|
qp.add_prefix("ext", "E");
|
||||||
|
qp.add_prefix("title", "S");
|
||||||
|
qp.add_prefix("tag", "K");
|
||||||
|
Xapian::Stem stemmer(lang);
|
||||||
|
Xapian::Query query;
|
||||||
|
if (queryArg.getValue() == "") {
|
||||||
|
query = Xapian::Query::MatchAll;
|
||||||
|
} else {
|
||||||
|
qp.set_stemmer(stemmer);
|
||||||
|
qp.set_database(db);
|
||||||
|
qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
|
||||||
|
unsigned flags =
|
||||||
|
Xapian::QueryParser::FLAG_DEFAULT |
|
||||||
|
(flag_pure_not.getValue() ?
|
||||||
|
Xapian::QueryParser::FLAG_PURE_NOT : 0) |
|
||||||
|
(flag_boolean_any_case.getValue() ?
|
||||||
|
Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE : 0) |
|
||||||
|
(cjk ? QP_CJK : 0);
|
||||||
|
query = qp.parse_query(queryArg.getValue(), flags);
|
||||||
|
if (verbose)
|
||||||
|
cerr << "parsed query is: " << query.get_description() << endl;
|
||||||
|
}
|
||||||
|
enquire.set_query(query);
|
||||||
|
|
||||||
|
int maxItems = (maxDocCount ? maxDocCount : db.get_doccount());
|
||||||
|
Xapian::MSet matches = enquire.get_mset(0, maxItems);
|
||||||
|
for (Xapian::MSetIterator i = matches.begin(); i != matches.end(); ++i) {
|
||||||
|
cout << i.get_document().get_data() << endl;
|
||||||
|
}
|
||||||
|
} catch (const Xapian::Error &e) {
|
||||||
|
cerr << e.get_description() << endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char* argv[])
|
||||||
|
{
|
||||||
|
if (argc <= 1) {
|
||||||
|
usage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
string cmd(argv[1]);
|
||||||
|
vector<string> args({ string(argv[0]) + " " + cmd });
|
||||||
|
for (int i = 2; i < argc; i++)
|
||||||
|
args.emplace_back(argv[i]);
|
||||||
|
// for (auto s : args) cout << s << endl;
|
||||||
|
|
||||||
|
if (cmd == "index") {
|
||||||
|
return doIndex(args);
|
||||||
|
} else if (cmd == "search") {
|
||||||
|
return doSearch(args);
|
||||||
|
} else if (cmd == "-h" || cmd == "--help") {
|
||||||
|
usage();
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
usage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (C) 2017 Tero Hasu
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU General Public License as
|
||||||
|
published by the Free Software Foundation; either version 2 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
See the file GPL-2 for the full text of the GNU GPL.
|
||||||
|
|
||||||
|
*/
|
Reference in a new issue