[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/xeft 11cf93d3aa 06/55: Refactor the module
From: |
ELPA Syncer |
Subject: |
[elpa] externals/xeft 11cf93d3aa 06/55: Refactor the module |
Date: |
Fri, 13 Jan 2023 23:58:36 -0500 (EST) |
branch: externals/xeft
commit 11cf93d3aa0f39f92849019814497d9871e52869
Author: Yuan Fu <casouri@gmail.com>
Commit: Yuan Fu <casouri@gmail.com>
Refactor the module
* README.md:
* xeft-module.cc (CHECK_EXIT):
(query_term):
(bind_function):
(nil):
(intern):
(cons):
(funcall):
(signal):
(define_error):
(file_name_absolute_p):
(NILP):
(nilp):
(define_function):
(xeft_reindex_file_doc):
(Fxeft_reindex_file):
(xeft_query_term_doc):
(Fxeft_query_term):
(emacs_module_init):
* xeft.el (xeft-directory):
(xeft-database):
(xeft-mode):
(xeft--insert-file-excerpt):
(xeft-refresh):
---
README.md | 19 +++--
xeft-module.cc | 239 ++++++++++++++++++++++++++++++++++-----------------------
xeft.el | 38 +++++++--
3 files changed, 185 insertions(+), 111 deletions(-)
diff --git a/README.md b/README.md
index 937c05185b..f776aeb398 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,13 @@
-# What is this
-
-Xeft is
+# What is Xeft
1. A dynamic module that exposes a very basic indexing feature to
Emacs Lisp, that lets you index and search a text files very fast.
- I tried to use grep and ripgrep to build my note-searching interface,
- but they are too slow; with Xeft I can search on every key press.
+
+```emacs-lisp
+;; Querying my ~40MB worth of notes.
+(benchmark-run 100 (xeft-query-term "common lisp" xeft-database 0 10))
+;;=> (0.031512 0 0.0)
+```
2. A note taking interface like Deft, built on the dynamic module.
@@ -44,6 +46,8 @@ Both file path and database path must be absolute path.
# How to use the note-taking interface
It is essentially the same as [Zeft](https://github.com/casouri/zeft).
+To try it out, build the dynamic module or download prebuilt ones from
+release page. Then type `M-x xeft RET`.
# How to build the dynamic module
@@ -60,9 +64,10 @@ Then, build the module by
make PREFIX=/opt/local
```
-Here `/opt/local` is the default prefix of macports.
+Here `/opt/local` is the default prefix of macports, which is what I
+used to install Xapian.
-# Disclaimer
+# Beware
Since its a dynamic module, if Xeft goes wrong, it will crash Emacs.
diff --git a/xeft-module.cc b/xeft-module.cc
index bcaba52902..9cfcd2a315 100644
--- a/xeft-module.cc
+++ b/xeft-module.cc
@@ -5,6 +5,7 @@
#include <vector>
#include <exception>
#include <iterator>
+#include <cstdarg>
#include <stdlib.h>
#include <assert.h>
@@ -29,6 +30,20 @@ int plugin_is_GPL_compatible;
# define EMACS_NOEXCEPT
#endif
+#define CHECK_EXIT(env) \
+ if (env->non_local_exit_check (env) \
+ != emacs_funcall_exit_return) \
+ return NULL;
+
+/* A few notes: The database we use, WritableDatabase, will not throw
+ DatabaseModifiedError, so we don’t need to handle that. For query,
+ we first try to parse it with special syntax enabled, i.e., with
+ AND, OR, +/-, etc. If that doesn’t parse, we’ll just parse it as
+ plain text.
+
+ REF:
https://lists.xapian.org/pipermail/xapian-discuss/2021-August/009906.html
+ */
+
/*** Xapian stuff */
static const Xapian::valueno DOC_MTIME = 0;
@@ -145,6 +160,9 @@ query_term
Xapian::Stem stemmer (lang);
parser.set_stemmer (stemmer);
parser.set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
+ // Partial match (FLAG_PARTIAL) needs the database to expand
+ // wildcards.
+ parser.set_database(database);
Xapian::Query query;
try
@@ -157,7 +175,7 @@ query_term
| Xapian::QueryParser::FLAG_PARTIAL
| Xapian::QueryParser::FLAG_DEFAULT);
}
- // If the syntax is wrong (xxx AND xxx), Xapian throws this error.
+ // If the syntax is syntactically wrong, Xapian throws this error.
// Try again without enabling any special syntax.
catch (Xapian::QueryParserError &e)
{
@@ -191,7 +209,7 @@ query_term
/*** Module definition */
-/**** Convenient functions */
+/**** Copied from Philipp’s documents */
static bool
copy_string_contents
@@ -222,16 +240,6 @@ copy_string_contents
return true;
}
-static void
-bind_function (emacs_env *env, const char *name, emacs_value Sfun)
-{
- emacs_value Qfset = env->intern (env, "fset");
- emacs_value Qsym = env->intern (env, name);
-
- emacs_value args[] = {Qsym, Sfun};
- env->funcall (env, Qfset, 2, args);
-}
-
static void
provide (emacs_env *env, const char *feature)
{
@@ -242,15 +250,28 @@ provide (emacs_env *env, const char *feature)
env->funcall (env, Qprovide, 1, args);
}
+/**** Convenient functions */
+
static emacs_value
-nil (emacs_env *env) {
- return env->intern (env, "nil");
+intern (emacs_env *env, const char *name)
+{
+ return env->intern (env, name);
}
static emacs_value
-cons (emacs_env *env, emacs_value car, emacs_value cdr) {
- emacs_value args[] = {car, cdr};
- return env->funcall (env, env->intern(env, "cons"), 2, args);
+funcall (emacs_env *env, const char* fn, ptrdiff_t nargs, ...)
+{
+ va_list argv;
+ va_start (argv, nargs);
+ emacs_value *args = (emacs_value *) malloc(nargs * sizeof(emacs_value));
+ for (int idx = 0; idx < nargs; idx++)
+ {
+ args[idx] = va_arg (argv, emacs_value);
+ }
+ va_end (argv);
+ emacs_value val = env->funcall (env, intern (env, fn), nargs, args);
+ free (args);
+ return val;
}
static void
@@ -258,8 +279,9 @@ signal (emacs_env *env, const char *name, const char
*message)
{
env->non_local_exit_signal
(env, env->intern (env, name),
- cons (env, env->make_string (env, message, strlen (message)),
- nil (env)));
+ funcall (env, "cons", 2,
+ env->make_string (env, message, strlen (message)),
+ intern (env, "nil")));
}
static string
@@ -280,141 +302,164 @@ copy_string (emacs_env *env, emacs_value value)
}
}
-void
+static void
define_error
(emacs_env *env, const char *name,
const char *description, const char *parent)
{
- emacs_value args[] = {
- env->intern (env, name),
- env->make_string (env, description, strlen (description)),
- env->intern (env, parent)
- };
- env->funcall (env, env->intern (env, "define-error"), 3, args);
+ funcall (env, "define-error", 3,
+ intern (env, name),
+ env->make_string (env, description, strlen (description)),
+ intern (env, parent));
}
-emacs_value
-file_name_absolute_p (emacs_env *env, emacs_value path)
+static bool
+NILP (emacs_env *env, emacs_value val)
{
- emacs_value args[] = {path};
- return env->funcall
- (env, env->intern (env, "file-name-absolute-p"), 1, args);
+ return !env->is_not_nil (env, val);
}
-bool
-nilp (emacs_env *env, emacs_value val)
+typedef emacs_value (*emacs_subr) (emacs_env *env,
+ ptrdiff_t nargs, emacs_value *args,
+ void *data);
+
+static void
+define_function
+(emacs_env *env, const char *name, ptrdiff_t min_arity,
+ ptrdiff_t max_arity, emacs_subr function, const char *documentation)
{
- return !env->is_not_nil (env, val);
+ emacs_value fn = env->make_function
+ (env, min_arity, max_arity, function, documentation, NULL);
+ funcall (env, "fset", 2, intern (env, name), fn);
}
+/**** Exposed functions */
+
static const char* xeft_reindex_file_doc =
- "Refindex file at PATH with database at DBPATH"
- "Both paths has to be absolute. Normally, this function only"
- "reindex a file if it has been modified since last indexed,"
- "but if FORCE is non-nil, this function will always reindex."
- "Return non-nil if actually reindexed the file, return nil if not."
- ""
- "LANG is the language used by the indexer, it tells Xapian how to"
- "reduce words to stems and vice versa, e.g., apples <-> apple."
- "A full list of possible languages can be found at"
- "https://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html."
- "By default, LANG is \"en\"."
- ""
- "\(PATH DBPATH &optional LANG FORCE)";
+ "Refindex file at PATH with database at DBPATH\n"
+ "Both paths has to be absolute. Normally, this function only\n"
+ "reindex a file if it has been modified since last indexed,\n"
+ "but if FORCE is non-nil, this function will always reindex.\n"
+ "Return non-nil if actually reindexed the file, return nil if not.\n"
+ "\n"
+ "LANG is the language used by the indexer, it tells Xapian how to\n"
+ "reduce words to word stems, e.g., apples <-> apple.\n"
+ "A full list of possible languages can be found at\n"
+ "https://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html.\n"
+ "By default, LANG is \"en\".\n"
+ "\n"
+ "(fn PATH DBPATH &optional LANG FORCE)";
static emacs_value
Fxeft_reindex_file
(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) {
-
+
+ // Decode arguments.
emacs_value lisp_path = args[0];
emacs_value lisp_dbpath = args[1];
- if (nilp (env, file_name_absolute_p (env, lisp_path)))
+ if (NILP (env, funcall (env, "file-name-absolute-p", 1, lisp_path)))
{
signal (env, "xeft-file-error", "PATH is not a absolute path");
+ return NULL;
}
- if (nilp (env, file_name_absolute_p (env, lisp_dbpath)))
+ if (NILP (env, funcall (env, "file-name-absolute-p", 1, lisp_dbpath)))
{
signal (env, "xeft-file-error", "DBPATH is not a absolute path");
+ return NULL;
}
// Expand "~" in the filename.
emacs_value lisp_args[] = {lisp_path};
- lisp_path = env->funcall
- (env, env->intern (env, "expand-file-name"), 1, lisp_args);
- lisp_args[0] = lisp_dbpath;
- lisp_dbpath = env->funcall
- (env, env->intern (env, "expand-file-name"), 1, lisp_args);
-
- emacs_value lisp_lang = nargs < 3 ? nil (env) : args[2];
- emacs_value lisp_force = nargs < 4 ? nil (env) : args[3];
+ lisp_path = funcall (env, "expand-file-name", 1, lisp_path);
+ lisp_dbpath = funcall (env, "expand-file-name", 1, lisp_dbpath);
+
+ emacs_value lisp_lang = nargs < 3 ? intern (env, "nil") : args[2];
+ emacs_value lisp_force = nargs < 4 ? intern (env, "nil") : args[3];
string path = copy_string (env, lisp_path);
string dbpath = copy_string (env, lisp_dbpath);
- bool force = !nilp (env, lisp_force);
- string lang = nilp (env, lisp_lang) ?
+ bool force = !NILP (env, lisp_force);
+ CHECK_EXIT (env)
+ string lang = NILP (env, lisp_lang) ?
"en" : copy_string (env, lisp_lang);
-
+ CHECK_EXIT (env)
+
+ // Do the work.
bool indexed;
try
{
indexed = reindex_file (path, dbpath, lang, force);
+ return indexed ? intern (env, "t") : intern (env, "nil");
}
catch (xeft_cannot_open_file &e)
{
signal (env, "xeft-file-error", "Cannot open the file");
+ return NULL;
}
catch (Xapian::Error &e)
{
signal (env, "xeft-xapian-error", e.get_description().c_str());
+ return NULL;
}
catch (exception &e)
{
signal (env, "xeft-error", "Something went wrong");
+ return NULL;
}
-
- return indexed ? env->intern (env, "t") : nil (env);
}
static const char *xeft_query_term_doc =
- "Query for TERM in database at DBPATH."
- "Paging is supported by OFFSET and PAGE-SIZE. OFFSET specifies page"
- "start, and PAGE-SIZE the size. For example, if a page is 10 entries,"
- "OFFSET and PAGE-SIZE would be first 0 and 10, then 10 and 10, and"
- "so on."
- ""
- "If a file in the result doesn't exist anymore, it is removed from"
- "the database, and not included in the return value."
- ""
- "LANG is the language used by the indexer, it tells Xapian how to"
- "reduce words to stems and vice versa, e.g., apples <-> apple."
- "A full list of possible languages can be found at"
- "https://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html."
- "By default, LANG is \"en\"."
- ""
- "\(TERM DBPATH OFFSET PAGE-SIZE &optional LANG)";
+ "Query for TERM in database at DBPATH.\n"
+ "Paging is supported by OFFSET and PAGE-SIZE. OFFSET specifies page\n"
+ "start, and PAGE-SIZE the size. For example, if a page is 10 entries,\n"
+ "OFFSET and PAGE-SIZE would be first 0 and 10, then 10 and 10, and\n"
+ "so on.\n"
+ "\n"
+ "If a file in the result doesn't exist anymore, it is removed from\n"
+ "the database, and is not included in the return value.\n"
+ "\n"
+ "LANG is the language used by the indexer, it tells Xapian how to\n"
+ "reduce words to word stems, e.g., apples <-> apple.\n"
+ "A full list of possible languages can be found at\n"
+ "https://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html.\n"
+ "By default, LANG is \"en\".\n"
+ "\n"
+ "TERM can use common Xapian syntax like AND, OR, and +/-.\n"
+ "Specifically, this function supports:\n"
+ "\n"
+ " Boolean operators: AND, OR, XOR, NOT\n"
+ " Parenthesized expression: ()\n"
+ " Love/hate terms: +/-\n"
+ " Exact match: \"\"\n"
+ "\n"
+ "If TERM contains syntactic errors, like \"a AND AND b\",\n"
+ "it is treated as a plain term.\n"
+ "\n"
+ "(fn TERM DBPATH OFFSET PAGE-SIZE &optional LANG)";
static emacs_value
Fxeft_query_term
(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) {
+ // Decode arguments.
emacs_value lisp_term = args[0];
emacs_value lisp_dbpath = args[1];
emacs_value lisp_offset = args[2];
emacs_value lisp_page_size = args[3];
- if (nilp (env, file_name_absolute_p (env, lisp_dbpath)))
+ if (NILP (env, funcall (env, "file-name-absolute-p", 1, lisp_dbpath)))
{
signal (env, "xeft-file-error", "DBPATH is not a absolute path");
+ return NULL;
}
- emacs_value lisp_args[] = {lisp_dbpath};
- lisp_dbpath = env->funcall
- (env, env->intern (env, "expand-file-name"), 1, lisp_args);
+ lisp_dbpath = funcall (env, "expand-file-name", 1, lisp_dbpath);
string term = copy_string (env, lisp_term);
string dbpath = copy_string (env, lisp_dbpath);
int offset = env->extract_integer (env, lisp_offset);
int page_size = env->extract_integer (env, lisp_page_size);
+ CHECK_EXIT (env)
vector<string> result;
try
@@ -424,21 +469,24 @@ Fxeft_query_term
catch (Xapian::Error &e)
{
signal (env, "xeft-xapian-error", e.get_description().c_str());
+ return NULL;
}
catch (exception &e)
{
signal (env, "xeft-error", "Something went wrong");
+ return NULL;
}
vector<string>::iterator it;
- emacs_value ret = nil (env);
+ emacs_value ret = intern (env, "nil");
for (it = result.begin(); it != result.end(); it++) {
- ret = cons (env, env->make_string(env, it->c_str(),
- strlen(it->c_str())),
- ret);
+ ret = funcall (env, "cons", 2,
+ env->make_string
+ (env, it->c_str(), strlen(it->c_str())),
+ ret);
+ CHECK_EXIT (env)
}
-
- return env->funcall (env, env->intern (env, "reverse"), 1, &ret);
+ return funcall (env, "reverse", 1, ret);
}
int
@@ -450,15 +498,10 @@ emacs_module_init (struct emacs_runtime *ert)
EMACS_NOEXCEPT
define_error (env, "xeft-xapian-error", "Xapian error", "xeft-error");
define_error (env, "xeft-file-error", "Cannot open file", "xeft-error");
- bind_function (env, "xeft-reindex-file",
- env->make_function
- (env, 2, 3, &Fxeft_reindex_file,
- xeft_reindex_file_doc, NULL));
-
- bind_function (env, "xeft-query-term",
- env->make_function
- (env, 4, 4, &Fxeft_query_term,
- xeft_query_term_doc, NULL));
+ define_function(env, "xeft-reindex-file", 2, 3,
+ &Fxeft_reindex_file, xeft_reindex_file_doc);
+ define_function(env, "xeft-query-term", 4, 4,
+ &Fxeft_query_term, xeft_query_term_doc);
provide (env, "xeft-module");
diff --git a/xeft.el b/xeft.el
index c8c9672dff..0b2bbf98c0 100644
--- a/xeft.el
+++ b/xeft.el
@@ -5,6 +5,31 @@
;;; This file is NOT part of GNU Emacs
;;; Commentary:
+;;
+;; Usage:
+;;
+;; Type M-x xeft RET, and you should see the Xeft buffer. Type in your
+;; search phrase in the first line and the results will show up as you
+;; type. Press C-n and C-p to go through each file. You can preview a
+;; file by pressing SPC when the point is on a file, or click the file
+;; with the mouse. Press RET to open the file in the same window.
+;;
+;; Type C-c C-g to force a refresh. When point is on the search
+;; phrase, press RET to create a file with the search phrase as
+;; the filename and title.
+;;
+;; Note that:
+;;
+;; 1. Xeft only looks for first-level files in ‘xeft-directory’. Files
+;; in sub-directories are not searched.
+;;
+;; 2. Xeft creates a new file by using the search phrase as the
+;; filename and title. If you want otherwise, redefine
+;; ‘xeft-create-note’ or ‘xeft-filename-fn’.
+;;
+;; 3. Xeft saves the current window configuration before switching to
+;; Xeft buffer. When Xeft buffer is killed, Xeft restores the saved
+;; window configuration.
;;; Code:
@@ -18,11 +43,11 @@
"Xeft note interface."
:group 'applications)
-(defcustom xeft-directory (expand-file-name "~/.deft")
+(defcustom xeft-directory "~/.deft"
"Directory in where notes are stored. Must be a full path."
:type 'directory)
-(defcustom xeft-database (expand-file-name "~/.deft/db")
+(defcustom xeft-database "~/.deft/db"
"The path to the database."
:type 'directory)
@@ -99,8 +124,7 @@
(defvar xeft--need-refresh)
(define-derived-mode xeft-mode fundamental-mode
"Xeft" "Search for notes and display summaries."
- (let ((inhibit-read-only t)
- (buffer-undo-list t))
+ (let ((inhibit-read-only t))
;; Reindex all files.
(dolist (file (xeft--file-list))
(xeft-reindex-file file xeft-database))
@@ -304,7 +328,7 @@ search phrase the user typed."
(car (last (split-string search-phrase))))
title excerpt)
(with-current-buffer (xeft--work-buffer)
- (setq buffer-undo-list t)
+ (buffer-disable-undo)
;; We don’t need to cache file content, because we only insert
;; 15 results. And adding cache (with alist) is actually slower.
(insert-file-contents file nil nil nil t)
@@ -404,6 +428,7 @@ non-nil, display all results."
;; ‘xeft--need-refresh’ in that hook.
(inhibit-modification-hooks t)
(orig-point (point)))
+ (buffer-disable-undo)
;; Actually insert the new content.
(goto-char (point-min))
(forward-line 2)
@@ -433,7 +458,8 @@ non-nil, display all results."
;; buffer and re-inserted contents.
(goto-char orig-point)
;; Re-apply highlight.
- (xeft--highlight-file-at-point))
+ (xeft--highlight-file-at-point)
+ (buffer-enable-undo))
;; If interrupted, go back.
(goto-char orig-point)
;; If finished, update this variable.
- [elpa] externals/xeft 8850838345 19/55: Fix "create note" prompt, (continued)
- [elpa] externals/xeft 8850838345 19/55: Fix "create note" prompt, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft b12a0f58ea 34/55: Factor out two faces xeft-excerpt-title and xeft-excerpt-body, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 6fcc903bbb 51/55: ; * xeft.el (xeft--compile-module): Refactor., ELPA Syncer, 2023/01/13
- [elpa] externals/xeft af94f77834 07/55: * xeft-module.cc: Fix signiture, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 8a9f1e41b9 16/55: This got to fix it, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 951db71170 21/55: Fix default-extension format and add a recursive option, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft f2a7dd9259 30/55: Minor layout change, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 9532c7dd17 40/55: * README.md: Add instruction for windows., ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 45d975d54d 49/55: Prepare for ELPA, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 974520af0a 43/55: * Makefile (SOEXT): Fix if condition., ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 11cf93d3aa 06/55: Refactor the module,
ELPA Syncer <=
- [elpa] externals/xeft 2b2c8e5925 09/55: Double-buffering, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 71d13999a7 13/55: Various fixes and improvements, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 6c94f7bdb4 18/55: * xeft.el (xeft-refresh): Fix to show the "create note" prompt., ELPA Syncer, 2023/01/13
- [elpa] externals/xeft a3050c1596 20/55: * xeft.el (xeft-ignore-extension): Explain the option more clearly., ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 035ae7f3d0 22/55: * xeft.el: Update commentary about 'xeft-recursive'., ELPA Syncer, 2023/01/13
- [elpa] externals/xeft ad2d7b259d 23/55: Don't highlight short keywords., ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 7646d9d254 24/55: * xeft-module.cc (copy_string): Remember to free string buffer., ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 71febb833f 26/55: Improve latency, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 1c1b449d21 25/55: Add semicolon after macro, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 8b8c4bd37a 27/55: Improve README, ELPA Syncer, 2023/01/13