-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkite.el
216 lines (174 loc) · 6.42 KB
/
kite.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
;; Contents of this plugin will be reset by Kite on start. Changes you make
;; are not guaranteed to persist.
;; TODO
;; * test using emacs batch mode
;; * log errors in a way that we will be able to give users instructions to find them
;; * disable self if anything goes wrong
(defvar kite-plugin-id
(concat "emacs_" emacs-version "_" (number-to-string (emacs-pid)))
"id of this elisp plugin.")
(defvar kite-max-packet-size 262143 "Max number of chars to send in one UDP packet.")
(defvar kite-in-hook nil "True if we are currently in a hook (prevents infinite loops).")
(defvar kite-prev-point 0
"Holds the cursor position from the last run of post-command-hooks.")
(make-variable-buffer-local 'kite-prev-point)
(defvar kite-socket-path "~/.kite/kite.sock" "path to unix domain socket")
(defvar kite-udswrite-path "~/.kite/emacs/udswrite" "path to udswrite binary")
;;
;; Logging
;;
(defun kite-log (str)
"Print a message to the log"
(message (format "[Kite] %s" str)))
;;
;; State management
;;
(defun kite-buffer-state-changed ()
"Determines whether the cursor position changed since the last call to
kite-checkpoint-buffer-state."
(not (equal (point) kite-prev-point)))
(defun kite-checkpoint-buffer-state ()
(setq kite-prev-point (point)))
(defun kite-ignore-buffer ()
"Determines whether the current buffer is visiting a file (as opposed to *scratch or the minibuffer)"
(null (buffer-file-name)))
;;
;; JSON Marshaling
;;
(defun kite-alist-p (list)
"Non-null if and only if LIST is an alist with simple keys."
(while (consp list)
(setq list (if (and (consp (car list))
(atom (caar list)))
(cdr list)
'not-alist)))
(null list))
(defvar kite-json-true :json-true "symbol representing true in json")
(defvar kite-json-false :json-false "symbol representing false in json")
(defvar kite-json-special-chars
'((?\" . ?\")
(?\\ . ?\\)
(?/ . ?/)
(?b . ?\b)
(?f . ?\f)
(?n . ?\n)
(?r . ?\r)
(?t . ?\t))
"Characters which are escaped in JSON, with their elisp counterparts.")
(defun kite-comma-separate (strings)
(mapconcat 'identity strings ","))
(defun kite-marshal-char (char)
"Encode a character within a JSON string."
(setq char (encode-char char 'ucs))
(let ((control-char (car (rassoc char kite-json-special-chars))))
(cond
;; Special JSON character (\n, \r, etc.).
(control-char
(format "\\%c" control-char))
;; ASCIIish printable character.
((and (> char 31) (< char 127))
(format "%c" char))
;; Fallback: UCS code point in \uNNNN form.
(t
(format "\\u%04x" char)))))
(defun kite-marshal-string (string)
(format "\"%s\"" (mapconcat 'kite-marshal-char string "")))
(defun kite-marshal-keyvalue (pair)
(format "%s:%s" (kite-marshal (car pair)) (kite-marshal (cdr pair))))
(defun kite-marshal-list (list)
(format "[%s]" (kite-comma-separate (mapcar 'kite-marshal list))))
(defun kite-marshal-dict (dict)
(format "{%s}" (kite-comma-separate (mapcar 'kite-marshal-keyvalue dict))))
(defun kite-marshal (obj)
(cond ((null obj) "null")
((eq obj t) "true")
((eq obj kite-json-true) "true")
((eq obj kite-json-false) "false")
((numberp obj) (format "%s" obj))
((stringp obj) (kite-marshal-string obj))
((kite-alist-p obj) (kite-marshal-dict obj))
(t (kite-marshal-list obj))))
;;
;; Network protocol
;;
(defun kite-message (action)
"Build a json string to send to kited"
(kite-marshal (list (cons "source" "emacs")
(cons "action" action)
(cons "filename" (buffer-file-name (current-buffer)))
(cons "selections" (list (list (cons "start" (- (point) 1))
(cons "end" (- (point) 1)))))
(cons "pluginId" kite-plugin-id)
(cons "text" (buffer-string))
)))
(defun kite-surface ()
(kite-marshal (list (cons "source" "emacs")
(cons "action" "surface"))))
(defun kite-send (message)
"Check status of socket and send a message if possible"
(if (< (length message) kite-max-packet-size)
(call-process kite-udswrite-path nil nil nil (expand-file-name kite-socket-path) message)
(kite-log "unable to send message because length exceeded limit")))
;;
;; Hooks
;;
(defun kite-handle-focus-in ()
"Called when the user switches to the emacs window."
(if (not (kite-ignore-buffer))
(kite-send (kite-message "focus"))
(kite-send (kite-surface))))
(defun kite-handle-focus-out ()
"Called when the user switches away from the emacs window."
(unless (kite-ignore-buffer)
(kite-send (kite-message "lost_focus"))))
(defun kite-handle-after-change (begin end oldlength)
(if (kite-ignore-buffer)
(kite-send (kite-surface))
(unless kite-in-hook
(setq kite-in-hook t)
(kite-send (kite-message "edit"))
(kite-checkpoint-buffer-state)
(setq kite-in-hook nil))))
(defun kite-handle-buffer-list-update ()
"Called when the user switches between buffers."
(if (kite-ignore-buffer)
(kite-send (kite-surface))
(unless kite-in-hook
(setq kite-in-hook t)
(kite-send (kite-message "selection"))
(setq kite-in-hook nil))))
(defun kite-handle-post-command ()
"Called when the user issues any command"
(if (kite-ignore-buffer)
(kite-send (kite-surface))
(unless kite-in-hook
(setq kite-in-hook t)
(when (kite-buffer-state-changed)
(kite-send (kite-message "selection"))
(kite-checkpoint-buffer-state))
(setq kite-in-hook nil))))
;;
;; Initialization
;;
(defun kite-add-hooks ()
"Register the hooks we need"
(add-hook 'after-change-functions 'kite-handle-after-change)
(add-hook 'focus-in-hook 'kite-handle-focus-in)
(add-hook 'focus-out-hook 'kite-handle-focus-out)
(add-hook 'post-command-hook 'kite-handle-post-command))
(defun kite-remove-hooks ()
"Remove kite-related hooks"
(remove-hook 'buffer-list-update-hook 'kite-handle-buffer-list-update)
(remove-hook 'after-change-functions 'kite-handle-after-change)
(remove-hook 'focus-in-hook 'kite-handle-focus-in)
(remove-hook 'focus-out-hook 'kite-handle-focus-out))
(defun kite-init ()
"Setup Kite connection and hooks."
(interactive)
(kite-add-hooks))
(defun kite-stop ()
"Remove hooks and clean up socket."
(interactive)
(kite-remove-hooks))
(kite-init)
(provide 'kite)