2
;; Copyright 2010 Yusuke Kawakami
4
;; Licensed under the Apache License, Version 2.0 (the "License");
5
;; you may not use this file except in compliance with the License.
6
;; You may obtain a copy of the License at
8
;; http://www.apache.org/licenses/LICENSE-2.0
10
;; Unless required by applicable law or agreed to in writing, software
11
;; distributed under the License is distributed on an "AS IS" BASIS,
12
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
;; See the License for the specific language governing permissions and
14
;; limitations under the License.
17
;; evernote-mode home page is at:
18
;; Author: Yusuke Kawakami
22
;; This emacs lisp offers the interactive functions to open, edit, and update notes of Evernote.
23
;; The minor mode Evernote-mode is applied to the buffer editing a note of Evernote.
25
;; Please copy this file into emacs lisp library directory or place it in
26
;; a directory (for example "~/lisp") and write $HOME/.emacs like this.
28
;; (add-to-list 'load-path "~/lisp")
29
;; (require 'evernote-mode)
30
;; (global-set-key "\C-cec" 'evernote-create-note)
31
;; (global-set-key "\C-ceo" 'evernote-open-note)
32
;; (global-set-key "\C-ces" 'evernote-search-notes)
33
;; (global-set-key "\C-ceS" 'evernote-do-saved-search)
34
;; (global-set-key "\C-cew" 'evernote-write-note)
36
;; There is one hooks, evernotes-mode-hook.
37
;; The usage of the hook is shown as follows.
39
;; (setq evernote-mode-hook
45
(defvar evernote-mode nil
46
"Non-nil if Evernote mode is enabled.")
47
(make-variable-buffer-local 'evernote-mode)
49
(defvar evernote-note-guid nil
50
"Note guid of the buffer")
51
(make-variable-buffer-local 'evernote-note-guid)
53
(defvar evernote-note-name nil
54
"Note name of the buffer")
55
(make-variable-buffer-local 'evernote-note-name)
57
(defvar evernote-note-tags nil
59
(make-variable-buffer-local 'evernote-note-tags)
61
(defvar evernote-note-edit-mode nil
62
"Edit mode of the buffer")
63
(make-variable-buffer-local 'evernote-note-edit-mode)
65
(defvar evernote-mode-map (make-sparse-keymap)
66
"Keymap used in evernote mode.")
67
(define-key evernote-mode-map "\C-x\C-s" 'evernote-save-note)
68
(define-key evernote-mode-map "\C-cet" 'evernote-edit-tags)
69
(define-key evernote-mode-map "\C-cee" 'evernote-change-edit-mode)
70
(define-key evernote-mode-map "\C-cer" 'evernote-rename-note)
71
(define-key evernote-mode-map "\C-ced" 'evernote-delete-note)
73
(defvar evernote-read-note-map
74
(copy-keymap minibuffer-local-completion-map))
75
(define-key evernote-read-note-map [tab] 'evernote-read-note-completion)
76
(define-key evernote-read-note-map "\C-i" 'evernote-read-note-completion)
77
(define-key evernote-read-note-map "\C-m" 'evernote-read-note-finish)
78
(define-key evernote-read-note-map " " 'self-insert-command)
81
(defvar evernote-search-mode-map (copy-keymap global-map)
82
"Keymap used in evernote search mode.")
83
(define-key evernote-search-mode-map "\C-m" 'evernote-select-note-in-search-mode)
85
(defvar evernote-search-mode-formatted-name-displayed-name-alist nil
86
"Alist from formatted names to names used only in evernote-search-mode buffer")
87
(make-variable-buffer-local 'evernote-search-mode-formatted-name-displayed-name-alist)
90
(defconst evernote-command-output-buffer-name "*Evernote-Client-Output*")
91
(defconst evernote-error-ok 0)
92
(defconst evernote-error-fail 100)
93
(defconst evernote-error-parse 101)
94
(defconst evernote-error-unknown 1)
95
(defconst evernote-error-bad-data-format 2)
96
(defconst evernote-error-permission-denied 3)
97
(defconst evernote-error-internal-error 4)
98
(defconst evernote-error-data-required 5)
99
(defconst evernote-error-limit-reached 6)
100
(defconst evernote-error-quota-reached 7)
101
(defconst evernote-error-invalid-auth 8)
102
(defconst evernote-error-auth-expired 9)
103
(defconst evernote-error-data-conflict 10)
104
(defconst evernote-error-enml-validation 11)
105
(defconst evernote-error-shared-unavailable 12)
109
;; Interactive functions.
113
(defun evernote-open-note ()
116
(evernote-command-with-auth
118
(evernote-open-note-common
119
(evernote-command-get-note-list-from-tags
120
(evernote-completing-read-multiple
121
"Tags used for search (comma separated form. default search all tags):"
122
(evernote-get-tag-alist
123
(evernote-command-get-tag-list))
127
(defun evernote-search-notes ()
128
"Search notes with query and open a note among them"
130
(evernote-command-with-auth
132
(evernote-open-note-common
133
(evernote-command-get-note-list-from-query
134
(read-string "Query:"))
138
(defun evernote-do-saved-search ()
139
"Do a saved search and open a note"
141
(evernote-command-with-auth
143
(evernote-open-note-common
144
(evernote-command-get-note-list-from-query
146
(evernote-get-search-alist (evernote-command-get-search-list))))
158
(defun evernote-open-note-common (note-command-output &optional display-completion)
159
"Common procedure of opening a note"
160
(let (note-attr note-guid note-name note-edit-mode note-tags opened-buf)
161
(setq note-attr (evernote-read-note-attr note-command-output display-completion))
162
(setq note-guid (evernote-assoc-cdr 'guid note-attr)
163
note-name (evernote-assoc-cdr 'name note-attr)
164
note-edit-mode (evernote-assoc-cdr 'edit-mode note-attr)
165
note-tags (evernote-assoc-cdr 'tags note-attr))
166
(setq opened-buf (evernote-find-opened-buffer note-guid))
168
(evernote-move-cursor-to-window opened-buf)
170
(evernote-create-note-buffer
175
(evernote-command-get-note-content note-guid note-edit-mode))))))
178
(defun evernote-save-note ()
181
(if (and evernote-note-guid (buffer-modified-p))
182
(evernote-command-with-auth
185
(evernote-command-update-note
190
evernote-note-edit-mode)
191
(set-buffer-modified-p nil))))
192
(message "(No changes need to be saved)")))
195
(defun evernote-create-note ()
198
(evernote-command-with-auth
200
(let (tags name edit-mode)
202
(evernote-completing-read-multiple
203
"Attached Tags (comma separated form):"
204
(evernote-get-tag-alist
205
(evernote-command-get-tag-list))))
206
(setq name (read-string "Note name:"))
207
(setq edit-mode "TEXT")
208
(switch-to-buffer (generate-new-buffer name))
209
(evernote-command-create-note (current-buffer)
213
(setq evernote-note-guid (evernote-eval-command-result)
214
evernote-note-name name
215
evernote-note-tags tags
216
evernote-note-edit-mode edit-mode)
218
(evernote-update-mode-line)
219
(set-buffer-modified-p nil)))))
222
(defun evernote-write-note ()
223
"Write buffer to a note"
225
(evernote-command-with-auth
227
(let (tags name edit-mode)
229
(evernote-completing-read-multiple
230
"Attached Tags (comma separated form):"
231
(evernote-get-tag-alist
232
(evernote-command-get-tag-list))))
233
(setq name (read-string "Note name:" (buffer-name)))
234
(setq edit-mode (evernote-read-edit-mode "TEXT"))
235
(evernote-command-create-note (current-buffer)
239
(setq evernote-note-guid (evernote-eval-command-result)
240
evernote-note-name name
241
evernote-note-tags tags
242
evernote-note-edit-mode edit-mode)
243
(if (not evernote-mode)
245
(rename-buffer name t)
246
(evernote-update-mode-line)
247
(set-buffer-modified-p nil)))))
250
(defun evernote-edit-tags ()
251
"Add or remove tags from/to the note"
253
(evernote-command-with-auth
257
(setq evernote-note-tags
258
(evernote-completing-read-multiple
259
"Change attached Tags (comma separated form):"
260
(evernote-get-tag-alist (evernote-command-get-tag-list))
261
nil nil (mapconcat #'identity evernote-note-tags ",")))
262
(evernote-update-mode-line)
263
(set-buffer-modified-p t))))))
266
(defun evernote-change-edit-mode ()
267
"Change edit mode of the note"
269
(evernote-command-with-auth
273
(setq evernote-note-edit-mode
274
(evernote-read-edit-mode evernote-note-edit-mode))
275
(evernote-update-mode-line)
276
(set-buffer-modified-p t))))))
279
(defun evernote-rename-note ()
284
(setq evernote-note-name
285
(read-string "New note name:" evernote-note-name))
286
(rename-buffer evernote-note-name t)
287
(set-buffer-modified-p t))))
290
(defun evernote-delete-note ()
293
(if (and evernote-mode
294
(y-or-n-p "Do you really want to remove this note? "))
295
(evernote-command-with-auth
297
(evernote-command-delete-note evernote-note-guid)
298
(kill-buffer (current-buffer))))))
301
(defun evernote-create-search ()
302
"Create a saved search"
304
(evernote-command-with-auth
306
(evernote-command-create-search
307
(read-string "Saved Search Name:")
308
(read-string "Query:")))))
315
(defun evernote-command-with-auth (func &rest args)
316
"Add or remove tags from/to the note"
317
(let ((error-code (catch 'error (apply func args))))
318
(if (or (eq error-code evernote-error-invalid-auth)
319
(eq error-code evernote-error-auth-expired))
320
(let ((error-code (catch 'error (evernote-command-login))))
321
(if (eq error-code t)
323
(message (evernote-error-message error-code))))
324
(message (evernote-error-message error-code)))))
327
(defun evernote-get-tag-alist (tag-command-out)
328
"Get the alist for completion from command output"
329
(let (acc collect-tagname)
330
(setq collect-tagname
334
(list (evernote-assoc-cdr 'name node))
336
(mapc collect-tagname (evernote-assoc-cdr 'children node))))
337
(mapcar collect-tagname tag-command-out)
341
(defun evernote-get-search-alist (search-command-out)
342
"Get the alist for completion from command output"
346
(evernote-assoc-cdr 'name elem)
351
(defun evernote-read-note-attr (note-command-out &optional display-completion)
352
"Prompts a note name and returns a note attribute"
353
(let ((name-num-hash (make-hash-table :test #'equal))
354
evernote-note-displayed-name-attr-alist ; used in evernote-search-mode
355
evenote-note-displayed-name-formatted-name-alist) ; used in evernote-search-mode
358
(let (name displayed-name)
359
(setq name (evernote-assoc-cdr 'name attr))
361
(evernote-get-displayed-note-name name name-num-hash))
362
(setq evernote-note-displayed-name-attr-alist
363
(cons (cons displayed-name attr)
364
evernote-note-displayed-name-attr-alist))
365
(setq evenote-note-displayed-name-formatted-name-alist
366
(cons (cons displayed-name
368
(evernote-assoc-cdr 'updated attr)
370
evenote-note-displayed-name-formatted-name-alist))))
372
(setq evernote-note-displayed-name-attr-alist
373
(nreverse evernote-note-displayed-name-attr-alist))
374
(setq evenote-note-displayed-name-formatted-name-alist
375
(nreverse evenote-note-displayed-name-formatted-name-alist))
376
(if display-completion
377
(evernote-display-note-completion-buf
378
evenote-note-displayed-name-formatted-name-alist))
379
(evernote-assoc-cdr (read-from-minibuffer "Note:"
380
nil evernote-read-note-map)
381
evernote-note-displayed-name-attr-alist)))
384
(defun evernote-get-displayed-note-name (name name-hash)
385
"Get displayed note name from the read note name"
386
(let ((num (gethash name name-num-hash))
391
(setq result (format "%s(%d)" name num))
392
(puthash name num name-num-hash))
393
(setq result (substring name 0))
394
(puthash name 1 name-num-hash))
398
(defun evernote-read-note-completion ()
399
"Complete note name and display completion list"
401
(let (word result start)
402
(setq word (evernote-get-minibuffer-string))
403
(setq result (try-completion word evernote-note-displayed-name-attr-alist))
404
;(evernote-minibuffer-tmp-message (concat "[" word "]"))
407
(evernote-minibuffer-tmp-message "[Sole Completion]"))
410
(evernote-minibuffer-tmp-message "[No Match]"))
411
((string= result word)
412
(evernote-display-note-completion-buf
413
evenote-note-displayed-name-formatted-name-alist))
414
(t (evernote-set-minibuffer-string result)
417
(try-completion result
418
evernote-note-displayed-name-attr-alist))
420
(evernote-minibuffer-tmp-message "[Complete, but not unique]"))))))
423
(defun evernote-read-note-finish ()
424
"Finish input note name"
427
(evernote-get-minibuffer-string)
428
evernote-note-displayed-name-attr-alist)
430
(let ((completion-buf (get-buffer "*Evernote-Completions*")))
432
(kill-buffer completion-buf)))
434
(evernote-minibuffer-tmp-message "[No Match]")))
437
(defun evernote-display-note-completion-buf (displayed-name-formatted-name-alist &optional word)
438
(let (formatted-name-displayed-name-alist completion-buf)
439
(setq formatted-name-displayed-name-alist
440
(mapcar (lambda (displayed-name)
444
evenote-note-displayed-name-formatted-name-alist)
450
evenote-note-displayed-name-formatted-name-alist)))
452
(setq completion-buf (get-buffer-create "*Evernote-Completions*"))
453
(set-buffer completion-buf)
454
(evernote-display-note-completion-list
455
formatted-name-displayed-name-alist)
456
(setq evernote-search-mode-formatted-name-displayed-name-alist
457
formatted-name-displayed-name-alist)
458
(evernote-search-mode))
459
(display-buffer completion-buf)))
462
(defun evernote-display-note-completion-list (formatted-name-displayed-name-alist)
463
"Display formatted note names on this buffer"
464
(setq buffer-read-only nil)
467
(insert (car elem) "\n"))
468
formatted-name-displayed-name-alist)
469
(setq buffer-read-only t))
472
(defun evernote-select-note-in-search-mode ()
473
"Select a note name on this buffer and input it into the mini buffer"
476
(let (displayed-name)
479
(evernote-get-current-line-string)
480
evernote-search-mode-formatted-name-displayed-name-alist))
481
(kill-buffer (current-buffer))
482
(if (active-minibuffer-window)
484
(evernote-set-minibuffer-string displayed-name)
485
(exit-minibuffer))))))
488
(defun evernote-find-opened-buffer (guid)
489
"Find a buffer associated with guid"
490
(let ((found_buf nil))
494
(if (string= evernote-note-guid guid)
495
(setq found_buf buf)))
500
(defun evernote-move-cursor-to-window (buf)
501
"Move cursor to the window associated with the bufer"
502
(let ((buf-window (get-buffer-window buf)))
504
(select-window buf-window)
505
(pop-to-buffer buf))))
508
(defun evernote-create-note-buffer (guid name edit-mode tags content)
509
"Create new buffer for the note"
511
(let ((buf (generate-new-buffer name)))
514
(setq evernote-note-guid guid
515
evernote-note-name name
516
evernote-note-edit-mode edit-mode
517
evernote-note-tags tags)
519
(goto-char (point-min))
520
(evernote-update-mode-line)
521
(set-buffer-modified-p nil)
522
(pop-to-buffer buf))))
525
(defun evernote-get-tag-list-string (tags maxlen)
527
(mapconcat #'identity tags ",")
531
(defun evernote-read-edit-mode (default)
532
(completing-read "Edit Mode (type \"TEXT\" or \"XHTML\"):"
533
'(("TEXT") ("XHTML"))
537
(defun evernote-string-to-oct (string)
538
"Convert the string into quoted backslashed octal edit mode."
541
(apply 'concat (mapcar (lambda (string)
542
(format "\\%03o" string))
543
(mapcar 'identity (encode-coding-string string 'utf-8))))
547
(defun evernote-assoc-cdr (key alist)
548
(cdr (assoc key alist)))
551
(defun evernote-get-current-line-string ()
562
(defun evernote-get-minibuffer-string ()
564
(evernote-set-buffer-to-minibuffer)
567
(goto-char (+ 1 (minibuffer-prompt-width)))
574
(defun evernote-set-minibuffer-string (str)
576
(evernote-set-buffer-to-minibuffer)
579
(goto-char (+ 1 (minibuffer-prompt-width)))
587
(defun evernote-set-buffer-to-minibuffer ()
588
(set-buffer (window-buffer (active-minibuffer-window))))
591
(defun evernote-minibuffer-tmp-message (msg)
593
(goto-char (point-max))
594
(save-excursion (insert " " msg))
596
(delete-region (point) (point-max))))
599
(defun evernote-update-mode-line ()
602
(concat "[Tag:" (mapconcat #'identity evernote-note-tags ",") "] "
603
"[Edit mode:" evernote-note-edit-mode "]"))
604
(force-mode-line-update))
607
(defun evernote-completing-read-multiple
608
(prompt table &optional predicate require-match initial-input hist def inherit-input-method)
609
"Read multiple strings with completion. and return nil if no input is given"
612
(completing-read-multiple prompt
619
inherit-input-method))
620
(delete "" results)))
624
;; Command interface.
627
(defun evernote-command-login ()
628
"Issue login command"
629
(let* ((user (read-string "Evernote user name:"))
630
(passwd (read-passwd "Passwd:")))
631
(evernote-issue-command nil "login" user passwd)))
634
(defun evernote-command-get-tag-list ()
635
"Issue listtags command"
636
(evernote-issue-command nil "listtags")
637
(evernote-eval-command-result))
640
(defun evernote-command-get-note-list-from-tags (tag-names)
641
"Issue listnotes command from the tag name list."
644
(mapconcat #'identity (mapcar 'evernote-string-to-oct tag-names) ",")))
645
(evernote-issue-command nil "listnotes" "-t" oct-tag-names))
646
(evernote-issue-command nil "listnotes"))
647
(evernote-eval-command-result))
650
(defun evernote-command-get-note-list-from-query (query)
651
"Issue listnotes command from the query."
653
(let ((oct-query (evernote-string-to-oct query)))
654
(evernote-issue-command nil "listnotes" "-q" oct-query))
655
(evernote-issue-command nil "listnotes"))
656
(evernote-eval-command-result))
659
(defun evernote-command-get-note-content (guid note-edit-mode)
660
"Issue getnotecontent command specified by the guid and the edit mode."
662
((string= note-edit-mode "XHTML") "-x")
663
((string= note-edit-mode "TEXT") "--text"))))
664
(evernote-issue-command nil "getnotecontent" guid option)
665
(evernote-get-command-result)))
668
(defun evernote-command-create-note (inbuf name tags edit-mode)
669
"Issue createnote command specified by the guid, tags and the edit-mode."
670
(let (edit-mode-option)
672
((string= edit-mode "XHTML")
673
(setq edit-mode-option "-x"))
674
((string= edit-mode "TEXT")
675
(setq edit-mode-option "--text")))
677
(evernote-issue-command inbuf
679
"-t" (mapconcat #'identity (mapcar 'evernote-string-to-oct tags) ",")
680
(evernote-string-to-oct name)
682
(evernote-issue-command inbuf
684
(evernote-string-to-oct name)
688
(defun evernote-command-update-note (inbuf guid name tags edit-mode)
689
"Issue updatenote command specified by the guid and the parameters for updating."
690
(let (edit-mode-option)
692
((string= edit-mode "XHTML")
693
(setq edit-mode-option "-x"))
694
((string= edit-mode "TEXT")
695
(setq edit-mode-option "--text")))
697
(evernote-issue-command inbuf
699
"-t" (mapconcat #'identity (mapcar 'evernote-string-to-oct tags) ",")
700
guid (evernote-string-to-oct name) edit-mode-option)
701
(evernote-issue-command inbuf
702
"updatenote" "-c" "--delete-all-tags"
703
guid (evernote-string-to-oct name) edit-mode-option))))
706
(defun evernote-command-delete-note (guid)
707
"Issue deletenote command specified by the guid, tags and the edit mode."
708
(evernote-issue-command nil "deletenote" guid))
711
(defun evernote-command-get-search-list ()
712
"Issue listsearch command"
713
(evernote-issue-command nil "listsearch")
714
(evernote-eval-command-result))
717
(defun evernote-command-create-search (name query)
718
"Issue createsearch command"
719
(evernote-issue-command nil
721
(evernote-string-to-oct name)
722
(evernote-string-to-oct query)))
725
(defun evernote-issue-command (inbuf &rest args)
726
"Invoke external process to issue an evernote command."
727
(let ((outbuf (get-buffer-create evernote-command-output-buffer-name))
729
(coding-system-for-read 'utf-8)
730
(coding-system-for-write 'utf-8))
734
(setq infile (make-temp-file "evernote"))
735
(write-region (point-min) (point-max) infile)))
738
(set-buffer-file-coding-system 'utf-8)
740
(message "Waiting for the result...")
741
(let ((result (apply 'call-process "ruby" infile outbuf nil "-S" "enclient.rb" args)))
744
((eq result evernote-error-ok) t)
745
(t (throw 'error result))))))
748
(defun evernote-get-command-result ()
749
"Get the result of the result of the lately issued command as a string."
750
(let ((outbuf (get-buffer-create evernote-command-output-buffer-name)))
753
(set-buffer-file-coding-system 'utf-8)
754
(buffer-substring (point-min) (point-max)))))
757
(defun evernote-eval-command-result ()
758
"Get the result of the result of the lately issued command as a string and eval the string."
759
(let ((outbuf (get-buffer-create evernote-command-output-buffer-name)))
762
(set-buffer-file-coding-system 'utf-8)
763
(car (read-from-string
764
(buffer-substring (point-min) (point-max)))))))
767
(defun evernote-error-message (error-code)
768
"Get the error message corresponding to the integer command result."
770
((eq error-code evernote-error-ok) "OK")
771
((eq error-code evernote-error-fail) "System error")
772
((eq error-code evernote-error-parse) "Parse error")
773
((eq error-code evernote-error-unknown) "Unknown error")
774
((eq error-code evernote-error-bad-data-format) "Bad data format")
775
((eq error-code evernote-error-permission-denied) "Permission denied")
776
((eq error-code evernote-error-internal-error) "Internal error")
777
((eq error-code evernote-error-data-required) "Data required")
778
((eq error-code evernote-error-limit-reached) "Limit reached")
779
((eq error-code evernote-error-quota-reached) "Quota reached")
780
((eq error-code evernote-error-invalid-auth) "Invalid auth")
781
((eq error-code evernote-error-auth-expired) "Auth expired")
782
((eq error-code evernote-error-data-conflict) "Data conflict")
783
((eq error-code evernote-error-enml-validation) "Enml validation. Tried to save a note of invalid format.")
784
((eq error-code evernote-error-shared-unavailable) "Shared unavailable")))
791
(defun evernote-search-mode ()
792
"Major mode for selecting a note."
794
(use-local-map evernote-search-mode-map)
795
(setq buffer-read-only t
797
major-mode 'evernote-search-mode
798
mode-name "Evernote-Search")
799
(goto-char (point-min)))
805
(defun evernote-mode ()
806
"Toggle Evernote mode, a minor mode for using evernote functions."
808
(or (assq 'evernote-mode minor-mode-alist)
809
(setq minor-mode-alist (cons '(evernote-mode " Evernote") minor-mode-alist)))
810
(or (assq 'evernote-mode minor-mode-map-alist)
811
(setq minor-mode-map-alist
812
(cons (cons 'evernote-mode evernote-mode-map) minor-mode-map-alist)))
813
(set-buffer-file-coding-system 'utf-8)
814
(setq evernote-mode (not evernote-mode))
816
(concat "[Tag:" (mapconcat #'identity evernote-note-tags ",") "] "
817
"[Edit Mode:" evernote-note-edit-mode "]"))
818
(run-hooks 'evernote-mode-hook))
821
(provide 'evernote-mode)
823
;;(setq debug-on-error t)