devtools poc has been added

This commit is contained in:
Sameer Rahmani 2019-08-22 19:06:39 +01:00
parent 8e4f9c252e
commit 840f42ec1f
8 changed files with 744 additions and 4 deletions

2
.gitignore vendored
View File

@ -8,7 +8,7 @@ packages/
lib/magic-buffer.el
assets/
./fg42^
fg42
/fg42
project-config/
docs/_build
./fonts/

View File

@ -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)

View File

@ -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)

333
lib/fg42/devtools.el Normal file
View File

@ -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

View File

@ -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

25
lib/fg42/helpers.el Normal file
View File

@ -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

77
lib/fg42/utils.el Normal file
View File

@ -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

10
lib/fg42/vars.el Normal file
View File

@ -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