summaryrefslogtreecommitdiff
path: root/elpa/skewer-mode-20200304.1142/skewer-repl.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/skewer-mode-20200304.1142/skewer-repl.el')
-rw-r--r--elpa/skewer-mode-20200304.1142/skewer-repl.el210
1 files changed, 210 insertions, 0 deletions
diff --git a/elpa/skewer-mode-20200304.1142/skewer-repl.el b/elpa/skewer-mode-20200304.1142/skewer-repl.el
new file mode 100644
index 0000000..6a55d13
--- /dev/null
+++ b/elpa/skewer-mode-20200304.1142/skewer-repl.el
@@ -0,0 +1,210 @@
+;;; skewer-repl.el --- create a REPL in a visiting browser -*- lexical-binding: t; -*-
+
+;; This is free and unencumbered software released into the public domain.
+
+;;; Commentary:
+
+;; This is largely based on of IELM's code. Run `skewer-repl' to
+;; switch to the REPL buffer and evaluate code. Use
+;; `skewer-repl-toggle-strict-mode' to turn strict mode on and off.
+
+;; If `compilation-search-path' is set up properly, along with
+;; `skewer-path-strip-level', asynchronous errors will provide
+;; clickable error messages that will take you to the source file of
+;; the error. This is done using `compilation-shell-minor-mode'.
+
+;;; Code:
+
+(require 'comint)
+(require 'compile)
+(require 'skewer-mode)
+
+(defcustom skewer-repl-strict-p nil
+ "When non-NIL, all REPL evaluations are done in strict mode."
+ :type 'boolean
+ :group 'skewer)
+
+(defcustom skewer-repl-prompt "js> "
+ "Prompt string for JavaScript REPL."
+ :type 'string
+ :group 'skewer)
+
+(defvar skewer-repl-welcome
+ (propertize "*** Welcome to Skewer ***\n"
+ 'font-lock-face 'font-lock-comment-face)
+ "Header line to show at the top of the REPL buffer. Hack
+notice: this allows log messages to appear before anything is
+evaluated because it provides insertable space at the top of the
+buffer.")
+
+(defun skewer-repl-process ()
+ "Return the process for the skewer REPL."
+ (get-buffer-process (current-buffer)))
+
+(defface skewer-repl-log-face
+ '((((class color) (background light))
+ :foreground "#77F")
+ (((class color) (background dark))
+ :foreground "#77F"))
+ "Face for skewer.log() messages."
+ :group 'skewer)
+
+(define-derived-mode skewer-repl-mode comint-mode "js-REPL"
+ "Provide a REPL into the visiting browser."
+ :group 'skewer
+ :syntax-table emacs-lisp-mode-syntax-table
+ (setq comint-prompt-regexp (concat "^" (regexp-quote skewer-repl-prompt))
+ comint-input-sender 'skewer-input-sender
+ comint-process-echoes nil)
+ ;; Make opportunistic use of company-mode, but don't require it.
+ ;; This means company-backends may be undeclared, so don't emit a
+ ;; warning about it.
+ (with-no-warnings
+ (setq-local company-backends '(company-skewer-repl)))
+ (unless (comint-check-proc (current-buffer))
+ (insert skewer-repl-welcome)
+ (start-process "skewer-repl" (current-buffer) nil)
+ (set-process-query-on-exit-flag (skewer-repl-process) nil)
+ (goto-char (point-max))
+ (set (make-local-variable 'comint-inhibit-carriage-motion) t)
+ (comint-output-filter (skewer-repl-process) skewer-repl-prompt)
+ (set-process-filter (skewer-repl-process) 'comint-output-filter)))
+
+(defun skewer-repl-toggle-strict-mode ()
+ "Toggle strict mode for expressions evaluated by the REPL."
+ (interactive)
+ (setq skewer-repl-strict-p (not skewer-repl-strict-p))
+ (message "REPL strict mode %s"
+ (if skewer-repl-strict-p "enabled" "disabled")))
+
+(defun skewer-input-sender (_ input)
+ "REPL comint handler."
+ (skewer-eval input 'skewer-post-repl
+ :verbose t :strict skewer-repl-strict-p))
+
+(defun skewer-post-repl (result)
+ "Callback for reporting results in the REPL."
+ (let ((buffer (get-buffer "*skewer-repl*"))
+ (output (cdr (assoc 'value result))))
+ (when buffer
+ (with-current-buffer buffer
+ (comint-output-filter (skewer-repl-process)
+ (concat output "\n" skewer-repl-prompt))))))
+
+(defvar skewer-repl-types
+ '(("log" . skewer-repl-log-face)
+ ("error" . skewer-error-face))
+ "Faces to use for different types of log messages.")
+
+(defun skewer-log-filename (log)
+ "Create a log string for the source file in LOG if present."
+ (let ((name (cdr (assoc 'filename log)))
+ (line (cdr (assoc 'line log)))
+ (column (cdr (assoc 'column log))))
+ (when name
+ (concat (format "\n at %s:%s" name line)
+ (if column (format ":%s" column))))))
+
+(defun skewer-post-log (log)
+ "Callback for logging messages to the REPL."
+ (let* ((buffer (get-buffer "*skewer-repl*"))
+ (face (cdr (assoc (cdr (assoc 'type log)) skewer-repl-types)))
+ (value (or (cdr (assoc 'value log)) "<unspecified error>"))
+ (output (propertize value 'font-lock-face face)))
+ (when buffer
+ (with-current-buffer buffer
+ (save-excursion
+ (goto-char (point-max))
+ (forward-line 0)
+ (if (bobp)
+ (insert (concat output (skewer-log-filename log) "\n"))
+ (backward-char)
+ (insert (concat "\n" output (skewer-log-filename log)))))))))
+
+(defcustom skewer-path-strip-level 1
+ "Number of folders which will be stripped from url when discovering paths.
+Use this to limit path matching to files in your filesystem. You
+may want to add some folders to `compilation-search-path', so
+matched files can be found."
+ :type 'number
+ :group 'skewer)
+
+(defun skewer-repl-mode-compilation-shell-hook ()
+ "Setup compilation shell minor mode for highlighting files"
+ (let ((error-re (format "^[ ]*at https?://[^/]+/\\(?:[^/]+/\\)\\{%d\\}\\([^:?#]+\\)\\(?:[?#][^:]*\\)?:\\([[:digit:]]+\\)\\(?::\\([[:digit:]]+\\)\\)?$" skewer-path-strip-level)))
+ (setq-local compilation-error-regexp-alist `((,error-re 1 2 3 2))))
+ (compilation-shell-minor-mode 1))
+
+;;;###autoload
+(defun skewer-repl--response-hook (response)
+ "Catches all browser messages logging some to the REPL."
+ (let ((type (cdr (assoc 'type response))))
+ (when (member type '("log" "error"))
+ (skewer-post-log response))))
+
+;;;###autoload
+(defun skewer-repl ()
+ "Start a JavaScript REPL to be evaluated in the visiting browser."
+ (interactive)
+ (when (not (get-buffer "*skewer-repl*"))
+ (with-current-buffer (get-buffer-create "*skewer-repl*")
+ (skewer-repl-mode)))
+ (pop-to-buffer (get-buffer "*skewer-repl*")))
+
+(defun company-skewer-repl (command &optional arg &rest _args)
+ "Skewerl REPL backend for company-mode.
+See `company-backends' for more info about COMMAND and ARG."
+ (interactive (list 'interactive))
+ (cl-case command
+ (interactive
+ (with-no-warnings ;; opportunistic use of company-mode
+ (company-begin-backend 'company-skewer-repl)))
+ (prefix (skewer-repl-company-prefix))
+ (ignore-case t)
+ (sorted t)
+ (candidates (cons :async
+ (lambda (callback)
+ (skewer-repl-get-completions arg callback))))))
+
+(defun skewer-repl-get-completions (arg callback)
+ "Get the completion list matching the prefix ARG.
+Evaluate CALLBACK with the completion candidates."
+ (let* ((expression (skewer-repl--get-completion-expression arg))
+ (pattern (if expression
+ (substring arg (1+ (length expression)))
+ arg)))
+ (skewer-eval (or expression "window")
+ (lambda (result)
+ (cl-loop with value = (cdr (assoc 'value result))
+ for key being the elements of value
+ when expression
+ collect (concat expression "." key) into results
+ else
+ collect key into results
+ finally (funcall callback results)))
+ :type "completions"
+ :extra `((regexp . ,pattern)))))
+
+(defun skewer-repl--get-completion-expression (arg)
+ "Get completion expression from ARG."
+ (let ((components (split-string arg "\\.")))
+ (when (> (length components) 1)
+ (mapconcat #'identity (cl-subseq components 0 -1) "."))))
+
+(defun skewer-repl-company-prefix ()
+ "Prefix for company."
+ (and (eq major-mode 'skewer-repl-mode)
+ (or (with-no-warnings ;; opportunistic use of company-mode
+ (company-grab-symbol-cons "\\." 1))
+ 'stop)))
+
+;;;###autoload
+(eval-after-load 'skewer-mode
+ '(progn
+ (add-hook 'skewer-response-hook #'skewer-repl--response-hook)
+ (add-hook 'skewer-repl-mode-hook #'skewer-repl-mode-compilation-shell-hook)
+ (define-key skewer-mode-map (kbd "C-c C-z") #'skewer-repl)))
+
+(provide 'skewer-repl)
+
+;;; skewer-repl.el ends here