emacs-elpa-diffs
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]