diff --git a/interface/emacs/README.org b/interface/emacs/README.org new file mode 100644 index 00000000..b89e1e1e --- /dev/null +++ b/interface/emacs/README.org @@ -0,0 +1,44 @@ +* Emacs Semantic Search + /An Emacs interface for [[https://github.com/debanjum/semantic-search][semantic-search]]/ + +** Requirements + - Install and Run [[https://github.com/debanjum/semantic-search][semantic-search]] + +** Installation + - Direct Install + - Put ~semantic-search.el~ in your Emacs load path. For e.g ~/.emacs.d/lisp + + - Load via ~use-package~ in your ~/.emacs.d/init.el or .emacs file by adding below snippet + #+begin_src elisp + ;; Org-Semantic Search Library + (use-package semantic-search + :load-path "~/.emacs.d/lisp/semantic-search.el" + :bind ("C-c s" . 'semantic-search)) + #+end_src + + - Use [[https://github.com/quelpa/quelpa#installation][Quelpa]] + - Ensure [[https://github.com/quelpa/quelpa#installation][Quelpa]], [[https://github.com/quelpa/quelpa-use-package#installation][quelpa-use-package]] are installed + - Add below snippet to your ~/.emacs.d/init.el or .emacs config file and execute it. + #+begin_src elisp + ;; Org-Semantic Search Library + (use-package semantic-search + :quelpa (semantic-search :fetcher url :url "https://raw.githubusercontent.com/debanjum/semantic-search/master/interface/emacs/semantic-search.el") + :bind ("C-c s" . 'semantic-search)) + #+end_src + +** Usage + 1. Call ~semantic-search~ using keybinding ~C-c s~ or ~M-x semantic-search~ + + 2. Enter Query in Natural Language + + e.g "What is the meaning of life?" "What are my life goals?" + + 3. Wait for results + + *Note: It takes about 15s on a Mac M1 and a ~100K lines corpus of org-mode files* + + 4. (Optional) Narrow down results further + + Include/Exclude specific words from results by adding to query + + e.g "What is the meaning of life? -god +none" diff --git a/interface/emacs/semantic-search.el b/interface/emacs/semantic-search.el new file mode 100644 index 00000000..0532026d --- /dev/null +++ b/interface/emacs/semantic-search.el @@ -0,0 +1,103 @@ +;;; semantic-search.el --- Semantic search via Emacs + +;; Copyright (C) 2021-2022 Debanjum Singh Solanky + +;; Author: Debanjum Singh Solanky +;; Version: 0.1 +;; Keywords: search, org-mode, outlines, ledger +;; URL: http://github.com/debanjum/emacs-semantic-search + +;; This file is NOT part of GNU Emacs. + +;;; License: + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; This package enables semantic search on org-mode, beancount files +;; It is a wrapper package that interfaces with a transformer based ML model +;; The models semantic search capabilities are exposed via an HTTP API + +;;; Code: + +(require 'url) +(require 'json) + +(defcustom semantic-search--server-url "http://localhost:8000" + "Location of semantic search API server." + :group 'semantic-search + :type 'string) + +(defun semantic-search--extract-entries-as-org (json-response) + "Convert json response from API to org-mode entries" + ;; remove leading (, ) or SPC from extracted entries string + (replace-regexp-in-string + "^[\(\) ]" "" + ;; extract entries from response as single string and convert to entries + (format "%s" + (mapcar + (lambda (args) (format "* %s" (cdr (assoc 'Entry args)))) + json-response)))) + +(defun semantic-search--extract-entries-as-ledger (json-response) + "Convert json response from API to ledger entries" + ;; remove leading (, ) or SPC from extracted entries string + (replace-regexp-in-string + "^[\(\) ]" "" + ;; extract entries from response as single string and convert to entries + (format "%s" + (mapcar + (lambda (args) (format "* %s" (cdr (assoc 'Entry args)))) + json-response)))) + +(defun semantic-search--buffer-name-to-search-type (buffer-name) + (let ((file-extension (file-name-extension buffer-name))) + (cond + ((equal file-extension "bean") "ledger") + ((equal file-extension "org") "notes") + (t "notes")))) + +(defun semantic-search--construct-api-query (query search-type) + (let ((encoded-query (url-hexify-string query))) + (format "%s/search?q=%s&t=%s" semantic-search--server-url encoded-query search-type))) + +(defun semantic-search (query) + "Semantic search on org-mode content via semantic-search API" + (interactive "sQuery: ") + (let* ((search-type (semantic-search--buffer-name-to-search-type (buffer-name))) + (url (semantic-search--construct-api-query query search-type)) + (buff (get-buffer-create "*semantic-search*"))) + ;; get json response from api + (with-current-buffer buff + (let ((inhibit-read-only t)) + (erase-buffer) + (url-insert-file-contents url))) + ;; convert json response to org-mode entries + (with-current-buffer buff + (let ((inhibit-read-only t) + (json-response (json-parse-buffer :object-type 'alist))) + (erase-buffer) + (insert + (cond ((equal search-type "notes") (semantic-search--extract-entries-as-org json-response)) + ((equal search-type "ledger") (semantic-search--extract-entries-as-ledger json-response)) + (t (format "%s" json-response))))) + (cond ((equal search-type "notes") (org-mode)) + (t (fundamental-mode))) + (read-only-mode t)) + (switch-to-buffer buff))) + +(provide 'semantic-search) + +;;; semantic-search.el ends here