From 840f42ec1fc42afbd24272b3ddfae79f3c90c62b Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Thu, 22 Aug 2019 19:06:39 +0100 Subject: [PATCH] devtools poc has been added --- .gitignore | 2 +- lib/extensions/editor.el | 2 +- lib/fg42/base.el | 4 +- lib/fg42/devtools.el | 333 +++++++++++++++++++++++++++++++++++ lib/fg42/devtools/console.el | 295 +++++++++++++++++++++++++++++++ lib/fg42/helpers.el | 25 +++ lib/fg42/utils.el | 77 ++++++++ lib/fg42/vars.el | 10 ++ 8 files changed, 744 insertions(+), 4 deletions(-) create mode 100644 lib/fg42/devtools.el create mode 100644 lib/fg42/devtools/console.el create mode 100644 lib/fg42/helpers.el create mode 100644 lib/fg42/utils.el create mode 100644 lib/fg42/vars.el diff --git a/.gitignore b/.gitignore index 3039b96..7fdbd37 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ packages/ lib/magic-buffer.el assets/ ./fg42^ -fg42 +/fg42 project-config/ docs/_build ./fonts/ diff --git a/lib/extensions/editor.el b/lib/extensions/editor.el index e040a04..3c4035d 100644 --- a/lib/extensions/editor.el +++ b/lib/extensions/editor.el @@ -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) diff --git a/lib/fg42/base.el b/lib/fg42/base.el index 0b01f91..d383a85 100644 --- a/lib/fg42/base.el +++ b/lib/fg42/base.el @@ -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) diff --git a/lib/fg42/devtools.el b/lib/fg42/devtools.el new file mode 100644 index 0000000..9084684 --- /dev/null +++ b/lib/fg42/devtools.el @@ -0,0 +1,333 @@ +;;; fg42-devtools --- Webkit devtool driver for FG42 +;; +;; Copyright (c) 2019 Sameer Rahmani +;; +;; Author: Sameer Rahmani +;; 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 . +;; +;;; 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 diff --git a/lib/fg42/devtools/console.el b/lib/fg42/devtools/console.el new file mode 100644 index 0000000..28fd4b3 --- /dev/null +++ b/lib/fg42/devtools/console.el @@ -0,0 +1,295 @@ +;;; fg42-devtools --- Webkit devtool driver for FG42 +;; +;; Copyright (c) 2019 Sameer Rahmani +;; +;; Author: Sameer Rahmani +;; 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 . +;; +;;; 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 diff --git a/lib/fg42/helpers.el b/lib/fg42/helpers.el new file mode 100644 index 0000000..b040875 --- /dev/null +++ b/lib/fg42/helpers.el @@ -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 diff --git a/lib/fg42/utils.el b/lib/fg42/utils.el new file mode 100644 index 0000000..31d69bb --- /dev/null +++ b/lib/fg42/utils.el @@ -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 diff --git a/lib/fg42/vars.el b/lib/fg42/vars.el new file mode 100644 index 0000000..91ac6c7 --- /dev/null +++ b/lib/fg42/vars.el @@ -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