Add jshell stuff
This commit is contained in:
parent
3ebc12ddc9
commit
e2db4e1193
287
elisp/inferior-jshell.el
Normal file
287
elisp/inferior-jshell.el
Normal file
@ -0,0 +1,287 @@
|
||||
;;; inferior-jshell.el --- Run JShell in a comint buffer -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
(require 'comint)
|
||||
(require 'cl-lib)
|
||||
(require 'cc-mode)
|
||||
(require 'treesit)
|
||||
(eval-when-compile (require 'rx))
|
||||
|
||||
(defgroup inferior-jshell ()
|
||||
"Run JShell in a comint buffer."
|
||||
:group 'comint)
|
||||
|
||||
(defvar jshell-buffer-name "*jshell*"
|
||||
"Name to use for inferior jshell process buffer.")
|
||||
|
||||
(defvar jshell-program "jshell"
|
||||
"The program to default to for `run-jshell'.")
|
||||
|
||||
(defvar jshell-switches ()
|
||||
"List of arguments to pass to jshell in `run-jshell'.")
|
||||
|
||||
(defvar-local jshell--fontification-buffer nil
|
||||
"The fontification buffer for the current jshell buffer.")
|
||||
|
||||
(defvar-local jshell--skip-next-lines 0
|
||||
"Number of lines of output to skip.")
|
||||
|
||||
(defun jshell--preoutput-filter-function (output)
|
||||
"Preoutput filter function for jshell.
|
||||
OUTPUT is the new text to be inserted."
|
||||
(if (<= jshell--skip-next-lines 0)
|
||||
output
|
||||
(let* ((lines (string-lines output))
|
||||
(cnt (length lines)))
|
||||
(if (> cnt jshell--skip-next-lines)
|
||||
(prog1
|
||||
(string-join (nthcdr jshell--skip-next-lines lines) "\n")
|
||||
(setq jshell--skip-next-lines 0))
|
||||
(cl-decf jshell--skip-next-lines cnt)
|
||||
(when (and (not (string-empty-p output))
|
||||
(/= ?\n (elt output (1- (length output)))))
|
||||
(cl-incf jshell--skip-next-lines))
|
||||
""))))
|
||||
|
||||
(defun jshell--get-fontification-buffer ()
|
||||
"Return the jshell fontification buffer."
|
||||
(if (buffer-live-p jshell--fontification-buffer)
|
||||
jshell--fontification-buffer
|
||||
(let ((buffer (generate-new-buffer
|
||||
(format " %s-fontification-buffer" (buffer-name)))))
|
||||
(with-current-buffer buffer
|
||||
(unless (derived-mode-p 'c++-mode)
|
||||
(let ((delayed-mode-hooks nil))
|
||||
(delay-mode-hooks
|
||||
(java-mode))))
|
||||
(when (eq c-basic-offset 'set-from-style)
|
||||
(setq-local c-basic-offset standard-indent))
|
||||
(let ((inhibit-message t))
|
||||
(indent-tabs-mode -1))
|
||||
(unless font-lock-mode
|
||||
(font-lock-mode 1)))
|
||||
(setq-local jshell--fontification-buffer buffer))))
|
||||
|
||||
(defmacro jshell--with-font-lock-buffer (&rest body)
|
||||
"Execute BODY in the jshell indirect buffer.
|
||||
Note that this erases the buffer before doing anything."
|
||||
`(with-current-buffer (jshell--get-fontification-buffer)
|
||||
(erase-buffer)
|
||||
,@body))
|
||||
|
||||
(defun jshell--fontify-current-input ()
|
||||
"Function called from `post-command-hook' to fontify the current input."
|
||||
(when-let ((proc (get-buffer-process (current-buffer)))
|
||||
(start (process-mark proc))
|
||||
(end (point-max))
|
||||
(input (buffer-substring-no-properties start end))
|
||||
(fontified (jshell--with-font-lock-buffer
|
||||
(insert input)
|
||||
(font-lock-ensure)
|
||||
(buffer-string)))
|
||||
(len (length fontified))
|
||||
(i 0))
|
||||
;; mostly from:
|
||||
;; `python-shell-font-lock-post-command-hook'
|
||||
(while (not (= i len))
|
||||
(let* ((props (text-properties-at i fontified))
|
||||
(change-i (or (next-property-change i fontified)
|
||||
len)))
|
||||
(when-let ((face (plist-get props 'face)))
|
||||
(setf (plist-get props 'face) nil
|
||||
(plist-get props 'font-lock-face) face))
|
||||
(set-text-properties (+ start i) (+ start change-i) props)
|
||||
(setq i change-i)))))
|
||||
|
||||
(defun jshell--bounds-of-last-prompt ()
|
||||
"Return the bounds of the last jshell prompt.
|
||||
This returns a cons."
|
||||
(save-excursion
|
||||
(let ((end (process-mark (get-buffer-process (current-buffer)))))
|
||||
(goto-char end)
|
||||
(cons (pos-bol) end))))
|
||||
|
||||
(defun jshell--remove-extra-indentation (count)
|
||||
"Remove COUNT spaces from the start of each line."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(back-to-indentation)
|
||||
(let ((indent (- (point) (pos-bol))))
|
||||
(when (> indent count)
|
||||
(delete-char (- count))))
|
||||
(forward-line))))
|
||||
|
||||
(defun jshell--indent-line-function ()
|
||||
"`indent-line-function' for jshell comint buffers."
|
||||
(let* ((start (process-mark (get-buffer-process (current-buffer)))))
|
||||
;; don't indent the first line
|
||||
(unless (= (pos-bol) (save-excursion (goto-char start) (pos-bol)))
|
||||
(let* ((input (buffer-substring-no-properties start (pos-eol)))
|
||||
(prompt-size (let ((bound (jshell--bounds-of-last-prompt)))
|
||||
(- (cdr bound) (car bound))))
|
||||
(col (jshell--with-font-lock-buffer
|
||||
(insert input)
|
||||
(jshell--remove-extra-indentation prompt-size)
|
||||
(c-indent-line nil t)
|
||||
(back-to-indentation)
|
||||
(- (point) (pos-bol)))))
|
||||
(save-excursion
|
||||
(indent-line-to (+ prompt-size col)))
|
||||
(skip-syntax-forward "-")))))
|
||||
|
||||
(defun jshell-send-input ()
|
||||
"Like `comint-send-input', but with some extra stuff for jshell."
|
||||
(interactive)
|
||||
(let ((pmark (process-mark (get-buffer-process (current-buffer))))
|
||||
(end (if comint-eol-on-send (pos-eol) (point))))
|
||||
(with-restriction pmark end
|
||||
(let ((res (syntax-ppss (point-max))))
|
||||
(without-restriction
|
||||
(cond
|
||||
;; open string
|
||||
((cl-fourth res)
|
||||
(message "Unterminated string"))
|
||||
;; unmatched blocks or comment
|
||||
((or (numberp (cl-fifth res))
|
||||
(not (zerop (cl-first res)))
|
||||
;; trailing . character
|
||||
(save-excursion
|
||||
(end-of-line)
|
||||
(skip-syntax-backward "-")
|
||||
(eql (char-before) ?.)))
|
||||
(newline-and-indent))
|
||||
(t
|
||||
;; jshell will echo out each line we send it, ignore all of them
|
||||
(setq-local jshell--skip-next-lines (count-lines pmark end))
|
||||
(when (= pmark end)
|
||||
(cl-incf jshell--skip-next-lines))
|
||||
;; also, methods add a bunch of extra newlines
|
||||
(when (>= jshell--skip-next-lines 2)
|
||||
(cl-incf jshell--skip-next-lines (- jshell--skip-next-lines 2)))
|
||||
(comint-send-input))))))))
|
||||
|
||||
(defvar-keymap inferior-jshell-mode-map
|
||||
:doc "Keymap for `inferior-jshell-mode'."
|
||||
:parent comint-mode-map
|
||||
"RET" #'jshell-send-input)
|
||||
|
||||
(defvar inferior-jshell-mode-syntax-table (copy-syntax-table java-mode-syntax-table)
|
||||
"Syntax table for `inferior-jshell-mode'.")
|
||||
|
||||
(defun jshell--kill-fontification-buffer ()
|
||||
"Kill the current `jshell--fontification-buffer'."
|
||||
(kill-buffer jshell--fontification-buffer))
|
||||
|
||||
(define-derived-mode inferior-jshell-mode comint-mode "Inferior Jshell"
|
||||
"Major mode for buffers running inferior CERN jshell processes."
|
||||
:group 'inferior-jshell
|
||||
:syntax-table inferior-jshell-mode-syntax-table
|
||||
(setq-local comint-highlight-input nil
|
||||
indent-line-function #'jshell--indent-line-function
|
||||
electric-indent-chars '(?\n ?}))
|
||||
(add-hook 'comint-preoutput-filter-functions
|
||||
#'jshell--preoutput-filter-function
|
||||
nil t)
|
||||
(add-hook 'post-command-hook
|
||||
#'jshell--fontify-current-input
|
||||
nil t)
|
||||
(add-hook 'kill-buffer-hook
|
||||
#'jshell--kill-fontification-buffer
|
||||
nil t))
|
||||
|
||||
(cl-defun run-jshell (&optional (cmd jshell-program) (switches jshell-switches))
|
||||
"Run CERN jshell in a comint buffer.
|
||||
CMD is the shell command to run. It defaults to `jshell-program'. SWITCHES are
|
||||
the arguments to pass. They default to `jshell-switches'."
|
||||
(interactive)
|
||||
(pop-to-buffer
|
||||
(with-current-buffer (get-buffer-create jshell-buffer-name)
|
||||
(prog1 (current-buffer)
|
||||
(unless (process-live-p (get-buffer-process (current-buffer)))
|
||||
(inferior-jshell-mode)
|
||||
(comint-exec (current-buffer) "Inferior Jshell" cmd nil switches))))))
|
||||
|
||||
(cl-defun jshell--find-buffer ()
|
||||
"Find and return a live jshell buffer."
|
||||
(dolist (buffer (buffer-list))
|
||||
(with-current-buffer buffer
|
||||
(when (and (derived-mode-p 'inferior-jshell-mode)
|
||||
(process-live-p (get-buffer-process buffer)))
|
||||
(cl-return-from jshell--find-buffer buffer)))))
|
||||
|
||||
(defun jshell-eval (code)
|
||||
"Evaluate CODE in a live JShell buffer."
|
||||
(interactive "sEval: ")
|
||||
(let ((buffer (jshell--find-buffer)))
|
||||
(unless buffer
|
||||
(user-error "No live JShell buffer found"))
|
||||
(with-current-buffer buffer
|
||||
(let* ((start (process-mark (get-buffer-process buffer)))
|
||||
(end (point-max))
|
||||
(old (buffer-substring-no-properties start end)))
|
||||
(delete-region start end)
|
||||
(goto-char (point-max))
|
||||
(insert code)
|
||||
(goto-char (point-max))
|
||||
(jshell-send-input)
|
||||
(goto-char (point-max))
|
||||
(insert old)
|
||||
(goto-char (point-max))))))
|
||||
|
||||
(defun jshell-eval-region (start end)
|
||||
"Evaluate the current buffer from START to END in a live JShell buffer.
|
||||
START and END default to the current region."
|
||||
(interactive "r")
|
||||
(jshell-eval (buffer-substring-no-properties start end)))
|
||||
|
||||
(defun jshell-eval-buffer ()
|
||||
"Send the current buffer to a live JShell buffer."
|
||||
(interactive)
|
||||
(jshell-eval-region (point-min) (point-max)))
|
||||
|
||||
(defun jshell-eval-defun ()
|
||||
"Send the defun under point to a live JShell buffer."
|
||||
(interactive)
|
||||
(let ((bounds (bounds-of-thing-at-point 'defun)))
|
||||
(unless bounds
|
||||
(user-error "No defun under point"))
|
||||
(jshell-eval-region (car bounds) (cdr bounds))))
|
||||
|
||||
(defun jshell-eval-expression ()
|
||||
"Send the Java expression under point to a live JShell buffer.
|
||||
This only works in `java-ts-mode'."
|
||||
(interactive)
|
||||
(let ((root (treesit-buffer-root-node)))
|
||||
(let ((node (car (or (treesit-query-range
|
||||
root '([(expression_statement)
|
||||
(field_declaration)
|
||||
(local_variable_declaration)
|
||||
(import_declaration)]
|
||||
@exp)
|
||||
(point) (1+ (point)))
|
||||
(treesit-query-range
|
||||
root '([(parenthesized_expression)
|
||||
(binary_expression)
|
||||
(update_expression)
|
||||
(unary_expression)]
|
||||
@exp)
|
||||
(point) (1+ (point)))))))
|
||||
(unless node
|
||||
(user-error "No expression found under point"))
|
||||
(let ((text (buffer-substring-no-properties (car node) (cdr node))))
|
||||
(when (string-match (rx (* (syntax whitespace))
|
||||
";"
|
||||
(* (syntax whitespace)) eos)
|
||||
text)
|
||||
(setq text (substring text 0 (match-beginning 0))))
|
||||
(when (string-match (rx bos (* (syntax whitespace)) "("
|
||||
(group (* any))
|
||||
")" (* (syntax whitespace)) eos)
|
||||
text)
|
||||
(setq text (match-string 1 text)))
|
||||
(jshell-eval text)))))
|
||||
|
||||
|
||||
(provide 'inferior-jshell)
|
||||
;;; inferior-jshell.el ends here
|
6
init.el
6
init.el
@ -1765,10 +1765,16 @@ otherwise, call `bibtex-find-text'."
|
||||
;; My dev environment for ROS2
|
||||
(require 'arch-ros2)
|
||||
|
||||
(require 'inferior-jshell)
|
||||
;; java-ts-mode
|
||||
(use-package java-ts-mode
|
||||
:hook ((java-ts-mode . trusted-files-eglot-ensure-if-safe)
|
||||
(java-ts-mode . my/-setup-java-ts-mode))
|
||||
:bind (:map java-ts-mode-map
|
||||
("C-c C-e" . jshell-eval-expression)
|
||||
("C-M-x" . jshell-eval-defun)
|
||||
("C-c C-r" . jshell-eval-region)
|
||||
("C-c C-b" . jshell-eval-buffer))
|
||||
:config
|
||||
(defun my/-setup-java-ts-mode ()
|
||||
(let ((rules (car treesit-simple-indent-rules)))
|
||||
|
Loading…
x
Reference in New Issue
Block a user