forked from FG42/FG42
devtools poc has been added
This commit is contained in:
parent
8e4f9c252e
commit
840f42ec1f
|
@ -8,7 +8,7 @@ packages/
|
|||
lib/magic-buffer.el
|
||||
assets/
|
||||
./fg42^
|
||||
fg42
|
||||
/fg42
|
||||
project-config/
|
||||
docs/_build
|
||||
./fonts/
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
(depends-on 'cheatsheet)
|
||||
(depends-on 'all-the-icons)
|
||||
(depends-on 'markdown-mode)
|
||||
|
||||
(depends-on 'json-mode)
|
||||
;; Fast move in the buffer
|
||||
(depends-on 'avy)
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
(require 'cl-lib)
|
||||
(require 'fg42/extension)
|
||||
|
||||
;; Vars -----------------------------------
|
||||
(defvar default-theme nil "Default FG42 theme.")
|
||||
(require 'fg42/vars)
|
||||
|
||||
|
||||
;; Macros ---------------------------------
|
||||
(defmacro theme (name &optional local)
|
||||
|
|
|
@ -0,0 +1,333 @@
|
|||
;;; 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 'json)
|
||||
(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-encode (data)
|
||||
"Convert the given DATA to json."
|
||||
(let ((json-array-type 'list)
|
||||
(json-object-type 'plist))
|
||||
(json-encode data)))
|
||||
|
||||
|
||||
(defun fg42/devtools-decode (data)
|
||||
"Convert the given json DATA to elisp data structure."
|
||||
(let ((json-array-type 'list)
|
||||
(json-object-type 'plist))
|
||||
(json-read-from-string data)))
|
||||
|
||||
|
||||
(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 (fg42/devtools-decode (websocket-frame-payload data)))
|
||||
(method (plist-get data :method))
|
||||
(params (plist-get data :params)))
|
||||
(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)
|
||||
(progn
|
||||
(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
|
||||
(fg42/devtools-encode (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 (lambda (x y)
|
||||
(fg42/devtools-on-message x y))
|
||||
: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
|
|
@ -0,0 +1,295 @@
|
|||
;;; 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 'cl)
|
||||
(require 'comint)
|
||||
(require 'fg42/devtools)
|
||||
;; For syntax highlighting
|
||||
(require 'js)
|
||||
|
||||
;;; Faces for console message -------------------------------------------------
|
||||
(defface fg42/devtools-log-warning
|
||||
'((t :inherit warning))
|
||||
"Basic face used to highlight warnings."
|
||||
:version "24.1"
|
||||
:group 'fg42/devtools-faces)
|
||||
|
||||
|
||||
(defface fg42/devtools-log-error
|
||||
'((t :inherit error))
|
||||
"Basic face used to highlight errors."
|
||||
:version "24.1"
|
||||
:group 'fg42/devtools-faces)
|
||||
|
||||
|
||||
(defface fg42/devtools-log-debug
|
||||
'((t :inherit font-lock-comment))
|
||||
"Basic face used to highlight debug-level messages."
|
||||
:version "24.1"
|
||||
:group 'fg42/devtools-faces)
|
||||
|
||||
|
||||
(defface fg42/devtools-log-log
|
||||
'((t :inherit default))
|
||||
"Basic face used to highlight regular messages."
|
||||
:version "24.1"
|
||||
:group 'fg42/devtools-faces)
|
||||
|
||||
|
||||
;; Customs & Variables --------------------------------------------------------
|
||||
(defcustom fg42/devtools-console-prompt "JS> "
|
||||
"Prompt used in fg42/devtools-console."
|
||||
:group 'fg42/devtools)
|
||||
|
||||
|
||||
(defvar fg42/devtools-console-mode-map
|
||||
(let ((map (copy-keymap widget-keymap))
|
||||
(menu-map (make-sparse-keymap)))
|
||||
;;(suppress-keymap map t)
|
||||
(define-key map "\t" 'fg42/devtools-async-completion-at-point)
|
||||
(define-key map "\C-cX" 'kite-clear-console)
|
||||
(define-key map "\C-cg" 'kite-console-visit-source)
|
||||
(define-key map "\C-ci" 'kite-show-log-entry)
|
||||
(define-key map "\C-j" 'fg42/devtools-console-send-input)
|
||||
(define-key map (kbd "RET") 'fg42/devtools-console-send-input)
|
||||
map)
|
||||
"Local keymap for `kite-console-mode' buffers.")
|
||||
|
||||
|
||||
(defvar fg42/devtools-console-input)
|
||||
|
||||
|
||||
(define-derived-mode fg42/devtools-console-mode comint-mode "fg42/devtools-console"
|
||||
"Provide a REPL into the visiting browser."
|
||||
:group 'fg42/devtools
|
||||
:syntax-table emacs-lisp-mode-syntax-table
|
||||
(setq comint-prompt-regexp (concat "^" (regexp-quote fg42/devtools-console-prompt))
|
||||
comint-get-old-input 'fg42/devtools-console-get-old-input ;; TODO: why?
|
||||
comint-input-sender 'fg42/devtools-console-input-sender
|
||||
comint-process-echoes nil)
|
||||
;; (set (make-local-variable 'comint-prompt-read-only) t)
|
||||
(unless (comint-check-proc (current-buffer))
|
||||
(start-process "fg42/devtools-console" (current-buffer) nil)
|
||||
(set-process-query-on-exit-flag (fg42/devtools-console-process) nil)
|
||||
|
||||
(set (make-local-variable 'font-lock-defaults)
|
||||
(list js--font-lock-keywords))
|
||||
|
||||
(goto-char (point-max))
|
||||
(set (make-local-variable 'comint-inhibit-carriage-motion) t)
|
||||
(comint-output-filter (fg42/devtools-console-process) fg42/devtools-console-prompt)
|
||||
(set-process-filter (fg42/devtools-console-process) 'comint-output-filter)))
|
||||
|
||||
|
||||
(defun fg42/devtools-console-append (data)
|
||||
(let ((buffer (get-buffer "*fg42/devtools-console*")))
|
||||
(when buffer
|
||||
(with-current-buffer buffer
|
||||
(comint-output-filter (fg42/devtools-console-process) (concat data "\n"))))))
|
||||
|
||||
|
||||
(defun fg42/devtools-console-process ()
|
||||
;; Return the current buffer's process.
|
||||
(get-buffer-process (current-buffer)))
|
||||
|
||||
|
||||
(defun fg42/devtools-console-get-old-input nil
|
||||
;; Return the previous input surrounding point
|
||||
(save-excursion
|
||||
(beginning-of-line)
|
||||
(unless (looking-at-p comint-prompt-regexp)
|
||||
(re-search-backward comint-prompt-regexp))
|
||||
(comint-skip-prompt)
|
||||
(buffer-substring (point) (progn (forward-sexp 1) (point)))))
|
||||
|
||||
|
||||
(defun fg42/devtools-console-input-sender (_proc input)
|
||||
;; Just sets the variable fg42/devtools-console-input, which is in the scope
|
||||
;; of `fg42/devtools-console-send-input's call.
|
||||
(setq fg42/devtools-console-input input))
|
||||
|
||||
|
||||
(defun fg42/devtools-console-send-input ()
|
||||
"Evaluate the current console prompt input."
|
||||
(interactive)
|
||||
(let (fg42/devtools-console-input) ; set by
|
||||
; kite-console-input-sender
|
||||
(comint-send-input) ; update history, markers etc.
|
||||
(fg42/devtools-console-eval-input fg42/devtools-console-input)))
|
||||
|
||||
|
||||
(defun fg42/devtools-console-eval-input (input)
|
||||
(fg42/devtools-send-eval
|
||||
input
|
||||
(lambda (result)
|
||||
(if (eq :json-false (plist-get result :wasThrown))
|
||||
(comint-output-filter
|
||||
(fg42/devtools-console-process)
|
||||
(format "%s\n%s"
|
||||
(plist-get (plist-get result :result) :value)
|
||||
fg42/devtools-console-prompt))
|
||||
;; TODO: fix and release object?
|
||||
(format "Error: %s\n%s"
|
||||
result
|
||||
fg42/devtools-console-prompt)))))
|
||||
;; (let ((object-id
|
||||
;; (kite--get result :result :objectId)))
|
||||
;; (when object-id
|
||||
;; (kite--release-object object-id)))
|
||||
|
||||
;; (defun fg42/devtools--eval-in-current-context (input success-function)
|
||||
;; "Evaluate INPUT in the remote remote debugger in the current
|
||||
;; execution context and asynchronously invoke SUCCESS-FUNCTION with
|
||||
;; the results in case of success."
|
||||
;; (let ((eval-params (list :expression input))
|
||||
;; (context-id (plist-get (kite-session-current-context
|
||||
;; kite-session)
|
||||
;; :id)))
|
||||
;; (when context-id
|
||||
;; (setq eval-params (plist-put eval-params :contextId context-id)))
|
||||
;; (kite-send
|
||||
;; "Runtime.evaluate"
|
||||
;; :params
|
||||
;; eval-params
|
||||
;; :success-function
|
||||
;; success-function)))
|
||||
|
||||
|
||||
(defconst fg42/devtools--identifier-part-regexp
|
||||
(rx
|
||||
word-boundary
|
||||
(1+ (or alnum
|
||||
?.
|
||||
(: "\\x" (repeat 2 xdigit))
|
||||
(: "\\u" (repeat 4 xdigit))))
|
||||
point)
|
||||
"Used by `kite-async-completion-at-point' to find a part of a
|
||||
JavaScript identifier.")
|
||||
|
||||
|
||||
(defun fg42/devtools-async-completion-at-point ()
|
||||
"Asynchronously fetch completions for the JavaScript expression
|
||||
at point and, once results have arrived, perform completion using
|
||||
`completion-in-region'.
|
||||
|
||||
Note: we can't use the usual mechanism of hooking into the
|
||||
completions API (`completion-at-point-functions') because it
|
||||
doesn't support asynchronicity."
|
||||
(interactive)
|
||||
(let (completion-begin)
|
||||
|
||||
;; Find the dotted JavaScript expression (consisting of
|
||||
;; identifiers only) before point. Note that we can't use just a
|
||||
;; single regex because greedy regexes don't work when searching
|
||||
;; backwards.
|
||||
(save-excursion
|
||||
(save-match-data
|
||||
(while (re-search-backward fg42/devtools--identifier-part-regexp nil t))
|
||||
(setq completion-begin (point))))
|
||||
|
||||
;; FIXME: the previous step is too broad, it will find identifiers
|
||||
;; starting with a digit. Could do a second pass here to make
|
||||
;; sure that we're looking at a valid expression, or improve error
|
||||
;; handling in `kite--get-properties-fast' to ensure that we do
|
||||
;; the right thing when the JavaScript side gets back to us with a
|
||||
;; complaint.
|
||||
|
||||
(when (< completion-begin (point))
|
||||
(let* ((components (split-string (buffer-substring-no-properties
|
||||
completion-begin
|
||||
(point))
|
||||
"\\."))
|
||||
(last-component (car (last components))))
|
||||
|
||||
(lexical-let ((lex-completion-begin (- (point)
|
||||
(length last-component)))
|
||||
(lex-completion-end (point)))
|
||||
(fg42/devtools--get-properties-fast
|
||||
(if (> (length components) 1)
|
||||
(mapconcat 'identity
|
||||
(subseq components
|
||||
0
|
||||
(- (length components) 1))
|
||||
".")
|
||||
"window")
|
||||
(concat "^" (regexp-quote last-component))
|
||||
(lambda (completions)
|
||||
(let* (completion-extra-properties
|
||||
completion-in-region-mode-predicate)
|
||||
(completion-in-region
|
||||
lex-completion-begin
|
||||
lex-completion-end
|
||||
completions)))))))))
|
||||
|
||||
|
||||
(defun fg42/devtools--get-properties-fast (object-expr js-regex callback)
|
||||
"Efficiently and asynchronously fetch matching property names
|
||||
for the object resulting from evaluating OBJECT-EXPR, a
|
||||
JavaScript expression. Only properties matching JS-REGEX, a
|
||||
regular expression using JavaScript syntax, are fetched. The
|
||||
resulting property names are passed as an unsorted list of
|
||||
strings to CALLBACK, which should accept a single parameter.
|
||||
|
||||
FIXME: no error handling."
|
||||
(lexical-let ((lex-callback callback))
|
||||
(fg42/devtools-send-eval
|
||||
(format "(function(val) {
|
||||
var regex = new RegExp('%s')
|
||||
var test = regex.test.bind(regex)
|
||||
var keys = new Set
|
||||
for (var key in val) regex.test(key) && keys.add(key)
|
||||
Object.getOwnPropertyNames(val).forEach(key => regex.test(key) && keys.add(key))
|
||||
return Array.from(keys)
|
||||
})(%s)"
|
||||
js-regex
|
||||
object-expr)
|
||||
(lambda (result)
|
||||
(funcall lex-callback (plist-get (plist-get result :result) :value))))))
|
||||
|
||||
|
||||
(defun fg42/devtools--release-object (object-id)
|
||||
"Release the object with the given OBJECT-ID on the browser
|
||||
side."
|
||||
(when (null object-id)
|
||||
(error "kite--release-object called with null OBJECT-ID"))
|
||||
(fg42/devtools-call-rpc "Runtime.releaseObject"
|
||||
`((objectId . ,object-id))))
|
||||
|
||||
|
||||
(defun fg42/devtools-console ()
|
||||
"Start a FG42 devtools console."
|
||||
(interactive)
|
||||
(when (not (get-buffer "*fg42/devtools-console*"))
|
||||
(with-current-buffer (get-buffer-create "*fg42/devtools-console*")
|
||||
(fg42/devtools-console-mode)))
|
||||
(pop-to-buffer (get-buffer "*fg42/devtools-console*")))
|
||||
|
||||
|
||||
(provide 'fg42/devtools/console)
|
||||
;; console.el ends here
|
|
@ -0,0 +1,25 @@
|
|||
;;; Helpers --- Helper functions of FG42
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
(require 'cl-lib)
|
||||
|
||||
;;;###autoload
|
||||
(defun what-face (pos)
|
||||
"Return the face of the thing at the current POS."
|
||||
(interactive "d")
|
||||
(let ((face (or (get-char-property (point) 'read-face-name)
|
||||
(get-char-property (point) 'face))))
|
||||
(if face (message "Face: %s" face) (message "No face at %d" pos))))
|
||||
|
||||
|
||||
;;;###autoload
|
||||
(defun env (&rest args)
|
||||
"Setup environment variables given as ARGS."
|
||||
(require 'seq)
|
||||
(let ((pairs (seq-partition args 2)))
|
||||
(dolist (pair pairs)
|
||||
(progn (setenv (substring (symbol-name (car pair)) 1) (car (cdr pair)))))))
|
||||
|
||||
|
||||
(provide 'fg42/helpers)
|
||||
;;; helpers ends here
|
|
@ -0,0 +1,77 @@
|
|||
;;; Utils --- A collection of utility functions for FG42
|
||||
;;; Commentary:
|
||||
;;; 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)))
|
||||
|
||||
|
||||
;;; Buffer helpers ------------------------------------------------------------
|
||||
(defun buffer-mode (buffer-or-string)
|
||||
"Return the major mode associated with a the given BUFFER-OR-STRING."
|
||||
(with-current-buffer buffer-or-string
|
||||
major-mode))
|
||||
|
||||
|
||||
(defun ->buffer (buffer-name data &optional fn)
|
||||
"Insert the given DATA into the given buffer provided by BUFFER-NAME.
|
||||
|
||||
It will create a the buffer if it doesn't exist. It will call the given FN
|
||||
at the end in context of the buffer. This function accepts only one argument
|
||||
with is the buffer."
|
||||
(let ((buf (get-buffer-create buffer-name)))
|
||||
(with-current-buffer buf
|
||||
(insert data)
|
||||
(funcall fn buf))))
|
||||
|
||||
|
||||
(defun inspect-as-json-and-switch (data)
|
||||
"Convert the given DATA to json and put it in the debug buffer."
|
||||
(->buffer fg42/inspect-buffer
|
||||
(->json data)
|
||||
#'(lambda (buf)
|
||||
(require 'json-mode)
|
||||
(json-mode)
|
||||
(json-pretty-print-buffer)
|
||||
(switch-to-buffer buf))))
|
||||
|
||||
|
||||
(defun inspect-as-json (data)
|
||||
"Convert the given DATA to json and put it in the debug buffer."
|
||||
(->buffer fg42/inspect-buffer
|
||||
(->json data)
|
||||
#'(lambda (buf)
|
||||
(require 'json-mode)
|
||||
(json-mode)
|
||||
(json-pretty-print-buffer))))
|
||||
|
||||
(defmacro inspect-expression (&rest body)
|
||||
"Pretty prints the result of the given BODY."
|
||||
`(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"))
|
||||
|
||||
|
||||
(provide 'fg42/utils)
|
||||
;;; utils.el ends here
|
|
@ -0,0 +1,10 @@
|
|||
;;; vars --- vars library of FG42
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
(defvar default-theme nil "Default FG42 theme.")
|
||||
|
||||
(defvar fg42/inspect-buffer "*inspect-buffer*"
|
||||
"The name of the buffer that is going to be used for data inspection.")
|
||||
|
||||
(provide 'fg42/vars)
|
||||
;;; vars.el ends here
|
Loading…
Reference in New Issue