“实战Elisp”系列旨在讲述我使用Elisp定制Emacs的经验,抛砖引玉,还请广大Emacs同好不吝赐教——如果真的有广大Emacs用户的话,哈哈哈。

半年前我在这篇文章中展示了在Emacs中查阅笔记的效果——用emacs-request请求ElasticSearch查询关键字、基于helm以下拉菜单的方式展示查询结果的标题(即问题),最后打开浏览器查看笔记内容。稍加使用就发现一些不足之处:

  1. 需要离开Emacs打开浏览器。我更希望能在Emacs中查看;
  2. ES的响应内容为JSON,要快速地辨认出questionanswer字段比较困难;
  3. 一个问题的答案往往是多行的,但在浏览器中answer字段是以一行的形式展示的,不利于阅读。

为了解决这些问题,我实现了一种新的查看笔记内容的方式。

改良后的效果

image

实现方法

我的目标是:

  1. 在不脱离Emacs的情况下浏览笔记内容;
  2. 提供org-mode的语法高亮。

为此,先用被选中笔记的_id请求ES,取回完整的JSON。接着,将_source中的questionanswer字段的内容拼接在一起(以\n作为分隔符)。最后,在Emacs中新建一个buffer、启用org-mode、插入拼接后的内容,并设置该buffer为只读。大功告成!

完整的代码如下

;;; 调用ElasticSearch查询笔记
(require 'request)

(defun faq (query)
  "向ElasticSearch查询QUERY匹配的笔记"
  (let ((response))
    (request
     "http://localhost:9200/faq/_search"
     :data (encode-coding-string
            (json-encode
             (list
              (cons "query" (list
                             (cons "multi_match" (list
                                                  (cons "fields" (list "answer" "question"))
                                                  (cons "query" query)))))))
            'utf-8)
     :headers '(("Content-Type" . "application/json"))
     :parser 'buffer-string
     :success (cl-function
               (lambda (&key data &allow-other-keys)
                 (setq data (decode-coding-string data 'utf-8))
                 (setq response (json-read-from-string data))))
     :sync t)
    response))

(defun make-faq-candidates (response)
  "将查询ElasticSearch的结果构造为helm可以识别的candidates格式"
  (let ((hits (cdr (assoc 'hits (cdr (assoc 'hits response))))))
    (mapcar (lambda (doc)
              (let ((_source (cdr (assoc '_source doc))))
                (cons (cdr (assoc 'question _source))
                      ;; (cdr (assoc 'answer (assoc '_source doc)))
                      (cdr (assoc '_id doc)))))
            hits)))

(defvar faq-query nil)

(defun faq-candidates ()
  (make-faq-candidates (faq faq-query)))

;;; 创建新的buffer并将ElasticSearch的内容展示在其中
(defun show-faq (text)
  ;; 创建一个buffer,显示它并选中这个窗口
  (let ((buffer (get-buffer-create "*FAQ*")))
    (let ((window (display-buffer buffer)))
      (select-window window)
      ;; 用新的内容覆盖原来的内容
      (setq inhibit-read-only t)
      (org-mode)
      (erase-buffer)
      (insert text)
      (read-only-mode))))

(setq faq-helm-sources
      `((name . "FAQ at Emacs")
        (candidates . faq-candidates)
        (action . (lambda (candidate)
                    (let (response
                          (url (format "http://localhost:9200/faq/_doc/%s" candidate)))
                      (message "url is %s" url)
                      (request
                       url
                       :parser 'buffer-string
                       :success (cl-function
                                 (lambda (&key data &allow-other-keys)
                                   (setq data (decode-coding-string data 'utf-8))
                                   (setq response (json-read-from-string data))))
                       :sync t)
                      ;; 从文档中提取出问题和答案,拼装成原本在.org文件中的模样
                      (let ((answer (cdr (assoc 'answer (assoc '_source response))))
                            (question (cdr (assoc 'question (assoc '_source response)))))
                        (show-faq
                         (concat question "\n" answer))))))))

(defun lt-ask ()
  "交互式地从minibuffer中读取笔记的关键词并展示选项"
  (interactive)
  (let ((content (read-from-minibuffer "笔记关键词:")))
    (setq faq-query content)
    (helm :sources '(faq-helm-sources))))

与之前版本的差异主要在于:

  1. 变量faq-helm-sources中的action部分多了很多内容,主要是请求ES和拼接字段;
  2. 新增了show-faq函数用于显示问题及其答案。

后记

挺好奇各位读者朋友是怎么记笔记和看笔记的XD

阅读原文


用户bPGfS
169 声望3.7k 粉丝