Automatically Find Similar Notes on Emacs in Background (#827)

Khoj will find and display notes similar to the current entry in the side pane when
1. find similar is open in side pane and
2. cursor has moved to a new entry

### Major
- Find similar notes to current note at cursor automatically in background
- Only show headings of search result and increase default results count

### Minor
- Pass absolute path of file to index from khoj.el emacs client
- Update help message to only show the smaller set of new keybindings
- Fix edge cases in loading some chat sessions
This commit is contained in:
Debanjum
2024-06-23 07:36:11 +05:30
committed by GitHub
2 changed files with 205 additions and 127 deletions

View File

@@ -83,7 +83,7 @@
:group 'khoj
:type 'integer)
(defcustom khoj-results-count 5
(defcustom khoj-results-count 8
"Number of results to show in search and use for chat responses."
:group 'khoj
:type 'integer)
@@ -93,6 +93,11 @@
:group 'khoj
:type 'number)
(defcustom khoj-auto-find-similar t
"Should try find similar notes automatically."
:group 'khoj
:type 'boolean)
(defcustom khoj-api-key nil
"API Key to your Khoj. Default at https://app.khoj.dev/config#clients."
:group 'khoj
@@ -158,28 +163,18 @@ NO-PAGING FILTER))
(defun khoj--keybindings-info-message ()
"Show available khoj keybindings in-context, when khoj invoked."
(let ((enabled-content-types (khoj--get-enabled-content-types)))
(concat
"
(concat
"
Set Content Type
-------------------------\n"
("C-c RET | improve sort \n")
(when (member 'markdown enabled-content-types)
"C-x m | markdown\n")
(when (member 'org enabled-content-types)
"C-x o | org-mode\n")
(when (member 'image enabled-content-types)
"C-x i | image\n")
(when (member 'pdf enabled-content-types)
"C-x p | pdf\n"))))
"C-c RET | improve sort \n"))
(defvar khoj--rerank nil "Track when re-rank of results triggered.")
(defvar khoj--reference-count 0 "Track number of references currently in chat bufffer.")
(defun khoj--improve-sort () "Use cross-encoder to improve sorting of search results." (interactive) (khoj--incremental-search t))
(defun khoj--make-search-keymap (&optional existing-keymap)
"Setup keymap to configure Khoj search. Build of EXISTING-KEYMAP when passed."
(let ((enabled-content-types (khoj--get-enabled-content-types))
(kmap (or existing-keymap (make-sparse-keymap))))
(let ((kmap (or existing-keymap (make-sparse-keymap))))
(define-key kmap (kbd "C-c RET") #'khoj--improve-sort)
kmap))
@@ -194,6 +189,9 @@ Use `which-key` if available, else display simple message in echo area"
nil t t))
(message "%s" (khoj--keybindings-info-message))))
(defvar khoj--last-heading-pos nil
"The last heading position point was in.")
;; ----------------
;; Khoj Setup
@@ -249,12 +247,12 @@ for example), set this to the full interpreter path."
(make-obsolete-variable 'khoj-org-files 'khoj-index-files "1.2.0" 'set)
(defcustom khoj-index-files (org-agenda-files t t)
"List of org, markdown, pdf and other plaintext to index on khoj server."
"List of org, md, text, pdf to index on khoj server."
:type '(repeat string)
:group 'khoj)
(defcustom khoj-index-directories nil
"List of directories with org, markdown, pdf and other plaintext files to index on khoj server."
"List of directories with org, md, text, pdf to index on khoj server."
:type '(repeat string)
:group 'khoj)
@@ -407,8 +405,10 @@ Auto invokes setup steps on calling main entrypoint."
;; This is a temporary change. `khoj-org-directories', `khoj-org-files' are deprecated. They will be removed in a future release
(content-directories (or khoj-index-directories khoj-org-directories))
(content-files (or khoj-index-files khoj-org-files))
(files-to-index (or file-paths
(append (mapcan (lambda (dir) (directory-files-recursively dir "\\.\\(org\\|md\\|markdown\\|pdf\\|txt\\|rst\\|xml\\|htm\\|html\\)$")) content-directories) content-files)))
(files-to-index (mapcar
#'expand-file-name
(or file-paths
(append (mapcan (lambda (dir) (directory-files-recursively dir "\\.\\(org\\|md\\|markdown\\|pdf\\|txt\\|rst\\|xml\\|htm\\|html\\)$")) content-directories) content-files))))
(type-query (if (or (equal content-type "all") (not content-type)) "" (format "t=%s" content-type)))
(delete-files (-difference khoj--indexed-files files-to-index))
(inhibit-message t)
@@ -501,11 +501,19 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
;; -------------------------------------------
;; Render Response from Khoj server for Emacs
;; -------------------------------------------
(defun khoj--construct-find-similar-title (query)
"Construct title for find-similar QUERY."
(format "Similar to: %s"
(replace-regexp-in-string "^[#\\*]* " "" (car (split-string query "\n")))))
(defun khoj--extract-entries-as-markdown (json-response query)
"Convert JSON-RESPONSE, QUERY from API to markdown entries."
(defun khoj--extract-entries-as-markdown (json-response query is-find-similar)
"Convert JSON-RESPONSE, QUERY from API to markdown entries.
Use IS-FIND-SIMILAR bool to filter out first result.
As first result is the current entry at point."
(thread-last
json-response
;; filter our first result if is find similar as it'll be the current entry at point
((lambda (response) (if is-find-similar (seq-drop response 1) response)))
;; Extract and render each markdown entry from response
(mapcar (lambda (json-response-item)
(thread-last
@@ -516,14 +524,18 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
;; Standardize results to 2nd level heading for consistent rendering
(replace-regexp-in-string "^\#+" "##"))))
;; Render entries into markdown formatted string with query set as as top level heading
(format "# %s\n%s" query)
(format "# %s\n%s" (if is-find-similar (khoj--construct-find-similar-title query) query))
;; remove leading (, ) or SPC from extracted entries string
(replace-regexp-in-string "^[\(\) ]" "")))
(defun khoj--extract-entries-as-org (json-response query)
"Convert JSON-RESPONSE, QUERY from API to `org-mode' entries."
(defun khoj--extract-entries-as-org (json-response query is-find-similar)
"Convert JSON-RESPONSE, QUERY from API to `org-mode' entries.
Use IS-FIND-SIMILAR bool to filter out first result.
As first result is the current entry at point."
(thread-last
json-response
;; filter our first result if is find similar as it'll be the current entry at point
((lambda (response) (if is-find-similar (seq-drop response 1) response)))
;; Extract and render each org-mode entry from response
(mapcar (lambda (json-response-item)
(thread-last
@@ -534,14 +546,18 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
;; Standardize results to 2nd level heading for consistent rendering
(replace-regexp-in-string "^\*+" "**"))))
;; Render entries into org formatted string with query set as as top level heading
(format "* %s\n%s\n" query)
(format "* %s\n%s\n" (if is-find-similar (khoj--construct-find-similar-title query) query))
;; remove leading (, ) or SPC from extracted entries string
(replace-regexp-in-string "^[\(\) ]" "")))
(defun khoj--extract-entries-as-pdf (json-response query)
"Convert QUERY, JSON-RESPONSE from API with PDF results to `org-mode' entries."
(defun khoj--extract-entries-as-pdf (json-response query is-find-similar)
"Convert JSON-RESPONSE, QUERY from API to PDF entries.
Use IS-FIND-SIMILAR bool to filter out first result.
As first result is the current entry at point."
(thread-last
json-response
;; filter our first result if is find similar as it'll be the current entry at point
((lambda (response) (if is-find-similar (seq-drop response 1) response)))
;; Extract and render each pdf entry from response
(mapcar (lambda (json-response-item)
(thread-last
@@ -550,7 +566,7 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
;; Format pdf entry as a org entry string
(format "** %s\n\n"))))
;; Render entries into org formatted string with query set as as top level heading
(format "* %s\n%s\n" query)
(format "* %s\n%s\n" (if is-find-similar (khoj--construct-find-similar-title query) query))
;; remove leading (, ) or SPC from extracted entries string
(replace-regexp-in-string "^[\(\) ]" "")))
@@ -582,9 +598,13 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
;; remove trailing (, ) or SPC from extracted entries string
(replace-regexp-in-string "[\(\) ]$" ""))))
(defun khoj--extract-entries (json-response query)
"Convert JSON-RESPONSE, QUERY from API to text entries."
(defun khoj--extract-entries (json-response query is-find-similar)
"Convert JSON-RESPONSE, QUERY from API to text entries.
Use IS-FIND-SIMILAR bool to filter out first result.
As first result is the current entry at point."
(thread-last json-response
;; filter our first result if is find similar as it'll be the current entry at point
((lambda (response) (if is-find-similar (seq-drop response 1) response)))
;; extract and render entries from API response
(mapcar (lambda (json-response-item)
(thread-last
@@ -598,7 +618,7 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
;; Format entries as org entry string
(format "** %s"))))
;; Set query as heading in rendered results buffer
(format "* %s\n%s\n" query)
(format "* %s\n%s\n" (if is-find-similar (khoj--construct-find-similar-title query) query))
;; remove leading (, ) or SPC from extracted entries string
(replace-regexp-in-string "^[\(\) ]" "")
;; remove trailing (, ) or SPC from extracted entries string
@@ -614,13 +634,30 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
((and (member 'markdown enabled-content-types) (or (equal file-extension "markdown") (equal file-extension "md"))) "markdown")
(t khoj-default-content-type))))
(defun khoj--org-cycle-content (&optional arg)
"Show all headlines in the buffer, like a table of contents.
With numerical argument ARG, show content up to level ARG.
Simplified fork of `org-cycle-content' from Emacs 29.1 to work with >=27.1."
(interactive "p")
(save-excursion
(goto-char (point-max))
(let ((regexp (if (and (wholenump arg) (> arg 0))
(format "^\\*\\{1,%d\\} " arg)
"^\\*+ "))
(last (point)))
(while (re-search-backward regexp nil t)
(org-fold-region (line-end-position) last t 'outline)
(setq last (line-end-position 0))))))
;; --------------
;; Query Khoj API
;; --------------
(defun khoj--call-api (path &optional method params callback &rest cbargs)
"Sync call API at PATH with METHOD and query PARAMS as kv assoc list.
Return json parsed response as alist."
Optionally apply CALLBACK with JSON parsed response and CBARGS."
(let* ((url-request-method (or method "GET"))
(url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key))))
(param-string (if params (url-build-query-string params) ""))
@@ -639,7 +676,7 @@ Return json parsed response as alist."
(defun khoj--call-api-async (path &optional method params callback &rest cbargs)
"Async call to API at PATH with METHOD and query PARAMS as kv assoc list.
Return json parsed response as alist."
Optionally apply CALLBACK with JSON parsed response and CBARGS."
(let* ((url-request-method (or method "GET"))
(url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key))))
(param-string (if params (url-build-query-string params) ""))
@@ -662,40 +699,42 @@ Return json parsed response as alist."
"Get content types enabled for search from API."
(khoj--call-api "/api/config/types" "GET" nil `(lambda (item) (mapcar #'intern item))))
(defun khoj--query-search-api-and-render-results (query content-type buffer-name &optional rerank title)
"Query Khoj Search API with QUERY, CONTENT-TYPE and (optional) RERANK as query params
Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional) TITLE."
(let ((title (or title query))
(rerank (or rerank "false"))
(params `((q ,query) (t ,content-type) (r ,rerank) (n ,khoj-results-count)))
(path "/api/search"))
(defun khoj--query-search-api-and-render-results (query content-type buffer-name &optional rerank is-find-similar)
"Query Khoj Search API with QUERY, CONTENT-TYPE and RERANK as query params.
Render search results in BUFFER-NAME using CONTENT-TYPE and QUERY.
Filter out first similar result if IS-FIND-SIMILAR set."
(let* ((rerank (or rerank "false"))
(params `((q ,query) (t ,content-type) (r ,rerank) (n ,khoj-results-count)))
(path "/api/search"))
(khoj--call-api-async path
"GET"
params
'khoj--render-search-results
content-type title buffer-name)))
content-type query buffer-name is-find-similar)))
(defun khoj--render-search-results (json-response content-type query buffer-name)
(defun khoj--render-search-results (json-response content-type query buffer-name &optional is-find-similar)
"Render search results in BUFFER-NAME using JSON-RESPONSE, CONTENT-TYPE, QUERY.
Filter out first similar result if IS-FIND-SIMILAR set."
;; render json response into formatted entries
(with-current-buffer buffer-name
(let ((inhibit-read-only t))
(let ((is-find-similar (or is-find-similar nil))
(inhibit-read-only t))
(erase-buffer)
(insert
(cond ((equal content-type "org") (khoj--extract-entries-as-org json-response query))
((equal content-type "markdown") (khoj--extract-entries-as-markdown json-response query))
((equal content-type "pdf") (khoj--extract-entries-as-pdf json-response query))
(cond ((equal content-type "org") (khoj--extract-entries-as-org json-response query is-find-similar))
((equal content-type "markdown") (khoj--extract-entries-as-markdown json-response query is-find-similar))
((equal content-type "pdf") (khoj--extract-entries-as-pdf json-response query is-find-similar))
((equal content-type "image") (khoj--extract-entries-as-images json-response query))
(t (khoj--extract-entries json-response query))))
(t (khoj--extract-entries json-response query is-find-similar))))
(cond ((or (equal content-type "all")
(equal content-type "pdf")
(equal content-type "org"))
(progn (visual-line-mode)
(org-mode)
(setq-local
org-startup-folded "showall"
org-hide-leading-stars t
org-startup-with-inline-images t)
(org-set-startup-visibility)))
(khoj--org-cycle-content 2)))
((equal content-type "markdown") (progn (markdown-mode)
(visual-line-mode)))
((equal content-type "image") (progn (shr-render-region (point-min) (point-max))
@@ -712,60 +751,61 @@ Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional)
;; Khoj Chat
;; ----------------
(defun khoj--chat ()
"Chat with Khoj."
(defun khoj--chat (&optional session-id)
"Chat with Khoj in session with SESSION-ID."
(interactive)
(when (not (get-buffer khoj--chat-buffer-name))
(khoj--load-chat-session khoj--chat-buffer-name))
(khoj--open-side-pane khoj--chat-buffer-name)
(when (or session-id (not (get-buffer khoj--chat-buffer-name)))
(khoj--load-chat-session khoj--chat-buffer-name session-id))
(let ((query (read-string "Query: ")))
(when (not (string-empty-p query))
(khoj--query-chat-api-and-render-messages query khoj--chat-buffer-name))))
(khoj--query-chat-api-and-render-messages query khoj--chat-buffer-name session-id))))
(defun khoj--open-side-pane (buffer-name)
"Open Khoj BUFFER-NAME in right side pane."
(if (get-buffer-window-list buffer-name)
;; if window is already open, switch to it
(progn
(select-window (get-buffer-window buffer-name))
(switch-to-buffer buffer-name))
;; else if window is not open, open it as a right-side window pane
(let ((bottomright-window (some-window (lambda (window) (and (window-at-side-p window 'right) (window-at-side-p window 'bottom))))))
(progn
;; Select the right-most window
(select-window bottomright-window)
;; if bottom-right window is not a vertical pane, split it vertically, else use the existing bottom-right vertical window
(let ((khoj-window (if (window-at-side-p bottomright-window 'left)
(split-window-right)
bottomright-window)))
;; Set the buffer in the khoj window
(set-window-buffer khoj-window buffer-name)
;; Switch to the khoj window
(select-window khoj-window)
;; Resize the window to 1/3 of the frame width
(window-resize khoj-window
(- (truncate (* 0.33 (frame-width))) (window-width))
t))))))
(save-selected-window
(if (get-buffer-window-list buffer-name)
;; if window is already open, switch to it
(progn
(select-window (get-buffer-window buffer-name))
(switch-to-buffer buffer-name))
;; else if window is not open, open it as a right-side window pane
(let ((bottomright-window (some-window (lambda (window) (and (window-at-side-p window 'right) (window-at-side-p window 'bottom))))))
(progn
;; Select the right-most window
(select-window bottomright-window)
;; if bottom-right window is not a vertical pane, split it vertically, else use the existing bottom-right vertical window
(let ((khoj-window (if (window-at-side-p bottomright-window 'left)
(split-window-right)
bottomright-window)))
;; Set the buffer in the khoj window
(set-window-buffer khoj-window buffer-name)
;; Switch to the khoj window
(select-window khoj-window)
;; Resize the window to 1/3 of the frame width
(window-resize khoj-window
(- (truncate (* 0.33 (frame-width))) (window-width))
t)))))
(goto-char (point-max))))
(defun khoj--load-chat-session (buffer-name &optional session-id)
"Load Khoj Chat conversation history into BUFFER-NAME."
"Load Khoj Chat conversation history from SESSION-ID into BUFFER-NAME."
(setq khoj--reference-count 0)
(let ((inhibit-read-only t)
(json-response (cdr (assoc 'chat (cdr (assoc 'response (khoj--get-chat-session session-id)))))))
(with-current-buffer (get-buffer-create buffer-name)
(erase-buffer)
(insert "* Khoj Chat\n")
(when json-response
(thread-last
json-response
;; generate chat messages from Khoj Chat API response
(mapcar #'khoj--format-chat-response)
;; insert chat messages into Khoj Chat Buffer
(mapc #'insert)))
(progn
(erase-buffer)
(insert "* Khoj Chat\n")
(when json-response
(thread-last
json-response
;; generate chat messages from Khoj Chat API response
(mapcar #'khoj--format-chat-response)
;; insert chat messages into Khoj Chat Buffer
(mapc #'insert)))
(org-mode)
(khoj--add-hover-text-to-footnote-refs (point-min))
;; commented add-hover-text func due to perf issues with the implementation
;;(khoj--add-hover-text-to-footnote-refs (point-min))
;; render reference footnotes as superscript
(setq-local
org-startup-folded "showall"
@@ -783,10 +823,11 @@ Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional)
;; enable minor modes for khoj chat
(visual-line-mode)
(read-only-mode t)))))
(read-only-mode t)))
(khoj--open-side-pane buffer-name)))
(defun khoj--close ()
"Kill Khoj buffer and window"
"Kill Khoj buffer and window."
(interactive)
(progn
(kill-buffer (current-buffer))
@@ -816,8 +857,8 @@ Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional)
;; show definition on hover on footnote reference
(overlay-put overlay 'help-echo it)))))))
(defun khoj--query-chat-api-and-render-messages (query buffer-name)
"Send QUERY to Khoj Chat. Render the chat messages from exchange in BUFFER-NAME."
(defun khoj--query-chat-api-and-render-messages (query buffer-name &optional session-id)
"Send QUERY to Chat SESSION-ID. Render the chat messages in BUFFER-NAME."
;; render json response into formatted chat messages
(with-current-buffer (get-buffer buffer-name)
(let ((inhibit-read-only t)
@@ -826,16 +867,19 @@ Render results in BUFFER-NAME using search results, CONTENT-TYPE and (optional)
(insert
(khoj--render-chat-message query "you" query-time))
(khoj--query-chat-api query
session-id
#'khoj--format-chat-response
#'khoj--render-chat-response buffer-name))))
(defun khoj--query-chat-api (query callback &rest cbargs)
"Send QUERY to Khoj Chat API and call CALLBACK with the response.
CBARGS are optional additional arguments to pass to CALLBACK."
(khoj--call-api-async "/api/chat"
"GET"
`(("q" ,query) ("n" ,khoj-results-count))
callback cbargs))
(defun khoj--query-chat-api (query session-id callback &rest cbargs)
"Send QUERY for SESSION-ID to Khoj Chat API.
Call CALLBACK func with response and CBARGS."
(let ((params `(("q" ,query) ("n" ,khoj-results-count))))
(when session-id (push `("conversation_id" ,session-id) params))
(khoj--call-api-async "/api/chat"
"GET"
params
callback cbargs)))
(defun khoj--get-chat-sessions ()
"Get all chat sessions from Khoj server."
@@ -863,8 +907,7 @@ CBARGS are optional additional arguments to pass to CALLBACK."
(defun khoj--open-conversation-session ()
"Menu to select Khoj conversation session to open."
(let ((selected-session-id (khoj--select-conversation-session "Open")))
(khoj--load-chat-session khoj--chat-buffer-name selected-session-id)
(khoj--open-side-pane khoj--chat-buffer-name)))
(khoj--load-chat-session khoj--chat-buffer-name selected-session-id)))
(defun khoj--create-chat-session ()
"Create new chat session."
@@ -872,21 +915,21 @@ CBARGS are optional additional arguments to pass to CALLBACK."
(defun khoj--new-conversation-session ()
"Create new Khoj conversation session."
(let* ((session (khoj--create-chat-session))
(new-session-id (cdr (assoc 'conversation_id session))))
(khoj--load-chat-session khoj--chat-buffer-name new-session-id)
(khoj--open-side-pane khoj--chat-buffer-name)))
(thread-last
(khoj--create-chat-session)
(assoc 'conversation_id)
(cdr)
(khoj--chat)))
(defun khoj--delete-chat-session (session-id)
"Delete new chat session."
"Delete chat session with SESSION-ID."
(khoj--call-api "/api/chat/history" "DELETE" `(("conversation_id" ,session-id))))
(defun khoj--delete-conversation-session ()
"Delete new Khoj conversation session."
(let* ((selected-session-id (khoj--select-conversation-session "Delete"))
(session (khoj--delete-chat-session selected-session-id)))
(khoj--load-chat-session khoj--chat-buffer-name)
(khoj--open-side-pane khoj--chat-buffer-name)))
(thread-last
(khoj--select-conversation-session "Delete")
(khoj--delete-chat-session)))
(defun khoj--render-chat-message (message sender &optional receive-date)
"Render chat messages as `org-mode' list item.
@@ -923,10 +966,11 @@ RECEIVE-DATE is the message receive date."
(format "\n[fn:%x] %s" khoj--reference-count)))))
(defun khoj--generate-online-reference (reference)
"Create `org-mode' footnotes for online REFERENCE."
(setq khoj--reference-count (1+ khoj--reference-count))
(let ((link (cdr (assoc 'link reference)))
(title (cdr (assoc 'title reference)))
(description (cdr (assoc 'description reference))))
(let* ((link (cdr (assoc 'link reference)))
(title (or (cdr (assoc 'title reference)) link))
(description (or (cdr (assoc 'description reference)) title)))
(cons
(propertize (format "^{ [fn:%x]}" khoj--reference-count) 'help-echo (format "%s\n%s" link description))
(thread-last
@@ -935,8 +979,8 @@ RECEIVE-DATE is the message receive date."
(replace-regexp-in-string "\n\n" "\n")
(format "\n[fn:%x] [[%s][%s]]\n%s\n" khoj--reference-count link title)))))
(defun khoj--extract-online-references (result-types searches)
"Extract link, title, and description of specified RESULT-TYPES from SEARCHES."
(defun khoj--extract-online-references (result-types query-result-pairs)
"Extract link, title and description from RESULT-TYPES in QUERY-RESULT-PAIRS."
(let ((result '()))
(-map
(lambda (search)
@@ -958,10 +1002,11 @@ RECEIVE-DATE is the message receive date."
(list (cdr search-result))
(cdr search-result))))
search-results)))
searches)
query-result-pairs)
result))
(defun khoj--render-chat-response (response buffer-name)
"Insert chat message from RESPONSE into BUFFER-NAME."
(with-current-buffer (get-buffer buffer-name)
(let ((start-pos (point))
(inhibit-read-only t))
@@ -975,7 +1020,8 @@ RECEIVE-DATE is the message receive date."
(re-search-backward "^\*+ 🏮" nil t)))))
(defun khoj--format-chat-response (json-response &optional callback &rest cbargs)
"Render chat message using JSON-RESPONSE from Khoj Chat API."
"Format chat message using JSON-RESPONSE from Khoj Chat API.
Run CALLBACK with CBARGS on formatted message."
(let* ((message (cdr (or (assoc 'response json-response) (assoc 'message json-response))))
(sender (cdr (assoc 'by json-response)))
(receive-date (cdr (assoc 'created json-response)))
@@ -1087,6 +1133,16 @@ RECEIVE-DATE is the message receive date."
;; Similar Search
;; --------------
(defun khoj--get-current-outline-entry-pos ()
"Get heading position of current outline section."
;; get heading position of current outline entry
(cond
;; when at heading of entry
((looking-at outline-regexp)
(point))
;; when within entry
(t (save-excursion (outline-previous-heading) (point)))))
(defun khoj--get-current-outline-entry-text ()
"Get text under current outline section."
(string-trim
@@ -1130,10 +1186,6 @@ Paragraph only starts at first text after blank line."
;; get paragraph, if in text mode
(t
(khoj--get-current-paragraph-text))))
;; extract heading to show in result buffer from query
(query-title
(format "Similar to: %s"
(replace-regexp-in-string "^[#\\*]* " "" (car (split-string query "\n")))))
(buffer-name (get-buffer-create khoj--search-buffer-name)))
(progn
(khoj--query-search-api-and-render-results
@@ -1141,9 +1193,35 @@ Paragraph only starts at first text after blank line."
content-type
buffer-name
rerank
query-title)
(khoj--open-side-pane buffer-name)
(goto-char (point-min)))))
t)
(khoj--open-side-pane buffer-name))))
(defun khoj--auto-find-similar ()
"Call find similar on current element, if point has moved to a new element."
;; Call find similar
(when (and (derived-mode-p 'org-mode)
(org-element-at-point)
(not (string= (buffer-name (current-buffer)) khoj--search-buffer-name))
(get-buffer-window khoj--search-buffer-name))
(let ((current-heading-pos (khoj--get-current-outline-entry-pos)))
(unless (eq current-heading-pos khoj--last-heading-pos)
(setq khoj--last-heading-pos current-heading-pos)
(khoj--find-similar)))))
(defun khoj--setup-auto-find-similar ()
"Setup automatic call to find similar to current element."
(if khoj-auto-find-similar
(add-hook 'post-command-hook #'khoj--auto-find-similar)
(remove-hook 'post-command-hook #'khoj--auto-find-similar)))
(defun khoj-toggle-auto-find-similar ()
"Toggle automatic call to find similar to current element."
(interactive)
(setq khoj-auto-find-similar (not khoj-auto-find-similar))
(khoj--setup-auto-find-similar)
(if khoj-auto-find-similar
(message "Auto find similar enabled")
(message "Auto find similar disabled")))
;; ---------

View File

@@ -64,7 +64,7 @@
")))
(should
(equal
(khoj--extract-entries-as-markdown json-response-from-khoj-backend user-query)
(khoj--extract-entries-as-markdown json-response-from-khoj-backend user-query nil)
"\
# Become God\n\
## Upgrade\n\
@@ -100,7 +100,7 @@ Rule everything\n\n"))))
")))
(should
(equal
(khoj--extract-entries-as-org json-response-from-khoj-backend user-query)
(khoj--extract-entries-as-org json-response-from-khoj-backend user-query nil)
"\
* Become God\n\
** Upgrade\n\