[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/xeft afc8a69a52 01/55: init
From: |
ELPA Syncer |
Subject: |
[elpa] externals/xeft afc8a69a52 01/55: init |
Date: |
Fri, 13 Jan 2023 23:58:36 -0500 (EST) |
branch: externals/xeft
commit afc8a69a526ab7662bf68eb87ee76d5547116bb0
Author: Yuan Fu <casouri@gmail.com>
Commit: Yuan Fu <casouri@gmail.com>
init
---
Makefile | 12 +
README.md | 75 ++++++
emacs-module.h | 763 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
gitignore | 2 +
xeft-module.cc | 445 +++++++++++++++++++++++++++++++++
xeft.el | 421 +++++++++++++++++++++++++++++++
6 files changed, 1718 insertions(+)
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000..85ea3a839a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+.POSIX:
+PREFIX = /usr/local
+CXX = g++
+CXXFLAGS = -I$(PREFIX)/include
+LDFLAGS = -L$(PREFIX)/lib
+LDLIBS = -lxapian
+
+xeft-module.so: xeft-module.cc
+ $(CXX) -shared $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) $< -o $@
+
+clean:
+ rm -f *.so *.o
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..937c05185b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,75 @@
+# What is this
+
+Xeft is
+
+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.
+
+2. A note taking interface like Deft, built on the dynamic module.
+
+# How to use the dynamic module
+
+Because it’s so basic, the dynamic module is very easy to use and
+also very flexible. To index files, use
+
+```emacs-lisp
+(dolist (file (directory-files "my-note-dir"))
+ (xeft-reindex-file file dbpath))
+```
+
+This indexes each file in `my-note-dir`, saving them to the database
+at `dbpath`. If the database doesn’t exist yet, it is created.
+
+To search for a term, use
+
+```emacs-lisp
+(xeft-query-term "search term" dbpath 0 10)
+```
+
+This returns a list of paths of the files that contains `search term`,
+ranked by relevance. The `0` and `10` means “return 10 results
+starting from the 0th place”, it is essentially used for paging. If
+you want all the result, use `0` and `999999`.
+
+When a file is modified, call `xeft-reindex-file` again on that file.
+If a file is removed, you don’t need to remove it from the database,
+it will be automatically removed. If the file has been indexed and
+haven’t been modified, `xeft-reindex-file` is (kind of) a no-op (i.e.
+fast).
+
+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).
+
+# How to build the dynamic module
+
+To build the module, you need to have Xapian installed. On Mac, it can
+be installed with macports by
+
+```shell
+sudo port install xapian-core
+```
+
+Then, build the module by
+
+```shell
+make PREFIX=/opt/local
+```
+
+Here `/opt/local` is the default prefix of macports.
+
+# Disclaimer
+
+Since its a dynamic module, if Xeft goes wrong, it will crash Emacs.
+
+# notdeft
+
+Many thanks to the author of notdeft. I don’t really know C++ or
+Xapian, without reading his code I wouldn’t be able to write Xeft.
+
+Also, if you want a more powerful searching experience, you will be
+happier using notdeft instead.
diff --git a/emacs-module.h b/emacs-module.h
new file mode 100644
index 0000000000..1185c06f45
--- /dev/null
+++ b/emacs-module.h
@@ -0,0 +1,763 @@
+/* emacs-module.h - GNU Emacs module API.
+
+Copyright (C) 2015-2021 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
+
+/*
+This file defines the Emacs module API. Please see the chapter
+`Dynamic Modules' in the GNU Emacs Lisp Reference Manual for
+information how to write modules and use this header file.
+*/
+
+#ifndef EMACS_MODULE_H
+#define EMACS_MODULE_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <time.h>
+
+#ifndef __cplusplus
+#include <stdbool.h>
+#endif
+
+#define EMACS_MAJOR_VERSION 28
+
+#if defined __cplusplus && __cplusplus >= 201103L
+# define EMACS_NOEXCEPT noexcept
+#else
+# define EMACS_NOEXCEPT
+#endif
+
+#if defined __cplusplus && __cplusplus >= 201703L
+# define EMACS_NOEXCEPT_TYPEDEF noexcept
+#else
+# define EMACS_NOEXCEPT_TYPEDEF
+#endif
+
+#if 3 < __GNUC__ + (3 <= __GNUC_MINOR__)
+# define EMACS_ATTRIBUTE_NONNULL(...) \
+ __attribute__ ((__nonnull__ (__VA_ARGS__)))
+#elif (defined __has_attribute \
+ && (!defined __clang_minor__ \
+ || 3 < __clang_major__ + (5 <= __clang_minor__)))
+# if __has_attribute (__nonnull__)
+# define EMACS_ATTRIBUTE_NONNULL(...) \
+ __attribute__ ((__nonnull__ (__VA_ARGS__)))
+# endif
+#endif
+#ifndef EMACS_ATTRIBUTE_NONNULL
+# define EMACS_ATTRIBUTE_NONNULL(...)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Current environment. */
+typedef struct emacs_env_28 emacs_env;
+
+/* Opaque pointer representing an Emacs Lisp value.
+ BEWARE: Do not assume NULL is a valid value! */
+typedef struct emacs_value_tag *emacs_value;
+
+enum { emacs_variadic_function = -2 };
+
+/* Struct passed to a module init function (emacs_module_init). */
+struct emacs_runtime
+{
+ /* Structure size (for version checking). */
+ ptrdiff_t size;
+
+ /* Private data; users should not touch this. */
+ struct emacs_runtime_private *private_members;
+
+ /* Return an environment pointer. */
+ emacs_env *(*get_environment) (struct emacs_runtime *runtime)
+ EMACS_ATTRIBUTE_NONNULL (1);
+};
+
+/* Type aliases for function pointer types used in the module API.
+ Note that we don't use these aliases directly in the API to be able
+ to mark the function arguments as 'noexcept' before C++20.
+ However, users can use them if they want. */
+
+/* Function prototype for the module Lisp functions. These must not
+ throw C++ exceptions. */
+typedef emacs_value (*emacs_function) (emacs_env *env, ptrdiff_t nargs,
+ emacs_value *args,
+ void *data)
+ EMACS_NOEXCEPT_TYPEDEF EMACS_ATTRIBUTE_NONNULL (1);
+
+/* Function prototype for module user-pointer and function finalizers.
+ These must not throw C++ exceptions. */
+typedef void (*emacs_finalizer) (void *data) EMACS_NOEXCEPT_TYPEDEF;
+
+/* Possible Emacs function call outcomes. */
+enum emacs_funcall_exit
+{
+ /* Function has returned normally. */
+ emacs_funcall_exit_return = 0,
+
+ /* Function has signaled an error using `signal'. */
+ emacs_funcall_exit_signal = 1,
+
+ /* Function has exit using `throw'. */
+ emacs_funcall_exit_throw = 2
+};
+
+/* Possible return values for emacs_env.process_input. */
+enum emacs_process_input_result
+{
+ /* Module code may continue */
+ emacs_process_input_continue = 0,
+
+ /* Module code should return control to Emacs as soon as possible. */
+ emacs_process_input_quit = 1
+};
+
+/* Define emacs_limb_t so that it is likely to match GMP's mp_limb_t.
+ This micro-optimization can help modules that use mpz_export and
+ mpz_import, which operate more efficiently on mp_limb_t. It's OK
+ (if perhaps a bit slower) if the two types do not match, and
+ modules shouldn't rely on the two types matching. */
+typedef size_t emacs_limb_t;
+#define EMACS_LIMB_MAX SIZE_MAX
+
+struct emacs_env_25
+{
+ /* Structure size (for version checking). */
+ ptrdiff_t size;
+
+ /* Private data; users should not touch this. */
+ struct emacs_env_private *private_members;
+
+ /* Memory management. */
+
+ emacs_value (*make_global_ref) (emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*free_global_ref) (emacs_env *env, emacs_value global_value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Non-local exit handling. */
+
+ enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_clear) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ enum emacs_funcall_exit (*non_local_exit_get)
+ (emacs_env *env, emacs_value *symbol, emacs_value *data)
+ EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
+
+ void (*non_local_exit_signal) (emacs_env *env,
+ emacs_value symbol, emacs_value data)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_throw) (emacs_env *env,
+ emacs_value tag, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Function registration. */
+
+ emacs_value (*make_function) (emacs_env *env,
+ ptrdiff_t min_arity,
+ ptrdiff_t max_arity,
+ emacs_value (*func) (emacs_env *env,
+ ptrdiff_t nargs,
+ emacs_value* args,
+ void *data)
+ EMACS_NOEXCEPT
+ EMACS_ATTRIBUTE_NONNULL(1),
+ const char *docstring,
+ void *data)
+ EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ emacs_value (*funcall) (emacs_env *env,
+ emacs_value func,
+ ptrdiff_t nargs,
+ emacs_value* args)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*intern) (emacs_env *env, const char *name)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Type conversion. */
+
+ emacs_value (*type_of) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*is_not_nil) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*eq) (emacs_env *env, emacs_value a, emacs_value b)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ intmax_t (*extract_integer) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_integer) (emacs_env *env, intmax_t n)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ double (*extract_float) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_float) (emacs_env *env, double d)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
+ null-terminated string.
+
+ SIZE must point to the total size of the buffer. If BUFFER is
+ NULL or if SIZE is not big enough, write the required buffer size
+ to SIZE and return true.
+
+ Note that SIZE must include the last null byte (e.g. "abc" needs
+ a buffer of size 4).
+
+ Return true if the string was successfully copied. */
+
+ bool (*copy_string_contents) (emacs_env *env,
+ emacs_value value,
+ char *buf,
+ ptrdiff_t *len)
+ EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ /* Create a Lisp string from a utf8 encoded string. */
+ emacs_value (*make_string) (emacs_env *env,
+ const char *str, ptrdiff_t len)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Embedded pointer type. */
+ emacs_value (*make_user_ptr) (emacs_env *env,
+ void (*fin) (void *) EMACS_NOEXCEPT,
+ void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void *(*get_user_ptr) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr))
+ (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_finalizer) (emacs_env *env, emacs_value arg,
+ void (*fin) (void *) EMACS_NOEXCEPT)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Vector functions. */
+ emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index,
+ emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector)
+ EMACS_ATTRIBUTE_NONNULL(1);
+};
+
+struct emacs_env_26
+{
+ /* Structure size (for version checking). */
+ ptrdiff_t size;
+
+ /* Private data; users should not touch this. */
+ struct emacs_env_private *private_members;
+
+ /* Memory management. */
+
+ emacs_value (*make_global_ref) (emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*free_global_ref) (emacs_env *env, emacs_value global_value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Non-local exit handling. */
+
+ enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_clear) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ enum emacs_funcall_exit (*non_local_exit_get)
+ (emacs_env *env, emacs_value *symbol, emacs_value *data)
+ EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
+
+ void (*non_local_exit_signal) (emacs_env *env,
+ emacs_value symbol, emacs_value data)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_throw) (emacs_env *env,
+ emacs_value tag, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Function registration. */
+
+ emacs_value (*make_function) (emacs_env *env,
+ ptrdiff_t min_arity,
+ ptrdiff_t max_arity,
+ emacs_value (*func) (emacs_env *env,
+ ptrdiff_t nargs,
+ emacs_value* args,
+ void *data)
+ EMACS_NOEXCEPT
+ EMACS_ATTRIBUTE_NONNULL(1),
+ const char *docstring,
+ void *data)
+ EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ emacs_value (*funcall) (emacs_env *env,
+ emacs_value func,
+ ptrdiff_t nargs,
+ emacs_value* args)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*intern) (emacs_env *env, const char *name)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Type conversion. */
+
+ emacs_value (*type_of) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*is_not_nil) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*eq) (emacs_env *env, emacs_value a, emacs_value b)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ intmax_t (*extract_integer) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_integer) (emacs_env *env, intmax_t n)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ double (*extract_float) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_float) (emacs_env *env, double d)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
+ null-terminated string.
+
+ SIZE must point to the total size of the buffer. If BUFFER is
+ NULL or if SIZE is not big enough, write the required buffer size
+ to SIZE and return true.
+
+ Note that SIZE must include the last null byte (e.g. "abc" needs
+ a buffer of size 4).
+
+ Return true if the string was successfully copied. */
+
+ bool (*copy_string_contents) (emacs_env *env,
+ emacs_value value,
+ char *buf,
+ ptrdiff_t *len)
+ EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ /* Create a Lisp string from a utf8 encoded string. */
+ emacs_value (*make_string) (emacs_env *env,
+ const char *str, ptrdiff_t len)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Embedded pointer type. */
+ emacs_value (*make_user_ptr) (emacs_env *env,
+ void (*fin) (void *) EMACS_NOEXCEPT,
+ void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void *(*get_user_ptr) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr))
+ (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_finalizer) (emacs_env *env, emacs_value arg,
+ void (*fin) (void *) EMACS_NOEXCEPT)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Vector functions. */
+ emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index,
+ emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Returns whether a quit is pending. */
+ bool (*should_quit) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+};
+
+struct emacs_env_27
+{
+ /* Structure size (for version checking). */
+ ptrdiff_t size;
+
+ /* Private data; users should not touch this. */
+ struct emacs_env_private *private_members;
+
+ /* Memory management. */
+
+ emacs_value (*make_global_ref) (emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*free_global_ref) (emacs_env *env, emacs_value global_value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Non-local exit handling. */
+
+ enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_clear) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ enum emacs_funcall_exit (*non_local_exit_get)
+ (emacs_env *env, emacs_value *symbol, emacs_value *data)
+ EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
+
+ void (*non_local_exit_signal) (emacs_env *env,
+ emacs_value symbol, emacs_value data)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_throw) (emacs_env *env,
+ emacs_value tag, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Function registration. */
+
+ emacs_value (*make_function) (emacs_env *env,
+ ptrdiff_t min_arity,
+ ptrdiff_t max_arity,
+ emacs_value (*func) (emacs_env *env,
+ ptrdiff_t nargs,
+ emacs_value* args,
+ void *data)
+ EMACS_NOEXCEPT
+ EMACS_ATTRIBUTE_NONNULL(1),
+ const char *docstring,
+ void *data)
+ EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ emacs_value (*funcall) (emacs_env *env,
+ emacs_value func,
+ ptrdiff_t nargs,
+ emacs_value* args)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*intern) (emacs_env *env, const char *name)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Type conversion. */
+
+ emacs_value (*type_of) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*is_not_nil) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*eq) (emacs_env *env, emacs_value a, emacs_value b)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ intmax_t (*extract_integer) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_integer) (emacs_env *env, intmax_t n)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ double (*extract_float) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_float) (emacs_env *env, double d)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
+ null-terminated string.
+
+ SIZE must point to the total size of the buffer. If BUFFER is
+ NULL or if SIZE is not big enough, write the required buffer size
+ to SIZE and return true.
+
+ Note that SIZE must include the last null byte (e.g. "abc" needs
+ a buffer of size 4).
+
+ Return true if the string was successfully copied. */
+
+ bool (*copy_string_contents) (emacs_env *env,
+ emacs_value value,
+ char *buf,
+ ptrdiff_t *len)
+ EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ /* Create a Lisp string from a utf8 encoded string. */
+ emacs_value (*make_string) (emacs_env *env,
+ const char *str, ptrdiff_t len)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Embedded pointer type. */
+ emacs_value (*make_user_ptr) (emacs_env *env,
+ void (*fin) (void *) EMACS_NOEXCEPT,
+ void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void *(*get_user_ptr) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr))
+ (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_finalizer) (emacs_env *env, emacs_value arg,
+ void (*fin) (void *) EMACS_NOEXCEPT)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Vector functions. */
+ emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index,
+ emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Returns whether a quit is pending. */
+ bool (*should_quit) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Processes pending input events and returns whether the module
+ function should quit. */
+ enum emacs_process_input_result (*process_input) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ struct timespec (*extract_time) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ emacs_value (*make_time) (emacs_env *env, struct timespec time)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ bool (*extract_big_integer) (emacs_env *env, emacs_value arg, int *sign,
+ ptrdiff_t *count, emacs_limb_t *magnitude)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ emacs_value (*make_big_integer) (emacs_env *env, int sign, ptrdiff_t count,
+ const emacs_limb_t *magnitude)
+ EMACS_ATTRIBUTE_NONNULL (1);
+};
+
+struct emacs_env_28
+{
+ /* Structure size (for version checking). */
+ ptrdiff_t size;
+
+ /* Private data; users should not touch this. */
+ struct emacs_env_private *private_members;
+
+ /* Memory management. */
+
+ emacs_value (*make_global_ref) (emacs_env *env, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*free_global_ref) (emacs_env *env, emacs_value global_value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Non-local exit handling. */
+
+ enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_clear) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ enum emacs_funcall_exit (*non_local_exit_get)
+ (emacs_env *env, emacs_value *symbol, emacs_value *data)
+ EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
+
+ void (*non_local_exit_signal) (emacs_env *env,
+ emacs_value symbol, emacs_value data)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*non_local_exit_throw) (emacs_env *env,
+ emacs_value tag, emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Function registration. */
+
+ emacs_value (*make_function) (emacs_env *env,
+ ptrdiff_t min_arity,
+ ptrdiff_t max_arity,
+ emacs_value (*func) (emacs_env *env,
+ ptrdiff_t nargs,
+ emacs_value* args,
+ void *data)
+ EMACS_NOEXCEPT
+ EMACS_ATTRIBUTE_NONNULL(1),
+ const char *docstring,
+ void *data)
+ EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ emacs_value (*funcall) (emacs_env *env,
+ emacs_value func,
+ ptrdiff_t nargs,
+ emacs_value* args)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*intern) (emacs_env *env, const char *name)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Type conversion. */
+
+ emacs_value (*type_of) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*is_not_nil) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ bool (*eq) (emacs_env *env, emacs_value a, emacs_value b)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ intmax_t (*extract_integer) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_integer) (emacs_env *env, intmax_t n)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ double (*extract_float) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ emacs_value (*make_float) (emacs_env *env, double d)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
+ null-terminated string.
+
+ SIZE must point to the total size of the buffer. If BUFFER is
+ NULL or if SIZE is not big enough, write the required buffer size
+ to SIZE and return true.
+
+ Note that SIZE must include the last null byte (e.g. "abc" needs
+ a buffer of size 4).
+
+ Return true if the string was successfully copied. */
+
+ bool (*copy_string_contents) (emacs_env *env,
+ emacs_value value,
+ char *buf,
+ ptrdiff_t *len)
+ EMACS_ATTRIBUTE_NONNULL(1, 4);
+
+ /* Create a Lisp string from a utf8 encoded string. */
+ emacs_value (*make_string) (emacs_env *env,
+ const char *str, ptrdiff_t len)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+
+ /* Embedded pointer type. */
+ emacs_value (*make_user_ptr) (emacs_env *env,
+ void (*fin) (void *) EMACS_NOEXCEPT,
+ void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void *(*get_user_ptr) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr))
+ (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1);
+ void (*set_user_finalizer) (emacs_env *env, emacs_value arg,
+ void (*fin) (void *) EMACS_NOEXCEPT)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Vector functions. */
+ emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index,
+ emacs_value value)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Returns whether a quit is pending. */
+ bool (*should_quit) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL(1);
+
+ /* Processes pending input events and returns whether the module
+ function should quit. */
+ enum emacs_process_input_result (*process_input) (emacs_env *env)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ struct timespec (*extract_time) (emacs_env *env, emacs_value arg)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ emacs_value (*make_time) (emacs_env *env, struct timespec time)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ bool (*extract_big_integer) (emacs_env *env, emacs_value arg, int *sign,
+ ptrdiff_t *count, emacs_limb_t *magnitude)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ emacs_value (*make_big_integer) (emacs_env *env, int sign, ptrdiff_t count,
+ const emacs_limb_t *magnitude)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ /* Add module environment functions newly added in Emacs 28 here.
+ Before Emacs 28 is released, remove this comment and start
+ module-env-29.h on the master branch. */
+
+ void (*(*EMACS_ATTRIBUTE_NONNULL (1)
+ get_function_finalizer) (emacs_env *env,
+ emacs_value arg)) (void *) EMACS_NOEXCEPT;
+
+ void (*set_function_finalizer) (emacs_env *env, emacs_value arg,
+ void (*fin) (void *) EMACS_NOEXCEPT)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ int (*open_channel) (emacs_env *env, emacs_value pipe_process)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ void (*make_interactive) (emacs_env *env, emacs_value function,
+ emacs_value spec)
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+ /* Create a unibyte Lisp string from a string. */
+ emacs_value (*make_unibyte_string) (emacs_env *env,
+ const char *str, ptrdiff_t len)
+ EMACS_ATTRIBUTE_NONNULL(1, 2);
+};
+
+/* Every module should define a function as follows. */
+extern int emacs_module_init (struct emacs_runtime *runtime)
+ EMACS_NOEXCEPT
+ EMACS_ATTRIBUTE_NONNULL (1);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EMACS_MODULE_H */
diff --git a/gitignore b/gitignore
new file mode 100644
index 0000000000..e2d2375a62
--- /dev/null
+++ b/gitignore
@@ -0,0 +1,2 @@
+*.so
+*.o
\ No newline at end of file
diff --git a/xeft-module.cc b/xeft-module.cc
new file mode 100644
index 0000000000..22152e6bd7
--- /dev/null
+++ b/xeft-module.cc
@@ -0,0 +1,445 @@
+#include <string>
+#include <cstring>
+#include <iostream>
+#include <fstream>
+#include <vector>
+#include <exception>
+#include <iterator>
+
+#include <stdlib.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <xapian.h>
+
+#include "emacs-module.h"
+
+using namespace std;
+
+int plugin_is_GPL_compatible;
+
+/*** Xapian stuff */
+
+static const Xapian::valueno DOC_MTIME = 0;
+static const Xapian::valueno DOC_FILEPATH = 1;
+
+static Xapian::WritableDatabase database;
+static string cached_dbpath = "";
+
+class xeft_cannot_open_file: public exception {};
+
+// Reindex the file at PATH, using database at DBPATH. Throws
+// cannot_open_file. Both path must be absolute. Normally only reindex
+// if file has change since last index, if FORCE is true, always
+// reindex. Return true if re-indexed, return false if didn’t.
+// LANG is the language used by the stemmer.
+// Possible langauges:
+// https://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html
+static bool
+reindex_file
+(string path, string dbpath, string lang = "en", bool force = false)
+{
+ // Check for mtime.
+ struct stat st;
+ time_t file_mtime;
+ off_t file_size;
+ if (stat (path.c_str(), &st) == 0)
+ {
+ file_mtime = st.st_mtime;
+ file_size = st.st_size;
+ }
+ else
+ {
+ throw xeft_cannot_open_file();
+ }
+
+ // Even though the document says that database object only carries a
+ // pointer to the actual object, it is still not cheap enough. By
+ // using this cache, we get much better performance when reindexing
+ // hundreds of files, which most are no-op because they hasn’t been
+ // modified.
+ if (dbpath != cached_dbpath)
+ {
+ database = Xapian::WritableDatabase
+ (dbpath, Xapian::DB_CREATE_OR_OPEN);
+ cached_dbpath = dbpath;
+ }
+ // Track doc with file path as "id". See
+ //
https://getting-started-with-xapian.readthedocs.io/en/latest/practical_example/indexing/updating_the_database.html
+ string termID = 'Q' + path;
+ Xapian::PostingIterator it_begin = database.postlist_begin (termID);
+ Xapian::PostingIterator it_end = database.postlist_end (termID);
+ bool has_doc = it_begin != it_end;
+ time_t db_mtime;
+ if (has_doc)
+ {
+ Xapian::Document db_doc = database.get_document(*it_begin);
+ db_mtime = (time_t) stoi (db_doc.get_value (DOC_MTIME));
+ }
+
+ // Need re-index.
+ if (!has_doc || (has_doc && db_mtime < file_mtime) || force)
+ {
+ // Get the file content.
+ // REF:
https://stackoverflow.com/questions/2912520/read-file-contents-into-a-string-in-c
+ ifstream infile (path);
+ string content ((istreambuf_iterator<char>(infile)),
+ (istreambuf_iterator<char>()));
+ // Create the indexer.
+ Xapian::TermGenerator indexer;
+ Xapian::Stem stemmer (lang);
+ indexer.set_stemmer (stemmer);
+ indexer.set_stemming_strategy
+ (Xapian::TermGenerator::STEM_SOME);
+ // Support CJK.
+ indexer.set_flags (Xapian::TermGenerator::FLAG_CJK_NGRAM);
+ // Index file content.
+ Xapian::Document new_doc;
+ indexer.set_document (new_doc);
+ indexer.index_text (content);
+ // Set doc info.
+ new_doc.add_boolean_term (termID);
+ // We store the path in value, no need to use set_data.
+ new_doc.add_value (DOC_FILEPATH, path);
+ new_doc.add_value (DOC_MTIME, (string) to_string (file_mtime));
+ database.replace_document (termID, new_doc);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+// Query TERM in the databse at DBPATH. OFFSET and PAGE_SIZE is for
+// paging, see the docstring for the lisp function. If a file in the
+// result doesn’t exist anymore, it is removed from the database.
+// LANG is the language used by the stemmer.
+// Possible langauges:
+// https://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html
+static vector<string>
+query_term
+(string term, string dbpath, int offset, int page_size, string lang = "en")
+{
+ // See reindex_file for the reason for caching the database object.
+ if (dbpath != cached_dbpath)
+ {
+ database = Xapian::WritableDatabase
+ (dbpath, Xapian::DB_CREATE_OR_OPEN);
+ cached_dbpath = dbpath;
+ }
+
+ Xapian::QueryParser parser;
+ Xapian::Stem stemmer (lang);
+ parser.set_stemmer (stemmer);
+ parser.set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
+
+ Xapian::Query query;
+ try
+ {
+ query = parser.parse_query
+ // Support CJK.
+ (term, Xapian::QueryParser::FLAG_CJK_NGRAM
+ | Xapian::QueryParser::FLAG_DEFAULT);
+ }
+ // If the syntax is wrong (xxx AND xxx), Xapian throws this error.
+ // Try again without enabling any special syntax.
+ catch (Xapian::QueryParserError &e)
+ {
+ query = parser.parse_query
+ // Support CJK.
+ (term, Xapian::QueryParser::FLAG_CJK_NGRAM);
+ }
+
+ Xapian::Enquire enquire (database);
+ enquire.set_query (query);
+
+ Xapian::MSet mset = enquire.get_mset (offset, page_size);
+ vector<string> result (0);
+ for (Xapian::MSetIterator it = mset.begin(); it != mset.end(); it++)
+ {
+ Xapian::Document doc = it.get_document();
+ string path = doc.get_value(DOC_FILEPATH);
+ // If the file doesn’t exists anymore, remove it.
+ struct stat st;
+ if (stat (path.c_str(), &st) == 0)
+ {
+ result.push_back (doc.get_value (DOC_FILEPATH));
+ }
+ else
+ {
+ database.delete_document (doc.get_docid());
+ }
+ }
+ return result;
+}
+
+/*** Module definition */
+
+/**** Convenient functions */
+
+static bool
+copy_string_contents
+(emacs_env *env, emacs_value value, char **buffer, size_t *size)
+{
+ ptrdiff_t buffer_size;
+ if (!env->copy_string_contents (env, value, NULL, &buffer_size))
+ return false;
+ assert (env->non_local_exit_check (env) == emacs_funcall_exit_return);
+ assert (buffer_size > 0);
+ *buffer = (char*) malloc ((size_t) buffer_size);
+ if (*buffer == NULL)
+ {
+ env->non_local_exit_signal (env, env->intern (env, "memory-full"),
+ env->intern (env, "nil"));
+ return false;
+ }
+ ptrdiff_t old_buffer_size = buffer_size;
+ if (!env->copy_string_contents (env, value, *buffer, &buffer_size))
+ {
+ free (*buffer);
+ *buffer = NULL;
+ return false;
+ }
+ assert (env->non_local_exit_check (env) == emacs_funcall_exit_return);
+ assert (buffer_size == old_buffer_size);
+ *size = (size_t) (buffer_size - 1);
+ 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)
+{
+ emacs_value Qfeat = env->intern (env, feature);
+ emacs_value Qprovide = env->intern (env, "provide");
+ emacs_value args[] = { Qfeat };
+
+ env->funcall (env, Qprovide, 1, args);
+}
+
+static emacs_value
+nil (emacs_env *env) {
+ return env->intern (env, "nil");
+}
+
+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);
+}
+
+static void
+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)));
+}
+
+static string
+copy_string (emacs_env *env, emacs_value value)
+{
+ char* char_buffer;
+ size_t size;
+ if (copy_string_contents (env, value, &char_buffer, &size))
+ {
+ string str = (string) char_buffer;
+ return str;
+ }
+ else
+ {
+ signal (env, "xeft-error",
+ "Error turning lisp string to C++ string");
+ return "";
+ }
+}
+
+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);
+}
+
+emacs_value
+file_name_absolute_p (emacs_env *env, emacs_value path)
+{
+ emacs_value args[] = {path};
+ return env->funcall
+ (env, env->intern (env, "file-name-absolute-p"), 1, args);
+}
+
+bool
+nilp (emacs_env *env, emacs_value val)
+{
+ return !env->is_not_nil (env, val);
+}
+
+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)";
+
+static emacs_value
+Fxeft_reindex_file
+(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) {
+
+ emacs_value lisp_path = args[0];
+ emacs_value lisp_dbpath = args[1];
+
+ if (nilp (env, file_name_absolute_p (env, lisp_path)))
+ {
+ signal (env, "xeft-file-error", "PATH is not a absolute path");
+ }
+ if (nilp (env, file_name_absolute_p (env, lisp_dbpath)))
+ {
+ signal (env, "xeft-file-error", "DBPATH is not a absolute path");
+ }
+
+ emacs_value lisp_lang = nargs < 3 ? nil (env) : args[2];
+ emacs_value lisp_force = nargs < 4 ? nil (env) : 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) ?
+ "en" : copy_string (env, lisp_lang);
+
+ bool indexed;
+ try
+ {
+ indexed = reindex_file (path, dbpath, lang, force);
+ }
+ catch (xeft_cannot_open_file &e)
+ {
+ signal (env, "xeft-file-error", "Cannot open the file");
+ }
+ catch (Xapian::Error &e)
+ {
+ signal (env, "xeft-xapian-error", e.get_description().c_str());
+ }
+ catch (exception &e)
+ {
+ signal (env, "xeft-error", "Something went wrong");
+ }
+
+ 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)";
+
+static emacs_value
+Fxeft_query_term
+(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) {
+ 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)))
+ {
+ signal (env, "xeft-file-error", "DBPATH is not a absolute path");
+ }
+
+ 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);
+
+ vector<string> result;
+ try
+ {
+ result = query_term (term, dbpath, offset, page_size);
+ }
+ catch (Xapian::Error &e)
+ {
+ signal (env, "xeft-xapian-error", e.get_description().c_str());
+ }
+ catch (exception &e)
+ {
+ signal (env, "xeft-error", "Something went wrong");
+ }
+
+ vector<string>::iterator it;
+ emacs_value ret = nil (env);
+ for (it = result.begin(); it != result.end(); it++) {
+ ret = cons (env, env->make_string(env, it->c_str(),
+ strlen(it->c_str())),
+ ret);
+ }
+
+ return env->funcall (env, env->intern (env, "reverse"), 1, &ret);
+}
+
+int
+emacs_module_init (struct emacs_runtime *ert)
+{
+ emacs_env *env = ert->get_environment (ert);
+
+ define_error (env, "xeft-error", "Generic xeft error", "error");
+ 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));
+
+ provide (env, "xeft-module");
+
+ /* Return 0 to indicate module loaded successfully. */
+ return 0;
+}
diff --git a/xeft.el b/xeft.el
new file mode 100644
index 0000000000..4a78dedeb7
--- /dev/null
+++ b/xeft.el
@@ -0,0 +1,421 @@
+;;; xeft.el --- Yay note-taking -*- lexical-binding: t; -*-
+
+;; Author: Yuan Fu <casouri@gmail.com>
+
+;;; This file is NOT part of GNU Emacs
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'cl-lib)
+(declare-function xeft-reindex-file nil (path dbpath lang force))
+(declare-function xeft-query-term nil (term dbpath offset page-size lang))
+
+;;; Customize
+
+(defgroup xeft nil
+ "Xeft note interface."
+ :group 'applications)
+
+(defcustom xeft-directory (expand-file-name "~/.deft")
+ "Directory in where notes are stored. Must be a full path."
+ :type 'directory)
+
+(defcustom xeft-database (expand-file-name "~/.deft/db")
+ "The path to the database."
+ :type 'directory)
+
+(defcustom xeft-find-file-hook nil
+ "Hook run when Xeft opens a file."
+ :type 'hook)
+
+(defface xeft-selection
+ '((t . (:inherit region :extend t)))
+ "Face for the current selected search result.")
+
+(defface xeft-inline-highlight
+ '((t . (:inherit underline :extend t)))
+ "Face for inline highlighting in Xeft buffer.")
+
+(defface xeft-preview-highlight
+ '((t . (:inherit highlight :extend t)))
+ "Face for highlighting in the preview buffer.")
+
+(defcustom xeft-load-file-hook nil
+ "Functions run before xeft loads a file into database."
+ :type 'hook)
+
+;;; Compile
+
+(defun xeft--compile-module ()
+ "Compile the dynamic module. Return non-nil if success."
+ ;; Just following vterm.el here.
+ (let* ((source-dir
+ (shell-quote-argument
+ (file-name-directory
+ (locate-library "xeft.el" t))))
+ (command (format "cd %s; make PREFIX=%s"
+ source-dir
+ (read-string "PREFIX: " "/usr/local")))
+ (buffer (get-buffer-create "*xeft compile*")))
+ (if (zerop (let ((inhibit-read-only t))
+ (call-process "sh" nil buffer t "-c" command)))
+ (progn (message "Successfully compiled the module :-D") t)
+ (pop-to-buffer buffer)
+ (compilation-mode)
+ (message "Failed to compile the module")
+ nil)))
+
+;;; Helpers
+
+(defvar xeft--last-window-config nil
+ "Window configuration before Xeft starts.")
+
+(defun xeft--buffer ()
+ "Return the xeft buffer."
+ (get-buffer-create "*xeft*"))
+
+(defun xeft--work-buffer ()
+ "Return the work buffer for Xeft. Used for holding file contents."
+ (get-buffer-create " *xeft work*"))
+
+(defun xeft--after-save-hook ()
+ "Reindex the file."
+ (xeft-reindex-file (buffer-file-name) xeft-database))
+
+(defvar xeft-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "RET") #'xeft-create-note)
+ (define-key map (kbd "C-c C-g") #'xeft-refresh-full)
+ (define-key map (kbd "C-n") #'xeft-next)
+ (define-key map (kbd "C-p") #'xeft-previous)
+ map)
+ "Mode map for `xeft-mode'.")
+
+(define-derived-mode xeft-mode fundamental-mode
+ "Xeft" "Search for notes and display summaries."
+ (let ((inhibit-read-only t))
+ ;; Reindex all files.
+ (dolist (file (xeft--file-list))
+ (xeft-reindex-file file xeft-database))
+ (visual-line-mode)
+ (setq default-directory xeft-directory
+ xeft--last-window-config (current-window-configuration))
+ (add-hook 'after-change-functions
+ (lambda (&rest _) (xeft-refresh)) 0 t)
+ (add-hook 'window-size-change-functions
+ (lambda (&rest _) (xeft-refresh))0 t)
+ (add-hook 'kill-buffer-hook
+ (lambda ()
+ (when xeft--last-window-config
+ (set-window-configuration xeft--last-window-config)))
+ 0 t)
+ (erase-buffer)
+ (insert "\n\nInsert search phrase and press RET to search.")
+ (goto-char (point-min))))
+
+
+;;; Userland
+
+;;;###autoload
+(defun xeft ()
+ "Start Xeft."
+ (interactive)
+ (unless (require 'xeft-module nil t)
+ (when (y-or-n-p
+ "Xeft needs the dynamic module to work, compile it now? ")
+ (when (xeft--compile-module)
+ (require 'xeft-module))))
+ (setq xeft--last-window-config (current-window-configuration))
+ (switch-to-buffer (xeft--buffer))
+ (when (not (derived-mode-p 'xeft-mode))
+ (xeft-mode)))
+
+(defun xeft-create-note ()
+ "Create a new note with the current search phrase as the title."
+ (interactive)
+ (let* ((search-phrase (xeft--get-search-phrase))
+ (file-path (expand-file-name (concat search-phrase ".txt")
+ xeft-directory))
+ (exists-p (file-exists-p file-path)))
+ ;; If there is no match, create the file without confirmation,
+ ;; otherwise prompt for confirmation. NOTE: this is not DRY, but
+ ;; should be ok.
+ (when (or (search-forward "Press RET to create a new note" nil t)
+ (y-or-n-p (format "Create file `%s'.txt? " search-phrase)))
+ (find-file file-path)
+ (unless exists-p
+ (insert search-phrase "\n\n")
+ (save-buffer))
+ (run-hooks 'xeft-find-file-hook))))
+
+(defvar-local xeft--select-overlay nil
+ "Overlay used for highlighting selected search result.")
+
+(defun xeft--highlight-file-at-point ()
+ "Activate (highlight) the file excerpt button at point."
+ (when-let ((button (button-at (point))))
+ ;; Create the overlay if it doesn't exist yet.
+ (when (null xeft--select-overlay)
+ (setq xeft--select-overlay (make-overlay (button-start button)
+ (button-end button)))
+ (overlay-put xeft--select-overlay 'evaporate t)
+ (overlay-put xeft--select-overlay 'face 'xeft-selection))
+ ;; Move the overlay over the file.
+ (move-overlay xeft--select-overlay
+ (button-start button) (button-end button))))
+
+(defun xeft-next ()
+ "Move to next file excerpt."
+ (interactive)
+ (when (forward-button 1 nil nil t)
+ (xeft--highlight-file-at-point)))
+
+(defun xeft-previous ()
+ "Move to previous file excerpt."
+ (interactive)
+ (if (backward-button 1 nil nil t)
+ (xeft--highlight-file-at-point)
+ ;; Go to the end of the search phrase.
+ (goto-char (point-min))
+ (end-of-line)))
+
+;;; Draw
+
+(defvar xeft--preview-window nil
+ "Xeft shows file previews in this window.")
+
+(defvar xeft--cache nil
+ "An alist of (filename . file content).")
+
+(defun xeft--cached-insert (file)
+ "Insert the content of FILE, and cache it."
+ (if-let ((content (alist-get file xeft--cache nil nil #'equal)))
+ (insert content)
+ (insert-file-contents file)
+ (setf (alist-get file xeft--cache nil nil #'equal)
+ (buffer-string))))
+
+(defun xeft--get-search-phrase ()
+ "Return the search phrase. Assumes current buffer is a xeft buffer."
+ (save-excursion
+ (goto-char (point-min))
+ (string-trim
+ (buffer-substring-no-properties (point) (line-end-position)))))
+
+(defun xeft--find-file-at-point ()
+ "View file at point."
+ (interactive)
+ (find-file (button-get (button-at (point)) 'path))
+ (add-hook 'after-save-hook #'xeft--after-save-hook 0 t))
+
+(defun xeft--preview-file (file &optional select)
+ "View FILE in another window.
+If SELECT is non-nil, select the buffer after displaying it."
+ (interactive)
+ (let* ((buffer (find-file-noselect file))
+ (search-phrase (xeft--get-search-phrase))
+ (keyword-list (split-string search-phrase)))
+ (if (and (window-live-p xeft--preview-window)
+ (not (eq xeft--preview-window (selected-window))))
+ (with-selected-window xeft--preview-window
+ (switch-to-buffer buffer))
+ (setq xeft--preview-window
+ (display-buffer
+ buffer '((display-buffer-use-some-window
+ display-buffer-in-direction
+ display-buffer-pop-up-window)
+ . ((inhibit-same-window . t)
+ (direction . right)
+ (window-width
+ . (lambda (win)
+ (let ((width (window-width)))
+ (when (< width 50)
+ (window-resize
+ win (- 50 width) t))))))))))
+ (if select (select-window xeft--preview-window))
+ (with-current-buffer buffer
+ (xeft--highlight-matched keyword-list)
+ (run-hooks 'xeft-find-file-hook))))
+
+(define-button-type 'xeft-excerpt
+ 'action (lambda (button)
+ ;; If the file is no already highlighted, highlight it
+ ;; first.
+ (when (not (and xeft--select-overlay
+ (overlay-buffer xeft--select-overlay)
+ (<= (overlay-start xeft--select-overlay)
+ (button-start button)
+ (overlay-end xeft--select-overlay))))
+ (goto-char (button-start button))
+ (xeft--highlight-file-at-point))
+ (xeft--preview-file (button-get button 'path)))
+ 'keymap (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map button-map)
+ (define-key map (kbd "RET") #'xeft--find-file-at-point)
+ (define-key map (kbd "SPC") #'push-button)
+ map)
+ 'help-echo "Open this file"
+ 'follow-link t
+ 'face 'default
+ 'mouse-face 'xeft-selection)
+
+(defun xeft--highlight-search-phrase ()
+ "Highlight search phrases in buffer."
+ (let ((keyword-list (split-string (xeft--get-search-phrase)))
+ (inhibit-read-only t))
+ (dolist (keyword keyword-list)
+ (goto-char (point-min))
+ (forward-line 2)
+ ;; We use overlay because overlay allows face composition.
+ ;; So we can have bold + underline.
+ (while (search-forward keyword nil t)
+ (let ((ov (make-overlay (match-beginning 0)
+ (match-end 0))))
+ (overlay-put ov 'face 'xeft-inline-highlight)
+ (overlay-put ov 'xeft-highlight t)
+ (overlay-put ov 'evaporate t))))))
+
+(defun xeft--insert-file-excerpt (file search-phrase)
+ "Insert an excerpt for FILE at point.
+This excerpt contains note title and content excerpt and is
+clickable. FILE should be an absolute path. SEARCH-PHRASE is the
+search phrase the user typed."
+ (let ((excerpt-len (floor (* 2.7 (1- (window-width)))))
+ (last-search-term
+ (car (last (split-string search-phrase))))
+ title excerpt)
+ (with-current-buffer (xeft--work-buffer)
+ (widen)
+ (erase-buffer)
+ (xeft--cached-insert file)
+ (goto-char (point-min))
+ (search-forward "#+TITLE: " (line-end-position) t)
+ (setq title (buffer-substring-no-properties
+ (point) (line-end-position)))
+ (when (eq title "") (setq title "no title"))
+ (forward-line)
+ (narrow-to-region (point) (point-max))
+ ;; Grab excerpt.
+ (setq excerpt (string-trim
+ (replace-regexp-in-string
+ "[[:space:]]+"
+ " "
+ (if (and last-search-term
+ (search-forward last-search-term nil t))
+ (buffer-substring-no-properties
+ (max (- (point) (/ excerpt-len 2))
+ (point-min))
+ (min (+ (point) (/ excerpt-len 2))
+ (point-max)))
+ (buffer-substring-no-properties
+ (point)
+ (min (+ (point) excerpt-len)
+ (point-max))))))))
+ ;; Now we insert the excerpt
+ (let ((start (point)))
+ (insert (propertize title 'face '(:weight bold))
+ "\n"
+ (propertize excerpt 'face '(:weight light))
+ "\n\n")
+ ;; If we use overlay (with `make-button'), the button's face
+ ;; will override the bold and light face we specified above.
+ (make-text-button start (- (point) 2)
+ :type 'xeft-excerpt
+ 'path file))))
+
+;;; Refresh and search
+
+(defun xeft-refresh-full ()
+ "Refresh and display _all_ results."
+ (interactive)
+ (xeft-refresh t))
+
+(defun xeft--file-list ()
+ "Return a list of all files in ‘xeft-directory’."
+ (cl-remove-if-not
+ (lambda (file)
+ (and (file-regular-p file)
+ (not (string-prefix-p
+ "." (file-name-base file)))))
+ (directory-files xeft-directory t nil t)))
+
+(defun xeft-refresh (&optional full)
+ "Search for notes and display their summaries.
+By default, only display the first 50 results. If FULL is
+non-nil, display all results."
+ (interactive)
+ (let ((search-phrase (xeft--get-search-phrase)))
+ (when (derived-mode-p 'xeft-mode)
+ (let* ((phrase-empty (equal search-phrase ""))
+ (file-list
+ (if phrase-empty
+ (cl-sort (xeft--file-list) #'file-newer-than-file-p)
+ (xeft-query-term search-phrase xeft-database
+ 0 (if full 2147483647 50)))))
+ (when (and (null full) (> (length file-list) 50))
+ (setq file-list (cl-subseq file-list 0 50)))
+ (let ((inhibit-read-only t)
+ (inhibit-modification-hooks t)
+ (orig-point (point)))
+ ;; Actually insert the new content.
+ (goto-char (point-min))
+ (forward-line 2)
+ (delete-region (point) (point-max))
+ (when (while-no-input
+ (let ((start (point)))
+ (insert (if file-list
+ (with-temp-buffer
+ (dolist (file file-list)
+ (xeft--insert-file-excerpt
+ file search-phrase))
+ (buffer-string))
+ ;; NOTE: this string is referred in
+ ;; ‘xeft-create-note’.
+ "Press RET to create a new note"))
+ ;; If we use (- start 2), emacs-rime cannot work.
+ (put-text-property (- start 1) (point)
+ 'read-only t))
+ (xeft--highlight-search-phrase)
+ (set-buffer-modified-p nil)
+ ;; Save excursion wouldn’t work since we erased the
+ ;; buffer and re-inserted contents.
+ (goto-char orig-point)
+ ;; Re-apply highlight.
+ (xeft--highlight-file-at-point))
+ (goto-char orig-point)))))))
+
+;;; Highlight matched phrases
+
+(defun xeft--highlight-matched (keyword-list)
+ "Highlight keywords in KEYWORD-LIST in the current buffer."
+ (save-excursion
+ ;; Add highlight overlays.
+ (dolist (keyword keyword-list)
+ (goto-char (point-min))
+ (while (search-forward keyword nil t)
+ (let ((ov (make-overlay (match-beginning 0)
+ (match-end 0))))
+ (overlay-put ov 'face 'xeft-preview-highlight)
+ (overlay-put ov 'xeft-highlight t))))
+ ;; Add cleanup hook.
+ (add-hook 'window-selection-change-functions
+ #'xeft--cleanup-highlight
+ 0 t)))
+
+(defun xeft--cleanup-highlight (window)
+ "Cleanup highlights in WINDOW."
+ (when (eq window (selected-window))
+ (let ((ov-list (overlays-in (point-min)
+ (point-max))))
+ (dolist (ov ov-list)
+ (when (overlay-get ov 'xeft-highlight)
+ (delete-overlay ov))))
+ (remove-hook 'window-selection-change-functions
+ #'xeft--cleanup-highlight
+ t)))
+
+(provide 'xeft)
+
+;;; xeft.el ends here
- [elpa] branch externals/xeft created (now 4bdb052d81), ELPA Syncer, 2023/01/13
- [elpa] externals/xeft afc8a69a52 01/55: init,
ELPA Syncer <=
- [elpa] externals/xeft b9759e2f57 02/55: Fix for Linux, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft f81dd92048 03/55: Some update, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft b3ff6bf5b6 04/55: Make search result more intuitive, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 290b146829 05/55: Remove caching, change to showing only 15 results, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 315126c9ae 08/55: Fix noexcept error, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 8e57d01aa6 10/55: Improve perceived latency, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft e7722e479e 14/55: Move the nonexcept qualifier to the correct place, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 8b3e653f6e 15/55: Really fix the nonexcept qualifier, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 2b1ff8402f 17/55: Various fixes, ELPA Syncer, 2023/01/13
- [elpa] externals/xeft 8850838345 19/55: Fix "create note" prompt, ELPA Syncer, 2023/01/13