Browse Source

Extract part of indium-client.el into a new library.

* indium-client.el:
(indium-client--connection)
(indium-client--process)
(indium-client--callbacks): Remove to have `indium-client--application`
instead.
(indium-client--application): New variable to save the return value of
`json-process-client-start`.
(indium-client-start): Rewrite to call `json-process-client-start`.
(indium-client-stop): Rewrite to call `json-process-client-stop`.
(indium-client-send): Rewrite to call `json-process-client-send`.
(indium-client-process-live-p): Rewrite to call
`json-process-client-process-live-p`.
(indium-client--ensure-process)
(indium-client--start-server)
(indium-client--process-filter-function)
(indium-client--open-network-stream)
(indium-client--connection-sentinel)
(indium-client--connection-filter)
(indium-client--handle-data)
(indium-client--complete-message-p): Move to the new
`json-process-client` library.
(indium-client--handle-message): Add CALLBACK parameter.
(indium-client--handle-response): Call the CALLBACK parameter instead
of getting it from a global variable.
tags/2.1.3
Damien Cassou 6 months ago
parent
commit
ded54e3b27
4 changed files with 35 additions and 179 deletions
  1. +1
    -0
      Makefile
  2. +29
    -139
      indium-client.el
  3. +1
    -1
      indium.el
  4. +4
    -39
      test/unit/indium-client-test.el

+ 1
- 0
Makefile View File

@@ -24,6 +24,7 @@ dependencies-elisp:
--eval "(package-install 'js2-mode)" \
--eval "(package-install 'js2-refactor)" \
--eval "(package-install 'assess)" \
--eval "(package-install 'json-process-client)" \
--eval "(package-install 'exec-path-from-shell)"

dependencies-javascript:

+ 29
- 139
indium-client.el View File

@@ -30,6 +30,7 @@
(require 'json)
(require 'map)
(require 'subr-x)
(require 'json-process-client)

(require 'indium-structs)

@@ -88,18 +89,12 @@
:group 'indium-client
:type 'file)

(defvar indium-client--connection nil
"The client connection to the server process.")

(defvar indium-client--process nil
"The Indium server process.")
(defvar indium-client--application nil
"The client connection as returned by `json-process-client-start'.")

(defvar indium-client--process-port 13840
"The port on which the server should listen.")

(defvar indium-client--callbacks nil
"Alist of functions to be evaluated as callbacks on process response.")

(defun indium-client-start (callback)
"Start an Indium process and store it as the client process.
Evaluate CALLBACK once the server is started."
@@ -108,33 +103,28 @@ Evaluate CALLBACK once the server is started."
(let ((executable (executable-find indium-client-executable)))
(unless executable
(user-error "Cannot find the indium executable. Please run \"npm install -g indium\""))
(when indium-client-debug
(with-current-buffer (get-buffer-create "*indium-debug-log*")
(erase-buffer)))
(indium-client--start-server executable callback)))
(setq indium-client--application
(json-process-client-start-with-id
:name "indium"
:executable executable
:port indium-client--process-port
:started-regexp "server listening"
:tcp-started-callback callback

:exec-callback #'indium-client--handle-message
:debug "*indium-debug-log*"
:args (list (number-to-string indium-client--process-port))))))

(defun indium-client-stop ()
"Stop the indium process."
(when (process-live-p indium-client--connection)
(kill-buffer (process-buffer indium-client--process))
(kill-buffer (process-buffer indium-client--connection)))
(setq indium-client--connection nil)
(setq indium-client--process nil)
(setq indium-client--callbacks nil)
(json-process-client-stop indium-client--application)
(setq indium-client--application nil)
(run-hooks 'indium-client-closed-hook))

(defun indium-client-send (message &optional callback)
"Send MESSAGE to the Indium process.
When CALLBACK is non-nil, evaluate it with the process response."
(indium-client--ensure-process)
(let* ((id (indium-client--next-id))
(json (json-encode (cons `(id . ,id) message))))
(map-put indium-client--callbacks id callback)
(when indium-client-debug
(with-current-buffer (get-buffer-create "*indium-debug-log*")
(goto-char (point-max))
(insert (format "Sent: %s\n\n" (cons `(id . ,id) message)))))
(process-send-string indium-client--connection (format "%s\n" json))))
(json-process-client-send indium-client--application message callback))

(defun indium-client-list-configurations (directory &optional callback)
@@ -283,109 +273,18 @@ When CALLBACK is non-nil, evaluate it with the list of sources."
callback))

(defun indium-client--ensure-process ()
"Signal an error if the Indium is not started."
(unless (indium-client-process-live-p)
(user-error "Indium server not started")))

(defun indium-client-process-live-p ()
"Return non-nil if the indium process is running."
(process-live-p indium-client--connection))

(defun indium-client--start-server (executable callback)
"Start the Indium server process in EXECUTABLE.

Evaluate CALLBACK once the server is started and the TCP
connection established."
(setq indium-client--process
(start-process "indium server"
(generate-new-buffer "*indium-process*")
executable
(format "%s" indium-client--process-port)))
(set-process-query-on-exit-flag indium-client--process nil)
(set-process-filter indium-client--process
(indium-client--process-filter-function callback)))

(defun indium-client--process-filter-function (callback)
"Return a process filter function for an Indium server process.

Evaluate CALLBACK when the server starts listening to TCP connections."
(lambda (process output)
(with-current-buffer (process-buffer process)
(goto-char (point-max))
(insert output))
(unless (process-live-p indium-client--connection) ;; do not try to open TCP connections multiple times
(if (string-match-p "server listening" output)
(indium-client--open-network-stream callback)
(progn
(indium-client-stop)
(error "Indium server process error: %s" output))))))

(defun indium-client--open-network-stream (callback)
"Open a network connection to the indium server TCP process.
Evaluate CALLBACK once the connection is established."
(let ((process (open-network-stream "indium"
(generate-new-buffer " indium-client-conn ")
"localhost"
indium-client--process-port)))
(set-process-filter process #'indium-client--connection-filter)
(set-process-coding-system process 'utf-8)
(set-process-query-on-exit-flag process nil)
;; TODO: Set a process sentinel
;; (set-process-sentinel process #'indium-client--connection-sentinel)
(setq indium-client--connection process)
(funcall callback)))

(defun indium-client--connection-sentinel (callback)
"Evaluate CALLBACK when the network process is open."
(lambda (proc _event)
(when (eq (process-status proc) 'open)
(funcall callback))))

(defun indium-client--connection-filter (process output)
"Filter function for handling the indium PROCESS OUTPUT."
(let ((buf (process-buffer process)))
(with-current-buffer buf
(save-excursion
(goto-char (point-max))
(insert output)))
(indium-client--handle-data buf)))

(defun indium-client--handle-data (buffer)
"Handle process data in BUFFER.

Read the complete messages sequentially and handle them. Each
read message is deleted from BUFFER."
(let ((data))
(with-current-buffer buffer
(when (indium-client--complete-message-p)
(save-excursion
(goto-char (point-min))
(setq data (json-read))
(delete-region (point-min) (point))
;; Remove the linefeed char
(delete-char 1))))
(when data
(indium-client--handle-message data)
(indium-client--handle-data buffer))))

(defun indium-client--complete-message-p ()
"Return non-nil if the current buffer has a complete message.
Messages end with a line feed."
(save-excursion
(goto-char (point-max))
(search-backward "\n" nil t)))

(defun indium-client--handle-message (data)
"Handle a server message with DATA."
(when indium-client-debug
(with-current-buffer (get-buffer-create "*indium-debug-log*")
(goto-char (point-max))
(insert (format "Received: %s\n\n" data))))
(json-process-client-process-live-p indium-client--application))

(defun indium-client--handle-message (data callback)
"Handle a server message with DATA.
If DATA is a successful response to a previously-sent message,
evaluate CALLBACK with the payload."
(let-alist data
(pcase .type
("error" (indium-client--handle-error .payload))
("success" (indium-client--handle-response .id .payload))
("success" (indium-client--handle-response .payload callback))
("notification" (indium-client--handle-notification .payload))
("log" (indium-client--handle-log .payload)))))

@@ -395,18 +294,14 @@ PAYLOAD is an alist containing the details of the error."
(let-alist payload
(message "Indium server error: %s" .error)))

(defun indium-client--handle-response (id payload)
(defun indium-client--handle-response (payload callback)
"Handle a response to a client request.
ID is the id of the request for which the server has answered.
PAYLOAD contains the data of the response.

If a callback function has been registered for ID, evaluate it
with the PAYLOAD."
(let ((callback (map-elt indium-client--callbacks id)))
(when callback
(unwind-protect
(funcall callback payload)
(map-delete indium-client--callbacks id)))))
If CALLBACK is non-nil, evaluate it with the PAYLOAD."
(when callback
(unwind-protect
(funcall callback payload))))

(defun indium-client--handle-log (payload)
"Handle a log event from the server.
@@ -446,10 +341,5 @@ PAYLOAD is an alist with the details of the notification."
(setq path (replace-regexp-in-string "^\\([a-z]\\):" #'capitalize path)))
path)

(defvar indium-client--id 0)
(defun indium-client--next-id ()
"Return the next unique identifier to be used."
(cl-incf indium-client--id))

(provide 'indium-client)
;;; indium-client.el ends here

+ 1
- 1
indium.el View File

@@ -6,7 +6,7 @@
;; URL: https://github.com/NicolasPetton/indium
;; Keywords: tools, javascript
;; Version: 2.1.1
;; Package-Requires: ((emacs "25") (seq "2.16") (js2-mode "20140114") (js2-refactor "0.9.0") (company "0.9.0"))
;; Package-Requires: ((emacs "25") (seq "2.16") (js2-mode "20140114") (js2-refactor "0.9.0") (company "0.9.0") (json-process-client "0.2.0"))

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by

+ 4
- 39
test/unit/indium-client-test.el View File

@@ -25,45 +25,10 @@
(require 'indium-client)

(describe "Regression test for GitHub issue #163"
(it "should signal a user error when the indium executable cannot be found"
(let ((indium-client-executable ""))
(expect (indium-client-start (lambda ())) :to-throw
'user-error '("Cannot find the indium executable. Please run \"npm install -g indium\"")))))

(describe "Reading server messages"
(it "should not change the buffer reading an incomplete message"
(with-temp-buffer
(insert "{foo")
(indium-client--handle-data (current-buffer))
(expect (buffer-string) :to-equal "{foo")))

(it "should call `indium-client--handle-message' when reading a complete message"
(spy-on #'indium-client--handle-message)
(with-temp-buffer
(insert "{\"foo\": 1}\n")
(indium-client--handle-data (current-buffer))
(expect #'indium-client--handle-message :to-have-been-called-with '((foo . 1)))))

(it "should remove the linefeed char when reading a complete message"
(spy-on #'indium-client--handle-message)
(with-temp-buffer
(insert "{\"foo\": 1}\n")
(indium-client--handle-data (current-buffer))
(expect (buffer-string) :to-equal "")))

(it "should remove all linefeed chars when reading multiple complete messages"
(spy-on #'indium-client--handle-message)
(with-temp-buffer
(insert "{\"foo\": 1}\n{\"bar\": 2}\n")
(indium-client--handle-data (current-buffer))
(expect (buffer-string) :to-equal "")))

(it "should remove all linefeed chars but keep incomplete messages"
(spy-on #'indium-client--handle-message)
(with-temp-buffer
(insert "{\"foo\": 1}\n{\"bar\": 2}\n{\"baz")
(indium-client--handle-data (current-buffer))
(expect (buffer-string) :to-equal "{\"baz"))))
(it "should signal a user error when the indium executable cannot be found"
(let ((indium-client-executable ""))
(expect (indium-client-start (lambda ())) :to-throw
'user-error '("Cannot find the indium executable. Please run \"npm install -g indium\"")))))

(provide 'indium-client-test)
;;; indium-client-test.el ends here

Loading…
Cancel
Save