diff options
Diffstat (limited to 'elpa/skewer-mode-20200304.1142/skewer-repl.el')
-rw-r--r-- | elpa/skewer-mode-20200304.1142/skewer-repl.el | 210 |
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 |