several utility functions have been extracted to their own modules

This commit is contained in:
Sameer Rahmani 2019-08-23 14:22:50 +01:00
parent 840f42ec1f
commit 8c25a66513
6 changed files with 376 additions and 27 deletions

View File

View File

@ -133,6 +133,7 @@
(let* ((data (fg42/devtools-decode (websocket-frame-payload data)))
(method (plist-get data :method))
(params (plist-get data :params)))
(inspect-data-append data)
(pcase method
("Debugger.scriptParsed" (fg42/devtools-on-script-parsed params))
;; we are getting an error in Console.messageAdded
@ -143,10 +144,8 @@
;; nil -> These are return messages from RPC calls, not notification
(_ (if method
(inspect-data-append data)
(progn
(inspect-data-append data)
(fg42/devtools-dispatch-callback (plist-get data :id)
(plist-get data :result))))))))
(fg42/devtools-dispatch-callback (plist-get data :id)
(plist-get data :result)))))))
(defun fg42/devtools-call-rpc (method &optional params callback)
@ -165,8 +164,7 @@
"Connect to the given URL and return a socket."
(websocket-open url
:on-open #'fg42/devtools-on-open
:on-message (lambda (x y)
(fg42/devtools-on-message x y))
:on-message #'fg42/devtools-on-message
:on-close #'fg42/devtools-on-close))

315
lib/fg42/devtools/core.el Normal file
View File

@ -0,0 +1,315 @@
;;; fg42-devtools --- Webkit devtool driver for FG42
;;
;; Copyright (c) 2019 Sameer Rahmani <lxsameer@gnu.org>
;;
;; Author: Sameer Rahmani <lxsameer@gnu.org>
;; URL: https://gitlab.com/FG42/FG42
;; Keywords: webkit
;; Version: 0.1.0
;; Package-Requires: ((dash "2.11.0") (websocket "1.5"))
;;
;; 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
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
;;; Acknoledgement:
;; This library is heavily inspired by Kite mini library. Kudos Tung Dao
;; for his great work.
;;
;;; Commentary:
;;; Code:
(require 'url)
(require 'dash)
(require 'websocket)
(require 'fg42/utils)
;;; Customs & Vars ------------------------------------------------------------
(defcustom fg42/devtools-remote-host "127.0.0.1"
"Default host for connection to WebKit remote debugging API."
:group 'fg42/devtools)
(defcustom fg42/devtools-remote-port 9222
"Default port for connection to WebKit remote debugging API."
:group 'fg42/devtools)
(defvar fg42/devtools-socket nil
"Websocket connection to WebKit remote debugging API.")
(defvar fg42/devtools-rpc-id 0)
(defvar fg42/devtools-rpc-callbacks nil)
(defvar fg42/devtools-rpc-scripts nil
"List of JavaScript files available for live editing.")
;;; Functions -----------------------------------------------------------------
(defun fg42/devtools-next-rpc-id ()
"Retun the next RPC call id to be used."
(setq fg42/devtools-rpc-id (+ 1 fg42/devtools-rpc-id)))
(defun fg42/devtools-register-callback (id fn)
"Register the given FN function with the given ID as rpc Callback."
(let ((hook (intern (number-to-string id) fg42/devtools-rpc-callbacks)))
(add-hook hook fn t)))
(defun fg42/devtools-dispatch-callback (id data)
"Execute the callback registered by the given ID with the given DATA."
(let ((hook (intern (number-to-string id) fg42/devtools-rpc-callbacks)))
(when hook
(run-hook-with-args hook data)
(unintern hook fg42/devtools-rpc-callbacks))))
(defun fg42/devtools-on-open (socket)
"Connect to the given SOCKET."
(message "FG42: connected to devtools."))
(defun fg42/devtools-on-close (socket)
"Disconnect from the given SOCKET."
(message "FG42: disconnected from devtools."))
(defun fg42/devtools-on-script-parsed (data)
(let ((extension? (plist-get data :isContentScript))
(url (plist-get data :url))
(id (plist-get data :scriptId)))
(when (and (eq extension? :json-false) (not (string-equal "" url)))
(add-to-list 'fg42/devtools-rpc-scripts (list :id id :url url)))))
(defun fg42/devtools-on-script-failed-to-parse (data)
(fg42/devtools-console-append (format "%s" data)))
(defun fg42/devtools-on-message-added (data)
(let* ((message (plist-get data :message))
(url (plist-get message :url))
(column (plist-get message :column))
(line (plist-get message :line))
(type (plist-get message :type))
(level (plist-get message :level))
(text (plist-get message :text)))
;; TODO: add colors based on level
(fg42/devtools-console-append
(propertize
(format "%s: %s\t%s (line: %s column: %s)"
level text url line column)
'font-lock-face (intern (format "fg42/devtools-log-%s" level))))))
(defun fg42/devtools-on-message (socket data)
"The on message callback that gets the incoming DATA from the SOCKET."
(let* ((data (<-json (websocket-frame-payload data)))
(method (plist-get data :method))
(params (plist-get data :params)))
(inspect-data-append data)
(pcase method
("Debugger.scriptParsed" (fg42/devtools-on-script-parsed params))
;; we are getting an error in Console.messageAdded
;; ("Debugger.scriptFailedToParse" (fg42/devtools-on-script-failed-to-parse params))
("Console.messageAdded" (fg42/devtools-on-message-added params))
;; ;; TODO: do something usefull here, possibly great for REPL
("Console.messageRepeatCountUpdated")
;; nil -> These are return messages from RPC calls, not notification
(_ (if method
(inspect-data-append data)
(fg42/devtools-dispatch-callback (plist-get data :id)
(plist-get data :result)))))))
(defun fg42/devtools-call-rpc (method &optional params callback)
"Call the given METHOD with PARAMS and call CALLBACK with the result."
(let ((id (fg42/devtools-next-rpc-id)))
(when callback
(fg42/devtools-register-callback id callback))
(websocket-send-text
fg42/devtools-socket
(->json (list :id id
:method method
:params params)))))
(defun fg42/devtools-open-socket (url)
"Connect to the given URL and return a socket."
(websocket-open url
:on-open #'fg42/devtools-on-open
:on-message #'fg42/devtools-on-message
:on-close #'fg42/devtools-on-close))
(defun fg42/devtools-get-json (url)
"Fetch the json data of the given URL using a GET request."
(let* ((url-request-method "GET")
(url-http-attempt-keepalives nil)
(json-array-type 'list)
(json-object-type 'plist))
(with-current-buffer (url-retrieve-synchronously url)
(if (not (eq 200 (url-http-parse-response)))
(error "FG42: Unable to connect to devtools host")
(goto-char (+ 1 url-http-end-of-headers))
(json-read)))))
(defun fg42/devtools-get-tabs (host port)
"Read the list of open tabs from the webkit instance at HOST:PORT."
(let* ((url (url-parse-make-urlobj
"http" nil nil host port "/json"))
(tabs (fg42/devtools-get-json url)))
(-filter (lambda (tab)
(and (plist-get tab :webSocketDebuggerUrl)
(string-equal (plist-get tab :type) "page")))
tabs)))
(defun fg42/devtools-tab-completion (tab)
"A simple completion backend for the given TAB."
(let ((title (plist-get tab :title))
(url (plist-get tab :url)))
(cons (format "%s" title) tab)))
(defun fg42/devtools-select-tab (host port)
"Print out the list of tabs from HOST:PORT for the user to choose from."
(let* ((tabs (mapcar #'fg42/devtools-tab-completion
(fg42/devtools-get-tabs host port)))
(selection (completing-read
"Tab: " tabs nil t "" nil (caar tabs)))
(tab (cdr (assoc selection tabs))))
(plist-get tab :webSocketDebuggerUrl)))
(defun fg42/devtools-connect ()
"Conntect to the Webkit devtools."
(interactive)
(message "FG42: Disconnect from any previous connection.")
(fg42/devtools-disconnect)
(let* ((socket-url (fg42/devtools-select-tab fg42/devtools-remote-host
fg42/devtools-remote-port)))
(message (format "FG42: Connecting to %s" socket-url))
(setq fg42/devtools-socket (fg42/devtools-open-socket socket-url))
(message "Sending initial RPC calls...")
(fg42/devtools-call-rpc "Console.enable")
(fg42/devtools-call-rpc "Debugger.enable")
(fg42/devtools-call-rpc "Network.setCacheDisabled" '(:cacheDisabled t))))
(defun fg42/devtools-disconnect ()
"Disconnect from the Webkit devtools."
(interactive)
(when (websocket-openp fg42/devtools-socket)
(websocket-close fg42/devtools-socket)
(setq fg42/devtools-socket nil
fg42/devtools-rpc-scripts nil)))
(defun fg42/devtools-send-eval (code &optional callback)
"Send the given CODE to the devtools for evaluation and call the CALLBACK."
(fg42/devtools-call-rpc
"Runtime.evaluate"
(list :expression code
:returnByValue t)
callback))
(defun fg42/devtools-remove-script (script)
(setq fg42/devtools-rpc-scripts
(delete script fg42/devtools-rpc-scripts)))
(defun fg42/devtools-script-id (file)
(let* ((name (file-name-nondirectory file))
(script (--find (string-suffix-p name (plist-get it :url))
fg42/devtools-rpc-scripts)))
(when script (plist-get script :id))))
(defun fg42/devtools-update ()
(interactive)
(let ((id (fg42/devtools-script-id (buffer-file-name)))
(source (buffer-substring-no-properties
(point-min) (point-max))))
(if id
(fg42/devtools-call-rpc
"Debugger.setScriptSource"
(list :scriptId id :scriptSource source))
(message "No matching script for current buffer."))))
(defun fg42/devtools-reload ()
"Reload the tab."
(interactive)
(fg42/devtools-call-rpc
"Page.reload"
(list :ignoreCache t)))
(defun fg42/devtools-evaluate-region-or-line (&optional args)
(interactive "*P")
(let ((start (if (region-active-p)
(region-beginning)
(line-beginning-position)))
(end (if (region-active-p)
(region-end)
(line-end-position))))
(fg42/devtools-send-eval (buffer-substring-no-properties start end))))
(defvar fg42/devtools-mode-map
(let ((map (make-sparse-keymap)))
(prog1 map
(define-key map (kbd "C-c C-c") #'fg42/devtools-evaluate-region-or-line)
(define-key map (kbd "C-c C-k") #'fg42/devtools-update)
(define-key map (kbd "C-c C-r") #'fg42/devtools-reload)))
"Keymap for FG42 devtools mode.")
;;;###autoload
(defun turn-on-fg42/devtools-mode ()
"Turn on FG42 devtools mode.")
;;;###autoload
(defun turn-off-fg42/devtools-mode ()
"Turn off FG42 devtools mode.")
;;;###autoload
(define-minor-mode fg42/devtools-mode
"Minor mode for interact with WebKit remote debugging API."
:global nil
:group 'fg42/devtools
:init-value nil
:lighter ""
:keymap fg42/devtools-mode-map
(if fg42/devtools-mode
(turn-on-fg42/devtools-mode)
(turn-off-fg42/devtools-mode)))
(defun fg42/devtools-debug-on ()
(interactive)
(setq websocket-callback-debug-on-error t))
(defun fg42/devtools-debug-restart ()
(interactive)
(fg42/devtools-debug-on)
(message "D: disconnect")
(fg42/devtools-disconnect)
(fg42/devtools-connect))
;; (fg42/devtools-debug-restart)
(provide 'fg42/devtools)
;;; devtools.el ends here

View File

View File

@ -0,0 +1,49 @@
;;; json --- FG42 json helpers
;;
;; Copyright (c) 2019 Sameer Rahmani <lxsameer@gnu.org>
;;
;; Author: Sameer Rahmani <lxsameer@gnu.org>
;; URL: https://gitlab.com/FG42/FG42
;; Keywords: webkit
;; Version: 0.1.0
;; Package-Requires: ((dash "2.11.0") (websocket "1.5"))
;;
;; 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
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
;;; Acknoledgement:
;; This library is heavily inspired by Kite mini library. Kudos Tung Dao
;; for his great work.
;;
;;; Commentary:
;;; Code:
(require 'json)
(defun ->json (data)
"Convert the given DATA to json."
(let ((json-array-type 'list)
(json-object-type 'plist))
(json-encode data)))
(defun <-json (data)
"Convert the given json DATA to elisp data structure."
(let ((json-array-type 'list)
(json-object-type 'plist))
(json-read-from-string data)))
(provide 'fg42/utils/json)
;;; json.el ends here

View File

@ -3,25 +3,9 @@
;;; Code:
(require 'cl)
(require 'json)
(require 'fg42/vars)
;;; JSON ----------------------------------------------------------------------
(defun ->json (data)
"Convert the given DATA to json."
(let ((json-array-type 'list)
(json-object-type 'plist))
(json-encode data)))
(defun <-json (data)
"Convert the given json DATA to elisp data structure."
(let ((json-array-type 'list)
(json-object-type 'plist))
(json-read-from-string data)))
(require 'fg42/utils/json)
;;; Buffer helpers ------------------------------------------------------------
(defun buffer-mode (buffer-or-string)
@ -67,10 +51,13 @@ with is the buffer."
`(pp-display-expression ,@body (get-buffer-create fg42/inspect-buffer)))
(defun inspect-data-append (data)
(->buffer fg42/inspect-buffer
"START ========================================================\n")
(->buffer fg42/inspect-buffer (pp-to-string data))
(->buffer fg42/inspect-buffer "END.\n"))
"Append the given DATA to the inspection buffer with padding."
(->buffer
fg42/inspect-buffer
(format
"\n;; START ======================================================\n%s%s"
(pp-to-string data)
";; END.\n")))
(provide 'fg42/utils)