-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathess-websocket.el
184 lines (159 loc) · 5.89 KB
/
ess-websocket.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
;; To use this package you will need to install the websocket package.
(use-package websocket)
;; set to non-nil to get some more debugging information.
(setq essgd-debug nil)
(defun essgd-start-websocket ()
"Start the websocket to monitor httpgd from elisp.
This allows us to respond automatically to new plots."
(setq essgd-websocket
(websocket-open
(string-replace "http" "ws" essgd-url)
:on-message #'essgd-process-message
:on-close (lambda (_websocket) (message "sje websocket closed")))))
(defun essgd-process-message (_websocket frame)
"Handle the message returing from the frame."
(when essgd-debug (message "ws frame: %S" (websocket-frame-text frame)))
(let* ((json-plist (json-parse-string (websocket-frame-text frame)
:false-object nil
:object-type 'plist))
(possible-plot (plist-get json-plist :hsize))
(active (plist-get json-plist :active)))
(when active
(with-current-buffer "*essgd*"
(unless (member possible-plot essgd-plot-nums)
(setq-local essgd-plot-nums (essgd-get-plot-nums))
(setq-local essgd-cur-plot possible-plot)
(when essgd-debug (message "cur plot is %d" essgd-cur-plot))
(essgd-show-plot-n possible-plot))))))
;; API:
;; https://cran.r-project.org/web/packages/httpgd/vignettes/c01_httpgd-api.html
;; curl -s http://127.0.0.1:5900/plot?index=2&width=800&height=600 > /tmp/a.svg
(defun essgd-start ()
"Start an *essgd* window to plot R output.
Must be called from a buffer that is either an *R* process, or attached to one.
The initial size of the plot is half the current window."
(interactive)
(let ((buf (get-buffer-create "*essgd*"))
(r-proc ess-local-process-name)
url1
)
(set-buffer buf)
(essgd-mode)
(if r-proc
(setq ess-local-process-name r-proc)
(error "No r process to communicate with")
)
;; start the hgd() device here; output should contain the url
;; that is serving the figures.
(setq start-output (ess-string-command essgd-start-text))
(string-match "\\(http://[0-9.:]+\\)/live\\?token=\\(.+\\)" start-output)
;; TODO - check case when token is missing.
;; TODO - error check if URL ccannot be found.
(setq-local essgd-url (match-string 1 start-output))
(setq-local essgd-token (match-string 2 start-output))
(if (> (length essgd-token ) 0)
(setq essgd-token (format "token=%s" essgd-token)))
(setq-local essgd-plot-nums (essgd-get-plot-nums))
(setq-local essgd-cur-plot
(length essgd-plot-nums))
;; (setq-local essgd-latest (make-temp-file "essgd" nil ".svg"))
(setq-local essgd-latest "/tmp/me.svg")
(pop-to-buffer buf)
(setq-local window-size-change-functions '(essgd-window-size-change))
(when (> essgd-cur-plot 0)
(essgd-show-plot-n essgd-cur-plot))
(essgd-start-websocket)
(setq cursor-type nil)
(read-only-mode 1)
))
(defun essgd-get-plot-nums ()
"Return the number of plots on the server."
;; TODO: check what happens if no plots served.
;;
(with-current-buffer "*essgd*"
(let (cmd text plist plots)
(setq cmd (format "curl -s '%s/plots?%s'" essgd-url essgd-token))
(when essgd-debug (message cmd))
(setq text (shell-command-to-string cmd))
(setq plist (json-parse-string text :object-type 'plist))
(setq plots (plist-get plist :plots))
(mapcar (lambda (x) (1+ (string-to-number (cadr x)))) plots))))
(defun essgd-show-plot-n (n)
"Show plot N.
Do nothing if n is zero."
(when (> n 0)
(let* ((edges (window-body-pixel-edges (get-buffer-window "*essgd*")))
(left (nth 0 edges))
(top (nth 1 edges))
(right (nth 2 edges))
(bottom (nth 3 edges))
(wid (- right left))
(ht (- bottom top))
img
;; (essgd-latest (format "/tmp/ess-latest-%d.svg" n))
(cmd (format "ugd_save(file=\"%s\",page=%d,width=%d,height=%d)"
essgd-latest n wid ht ))
(cmd1 (format "curl -s '%s/plot?index=%d&width=%d&height=%d&%s' > %s"
essgd-url
(1- n)
wid ht
essgd-token
essgd-latest))
)
(when essgd-debug (message cmd1))
(when essgd-debug (message "inside size %d x %d " wid ht))
(shell-command-to-string cmd1)
;; (message cmd)
;; (ess-string-command cmd)
(setq img (create-image essgd-latest))
(remove-images 0 1)
(put-image img 0)
;; images are cached, by filename, which we don't want here,
;; especially during testing.
(image-flush img)
(setq essgd-cur-plot n)
(setq-local mode-line-position
(format "P%d/%d" essgd-cur-plot (length essgd-plot-nums)))
)))
(defun essgd-refresh ()
"Refresh the latest plot."
(interactive)
(setq-local essgd-plot-nums (essgd-get-plot-nums))
(essgd-show-plot-n (with-current-buffer "*essgd*" essgd-cur-plot)))
;; Emacs 29 seems to make it much "easier" for defining major modes.
(defvar-keymap essgd-mode-map
"r" #'essgd-refresh
"p" #'essgd-prev-plot
"n" #'essgd-next-plot
"q" #'essgd-quit)
(define-derived-mode essgd-mode
fundamental-mode
"Essgd"
"Major mode for displaying essgd plots" )
(defun essgd-prev-plot ()
"Go to previous (earlier) plot in *R* session."
(interactive)
(if (equal essgd-cur-plot 1)
(message "Already at first plot")
(essgd-show-plot-n (1- essgd-cur-plot))))
(defun essgd-next-plot ()
"Go to next (later) plot in *R* session."
(interactive)
(if (equal essgd-cur-plot (length essgd-plot-nums))
(message "Already at latest plot")
(essgd-show-plot-n (1+ essgd-cur-plot))))
(defun essgd-quit ()
"Quit the current *essgd* device and close the device in R."
(interactive)
(ess-string-command "dev.off()")
(kill-buffer))
(defun essgd-window-size-change (win)
"Function run when the window size changes.
WIN is currently used to get the buffer *essgd*."
(if essgd-debug
(message "essgd: resize window"))
(with-current-buffer "*essgd*"
(essgd-refresh)))
(defvar essgd-start-text "httpgd::hgd(bg='transparent')
"
"R code required for *essgd* session.")