inf-clojure-3.2.1/ 0000755 0001752 0001753 00000000000 14266464005 012372 5 ustar elpa elpa inf-clojure-3.2.1/.github/ 0000755 0001752 0001753 00000000000 14105036072 013721 5 ustar elpa elpa inf-clojure-3.2.1/.github/ISSUE_TEMPLATE.md 0000644 0001752 0001753 00000001733 14105036072 016432 0 ustar elpa elpa *Use the template below when reporting bugs. Please, make sure that
you're running the latest stable inf-clojure and that the problem you're reporting
hasn't been reported (and potentially fixed) already.*
*When requesting new features or improvements to existing features you can
discard the template completely. Just make sure to make a good case for your
request.*
**Remove all of the placeholder text in your final report!**
## Expected behavior
## Actual behavior
## Steps to reproduce the problem
*This is extremely important! Providing us with a reliable way to reproduce
a problem will expedite its solution.*
## Environment & Version information
### inf-clojure version information
*E.g. 1.4.0*
### Lein/Boot version
*E.g. Lein 2.6.1* (skip this when you didn't use Lein or Boot to start a REPL)
### Emacs version
*E.g. 24.5* (use M-x emacs-version to check it if unsure)
### Operating system
*E.g. Fedora 23, OS X 10.11 "El Capitan", Windows 10, etc*
inf-clojure-3.2.1/.github/CONTRIBUTING.md 0000644 0001752 0001753 00000003147 14105036072 016157 0 ustar elpa elpa # Contributing
If you discover issues, have ideas for improvements or new features,
please report them to the [issue tracker][1] of the repository or
submit a pull request. Please, try to follow these guidelines when you
do so.
## Issue reporting
* Check that the issue has not already been reported.
* Check that the issue has not already been fixed in the latest code
(a.k.a. `master`).
* Be clear, concise and precise in your description of the problem.
* Open an issue with a descriptive title and a summary in grammatically correct,
complete sentences.
* Mention your Emacs version and operating system.
* Mention the `inf-clojure` version.
* Include any relevant code to the issue summary.
## Pull requests
* Read [how to properly contribute to open source projects on Github][2].
* Use a topic branch to easily amend a pull request later, if necessary.
* Write [good commit messages][3].
* Mention related tickets in the commit messages (e.g. `[Fix #N] Add command ...`)
* Use the same coding conventions as the rest of the project.
* Verify your Emacs Lisp code with `checkdoc` (C-c ? d).
* [Squash related commits together][5].
* Open a [pull request][4] that relates to *only* one subject with a clear title
and description in grammatically correct, complete sentences.
[1]: https://github.com/clojure-emacs/inf-clojure/issues
[2]: http://gun.io/blog/how-to-github-fork-branch-and-pull-request
[3]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[4]: https://help.github.com/articles/using-pull-requests
[5]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html
inf-clojure-3.2.1/.github/PULL_REQUEST_TEMPLATE.md 0000644 0001752 0001753 00000001231 14105036072 017517 0 ustar elpa elpa **Replace this placeholder text with a summary of the changes in your PR.
The more detailed you are, the better.**
-----------------
Before submitting the PR make sure the following things have been done (and denote this
by checking the relevant checkboxes):
- [ ] The commits are consistent with our [contribution guidelines][1]
- [ ] The new code is not generating bytecode or `M-x checkdoc` warnings
- [ ] You've updated the changelog (if adding/changing user-visible functionality)
- [ ] You've updated the readme (if adding/changing user-visible functionality)
Thanks!
[1]: https://github.com/clojure-emacs/inf-clojure/blob/master/.github/CONTRIBUTING.md
inf-clojure-3.2.1/.github/FUNDING.yml 0000644 0001752 0001753 00000000166 14105036072 015541 0 ustar elpa elpa # These are supported funding model platforms
github: bbatsov
patreon: bbatsov
custom: https://www.paypal.me/bbatsov
inf-clojure-3.2.1/Makefile 0000644 0001752 0001753 00000000314 14105036072 014017 0 ustar elpa elpa .PHONY: clean version test
all: build
clean:
rm -rf .cask
.cask:
cask install
cask update
version:
emacs --version
build: version .cask
cask build
test: version .cask
cask exec buttercup -L .
inf-clojure-3.2.1/Cask 0000644 0001752 0001753 00000000306 14105036072 013164 0 ustar elpa elpa (source gnu)
(source melpa)
(package-file "inf-clojure.el")
(files "*.el" (:exclude ".dir-locals.el"))
(development
(depends-on "clojure-mode")
(depends-on "buttercup")
(depends-on "assess"))
inf-clojure-3.2.1/inf-clojure.el 0000644 0001752 0001753 00000174743 14266464004 015150 0 ustar elpa elpa ;;; inf-clojure.el --- Run an external Clojure process in an Emacs buffer -*- lexical-binding: t; -*-
;; Copyright © 2014-2022 Bozhidar Batsov
;; Authors: Bozhidar Batsov
;; Olin Shivers
;; Maintainer: Bozhidar Batsov
;; URL: http://github.com/clojure-emacs/inf-clojure
;; Keywords: processes, comint, clojure
;; Version: 3.2.1
;; Package-Requires: ((emacs "25.1") (clojure-mode "5.11"))
;; This file is not part of GNU Emacs.
;; GNU Emacs 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.
;; GNU Emacs 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 GNU Emacs. If not, see .
;;; Commentary:
;;
;; This package provides basic interaction with a Clojure subprocess (REPL).
;; It's based on ideas from the popular `inferior-lisp` package.
;;
;; `inf-clojure` has two components - a nice Clojure REPL with
;; auto-completion and a minor mode (`inf-clojure-minor-mode`), which
;; extends `clojure-mode` with commands to evaluate forms directly in the
;; REPL.
;;
;; `inf-clojure` provides a set of essential features for interactive
;; Clojure(Script) development:
;;
;; * REPL
;; * Interactive code evaluation
;; * Code completion
;; * Definition lookup
;; * Documentation lookup
;; * ElDoc
;; * Apropos
;; * Macroexpansion
;; * Support connecting to socket REPLs
;; * Support for Lumo
;; * Support for Planck
;; * Support for Joker
;;
;; For a more powerful/full-featured solution see https://github.com/clojure-emacs/cider.
;;
;; If you're installing manually, you'll need to:
;;
;; * drop the file somewhere on your load path (perhaps ~/.emacs.d)
;; * Add the following lines to your .emacs file:
;;
;; (autoload 'inf-clojure "inf-clojure" "Run an inferior Clojure process" t)
;; (add-hook 'clojure-mode-hook #'inf-clojure-minor-mode)
;;; Code:
(require 'comint)
(require 'clojure-mode)
(require 'eldoc)
(require 'thingatpt)
(require 'ansi-color)
(require 'cl-lib)
(require 'subr-x)
(defvar inf-clojure-startup-forms '((lein . "lein repl")
(boot . "boot repl")
(clojure . "clojure")
(cljs . "clojure -m cljs.main -r")
(planck . "planck -d")
(babashka . "bb")
(lumo . "lumo -d")
(joker . "joker")))
(defvar inf-clojure-repl-features
'((cljs . ((doc . "(cljs.repl/doc %s)")
(source . "(cljs.repl/source %s)")
(arglists . "(try (->> '%s cljs.core/resolve cljs.core/meta :arglists) (catch :default _ nil))")
(apropos . "(cljs.repl/apropos \"%s\")")
(ns-vars . "(cljs.repl/dir %s)")
(set-ns . "(in-ns '%s)")
(macroexpand . "(cljs.core/macroexpand '%s)")
(macroexpand-1 . "(cljs.core/macroexpand-1 '%s)")))
(lumo . ((load . "(clojure.core/load-file \"%s\")")
(doc . "(lumo.repl/doc %s)")
(source . "(lumo.repl/source %s)")
(arglists .
"(let [old-value lumo.repl/*pprint-results*]
(set! lumo.repl/*pprint-results* false)
(js/setTimeout #(set! lumo.repl/*pprint-results* old-value) 0)
(lumo.repl/get-arglists \"%s\"))")
(apropos . "(lumo.repl/apropos \"%s\")")
(ns-vars . "(lumo.repl/dir %s)")
(set-ns . "(in-ns '%s)")
(macroexpand . "(macroexpand-1 '%s)")
(macroexpand-1 . "(macroexpand-1 '%s)")
(completion .
"(let [ret (atom nil)]
(lumo.repl/get-completions \"%s\" (fn [res] (reset! ret (map str res))))
@ret)")))
(planck . ((load . "(load-file \"%s\")")
(doc . "(planck.repl/doc %s)")
(source . "(planck.repl/source %s)")
(arglists . "(planck.repl/get-arglists \"%s\")")
(apropos . "(doseq [var (sort (planck.repl/apropos \"%s\"))] (println (str var)))")
(ns-vars . "(planck.repl/dir %s)")
(set-ns . "(in-ns '%s)")
(macroexpand . "(macroexpand '%s)")
(macroexpand-1 . "(macroexpand-1 '%s)")
(completion . "(seq (js->clj (#'planck.repl/get-completions \"%s\")))")))
(joker . ((load . "(load-file \"%s\")")
(doc . "(joker.repl/doc %s)")
(arglists .
"(try
(:arglists
(joker.core/meta
(joker.core/resolve
(joker.core/read-string \"%s\"))))
(catch Error _ nil))")
(set-ns . "(in-ns '%s)")
(macroexpand . "(macroexpand '%s)")
(macroexpand-1 . "(macroexpand-1 '%s)")))
(babashka . ((load . "(clojure.core/load-file \"%s\")")
(doc . "(clojure.repl/doc %s)")
(source . "(clojure.repl/source %s)")
(arglists .
"(try (-> '%s clojure.core/resolve clojure.core/meta :arglists)
(catch Throwable e nil))")
(apropos . "(doseq [var (sort (clojure.repl/apropos \"%s\"))] (println (str var)))")
(ns-vars . "(clojure.repl/dir %s)")
(set-ns . "(clojure.core/in-ns '%s)")
(macroexpand . "(clojure.core/macroexpand '%s)")
(macroexpand-1 . "(clojure.core/macroexpand-1 '%s)")))
(clojure . ((load . "(clojure.core/load-file \"%s\")")
(doc . "(clojure.repl/doc %s)")
(source . "(clojure.repl/source %s)")
(arglists .
"(try
(:arglists
(clojure.core/meta
(clojure.core/resolve
(clojure.core/read-string \"%s\"))))
(catch #?(:clj Throwable :cljr Exception) e nil))")
(apropos . "(doseq [var (sort (clojure.repl/apropos \"%s\"))] (println (str var)))")
(ns-vars . "(clojure.repl/dir %s)")
(set-ns . "(clojure.core/in-ns '%s)")
(macroexpand . "(clojure.core/macroexpand '%s)")
(macroexpand-1 . "(clojure.core/macroexpand-1 '%s)")))))
(defvar-local inf-clojure-repl-type nil
"Symbol to define your REPL type.
Its root binding is nil and it can be further customized using
either `setq-local` or an entry in `.dir-locals.el`." )
(defvar inf-clojure-buffer nil
"The current `inf-clojure' process buffer.
MULTIPLE PROCESS SUPPORT
===========================================================================
To run multiple Clojure processes, you start the first up
with \\[inf-clojure]. It will be in a buffer named `*inf-clojure*'.
Rename this buffer with \\[rename-buffer]. You may now start up a new
process with another \\[inf-clojure]. It will be in a new buffer,
named `*inf-clojure*'. You can switch between the different process
buffers with \\[switch-to-buffer].
Commands that send text from source buffers to Clojure processes --
like `inf-clojure-eval-defun' or `inf-clojure-show-arglists' -- have to choose a
process to send to, when you have more than one Clojure process around. This
is determined by the global variable `inf-clojure-buffer'. Suppose you
have three inferior Clojures running:
Buffer Process
foo inf-clojure
bar inf-clojure<2>
*inf-clojure* inf-clojure<3>
If you do a \\[inf-clojure-eval-defun] command on some Clojure source code,
what process do you send it to?
- If you're in a process buffer (foo, bar, or *inf-clojure*),
you send it to that process.
- If you're in some other buffer (e.g., a source file), you
send it to the process attached to buffer `inf-clojure-buffer'.
This process selection is performed by function `inf-clojure-proc'.
Whenever \\[inf-clojure] fires up a new process, it resets
`inf-clojure-buffer' to be the new process's buffer. If you only run
one process, this does the right thing. If you run multiple
processes, you might need to change `inf-clojure-buffer' to
whichever process buffer you want to use.")
(defun inf-clojure--get-feature (repl-type feature no-error)
"Get FEATURE for REPL-TYPE from repl-features.
If no-error is truthy don't error if feature is not present."
(let ((feature-form (alist-get feature (alist-get repl-type inf-clojure-repl-features))))
(cond (feature-form feature-form)
(no-error nil)
(t (error "%s not configured for %s" feature repl-type)))))
(defun inf-clojure-get-feature (proc feature &optional no-error)
"Get FEATURE based on repl type for PROC."
(let* ((repl-type (or (with-current-buffer (process-buffer proc)
inf-clojure-repl-type)
(error "REPL type is not known"))))
(inf-clojure--get-feature repl-type feature no-error)))
(defun inf-clojure--update-feature (repl-type feature form)
"Return a copy of the datastructure containing the repl features.
Given a REPL-TYPE (`clojure', `lumo', ...) and a FEATURE (`doc',
`apropos', ...) and a FORM this will return a new datastructure
that can be set as `inf-clojure-repl-features'."
(let ((original (alist-get repl-type inf-clojure-repl-features)))
(if original
(cons (cons repl-type (cons (cons feature form) (assoc-delete-all feature original)))
(assoc-delete-all repl-type inf-clojure-repl-features))
(error "Attempted to update %s form of unknown REPL type %s"
(symbol-name feature)
(symbol-name repl-type)))))
(defun inf-clojure-update-feature (repl-type feature form)
"Mutate the repl features to the new FORM.
Given a REPL-TYPE (`clojure', `lumo', ...) and a FEATURE (`doc',
`apropos', ...) and a FORM this will set
`inf-clojure-repl-features' with these new values."
(setq inf-clojure-repl-features (inf-clojure--update-feature repl-type feature form)))
(defun inf-clojure-proc (&optional no-error)
"Return the current inferior Clojure process.
When NO-ERROR is non-nil, don't throw an error when no process
has been found. See also variable `inf-clojure-buffer'."
(or (get-buffer-process (if (derived-mode-p 'inf-clojure-mode)
(current-buffer)
inf-clojure-buffer))
(unless no-error
(error "No Clojure subprocess; see variable `inf-clojure-buffer'"))))
(defun inf-clojure-repl-p (&optional buf)
"Indicates if BUF is an inf-clojure REPL.
If BUF is nil then defaults to the current buffer.
Checks the mode and that there is a live process."
(let ((buf (or buf (current-buffer))))
(and (with-current-buffer buf (derived-mode-p 'inf-clojure-mode))
(get-buffer-process buf)
(process-live-p (get-buffer-process buf)))))
(defun inf-clojure-repls ()
"Return a list of all inf-clojure REPL buffers."
(let (repl-buffers)
(dolist (b (buffer-list))
(when (inf-clojure-repl-p b)
(push (buffer-name b) repl-buffers)))
repl-buffers))
(defun inf-clojure-set-repl (always-ask)
"Set an inf-clojure buffer as the active (default) REPL.
If in a REPL buffer already, use that unless a prefix is used (or
ALWAYS-ASK). Otherwise get a list of all active inf-clojure
REPLS and offer a choice. It's recommended to rename REPL
buffers after they are created with `rename-buffer'."
(interactive "P")
(if (and (not always-ask)
(inf-clojure-repl-p))
(setq inf-clojure-buffer (current-buffer))
(let ((repl-buffers (inf-clojure-repls)))
(if (> (length repl-buffers) 0)
(when-let ((repl-buffer (completing-read "Select default REPL: " repl-buffers nil t)))
(setq inf-clojure-buffer (get-buffer repl-buffer)))
(user-error "No buffers have an inf-clojure process")))))
(defvar inf-clojure--repl-type-lock nil
"Global lock for protecting against proc filter race conditions.
See http://blog.jorgenschaefer.de/2014/05/race-conditions-in-emacs-process-filter.html")
(defun inf-clojure--prompt-repl-type ()
"Set the REPL type to one of the available implementations."
(interactive)
(let ((types (mapcar #'car inf-clojure-repl-features)))
(intern
(completing-read "Set REPL type: "
(sort (mapcar #'symbol-name types) #'string-lessp)))))
(defgroup inf-clojure nil
"Run an external Clojure process (REPL) in an Emacs buffer."
:prefix "inf-clojure-"
:group 'clojure
:link '(url-link :tag "GitHub" "https://github.com/clojure-emacs/inf-clojure")
:link '(emacs-commentary-link :tag "Commentary" "inf-clojure"))
(defconst inf-clojure-version
(or (if (fboundp 'package-get-version)
(package-get-version))
"3.2.1")
"The current version of `inf-clojure'.")
(defcustom inf-clojure-prompt-read-only t
"If non-nil, the prompt will be read-only.
Also see the description of `ielm-prompt-read-only'."
:type 'boolean)
(defcustom inf-clojure-filter-regexp
"\\`\\s *\\(:\\(\\w\\|\\s_\\)\\)?\\s *\\'"
"What not to save on inferior Clojure's input history.
Input matching this regexp is not saved on the input history in Inferior Clojure
mode. Default is whitespace followed by 0 or 1 single-letter colon-keyword
\(as in :a, :c, etc.)"
:type 'regexp)
(defun inf-clojure--modeline-info ()
"Return modeline info for `inf-clojure-minor-mode'.
Either \"no process\" or \"buffer-name(repl-type)\""
(if (and (bufferp inf-clojure-buffer)
(buffer-live-p inf-clojure-buffer))
(with-current-buffer inf-clojure-buffer
(format "%s(%s)" (buffer-name (current-buffer)) inf-clojure-repl-type))
"no process"))
(defvar inf-clojure-mode-map
(let ((map (copy-keymap comint-mode-map)))
(define-key map (kbd "C-x C-e") #'inf-clojure-eval-last-sexp)
(define-key map (kbd "C-c C-l") #'inf-clojure-load-file)
(define-key map (kbd "C-c C-a") #'inf-clojure-show-arglists)
(define-key map (kbd "C-c C-v") #'inf-clojure-show-var-documentation)
(define-key map (kbd "C-c C-s") #'inf-clojure-show-var-source)
(define-key map (kbd "C-c C-S-a") #'inf-clojure-apropos)
(define-key map (kbd "C-c M-o") #'inf-clojure-clear-repl-buffer)
(define-key map (kbd "C-c C-q") #'inf-clojure-quit)
(define-key map (kbd "C-c C-z") #'inf-clojure-switch-to-recent-buffer)
(easy-menu-define inf-clojure-mode-menu map
"Inferior Clojure REPL Menu"
'("Inf-Clojure REPL"
["Eval last sexp" inf-clojure-eval-last-sexp t]
"--"
["Load file" inf-clojure-load-file t]
"--"
["Show arglists" inf-clojure-show-arglists t]
["Show documentation for var" inf-clojure-show-var-documentation t]
["Show source for var" inf-clojure-show-var-source t]
["Apropos" inf-clojure-apropos t]
"--"
["Clear REPL" inf-clojure-clear-repl-buffer]
["Restart" inf-clojure-restart]
["Quit" inf-clojure-quit]
"--"
["Version" inf-clojure-display-version]))
map))
(defvar inf-clojure-insert-commands-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "d") #'inf-clojure-insert-defun)
(define-key map (kbd "C-d") #'inf-clojure-insert-defun)
(define-key map (kbd "e") #'inf-clojure-insert-last-sexp)
(define-key map (kbd "C-e") #'inf-clojure-insert-last-sexp)
map))
(defvar inf-clojure-minor-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-M-x") #'inf-clojure-eval-defun) ; Gnu convention
(define-key map (kbd "C-x C-e") #'inf-clojure-eval-last-sexp) ; Gnu convention
(define-key map (kbd "C-c C-e") #'inf-clojure-eval-last-sexp)
(define-key map (kbd "C-c C-c") #'inf-clojure-eval-defun) ; SLIME/CIDER style
(define-key map (kbd "C-c C-b") #'inf-clojure-eval-buffer)
(define-key map (kbd "C-c C-r") #'inf-clojure-eval-region)
(define-key map (kbd "C-c M-r") #'inf-clojure-reload)
(define-key map (kbd "C-c C-n") #'inf-clojure-eval-form-and-next)
(define-key map (kbd "C-c C-j") inf-clojure-insert-commands-map)
(define-key map (kbd "C-c C-z") #'inf-clojure-switch-to-repl)
(define-key map (kbd "C-c C-i") #'inf-clojure-show-ns-vars)
(define-key map (kbd "C-c C-S-a") #'inf-clojure-apropos)
(define-key map (kbd "C-c C-m") #'inf-clojure-macroexpand)
(define-key map (kbd "C-c C-l") #'inf-clojure-load-file)
(define-key map (kbd "C-c C-a") #'inf-clojure-show-arglists)
(define-key map (kbd "C-c C-v") #'inf-clojure-show-var-documentation)
(define-key map (kbd "C-c C-s") #'inf-clojure-show-var-source)
(define-key map (kbd "C-c M-n") #'inf-clojure-set-ns)
(define-key map (kbd "C-c C-q") #'inf-clojure-quit)
(define-key map (kbd "C-c M-c") #'inf-clojure-connect)
(easy-menu-define inf-clojure-minor-mode-menu map
"Inferior Clojure Minor Mode Menu"
'("Inf-Clojure"
["Eval top-level sexp at point" inf-clojure-eval-defun t]
["Eval last sexp" inf-clojure-eval-last-sexp t]
["Eval region" inf-clojure-eval-region t]
["Eval buffer" inf-clojure-eval-buffer t]
"--"
["Load file..." inf-clojure-load-file t]
["Reload file... " inf-clojure-reload t]
"--"
["Switch to REPL" inf-clojure-switch-to-repl t]
["Set REPL ns" inf-clojure-set-ns t]
"--"
["Show arglists" inf-clojure-show-arglists t]
["Show documentation for var" inf-clojure-show-var-documentation t]
["Show source for var" inf-clojure-show-var-source t]
["Show vars in ns" inf-clojure-show-ns-vars t]
["Apropos" inf-clojure-apropos t]
["Macroexpand" inf-clojure-macroexpand t]
"--"
["Restart REPL" inf-clojure-restart]
["Quit REPL" inf-clojure-quit]))
map))
;;;###autoload
(defcustom inf-clojure-mode-line
'(:eval (format " inf-clojure[%s]" (inf-clojure--modeline-info)))
"Mode line lighter for cider mode.
The value of this variable is a mode line template as in
`mode-line-format'. See Info Node `(elisp)Mode Line Format' for details
about mode line templates.
Customize this variable to change how inf-clojure-minor-mode
displays its status in the mode line. The default value displays
the current REPL. Set this variable to nil to disable the
mode line entirely."
:type 'sexp
:risky t)
(defcustom inf-clojure-enable-eldoc t
"Var that allows disabling `eldoc-mode' in `inf-clojure'.
Set to nil to disable eldoc. Eldoc can be quite useful by
displaying function signatures in the modeline, but can also
cause multiple prompts to appear in the REPL and mess with *1,
*2, etc."
:type 'boolean
:safe #'booleanp
:package-version '(inf-clojure . "3.2.0"))
;;;###autoload
(define-minor-mode inf-clojure-minor-mode
"Minor mode for interacting with the inferior Clojure process buffer.
The following commands are available:
\\{inf-clojure-minor-mode-map}"
:lighter inf-clojure-mode-line
:keymap inf-clojure-minor-mode-map
(setq-local comint-input-sender 'inf-clojure--send-string)
(when inf-clojure-enable-eldoc
(inf-clojure-eldoc-setup))
(make-local-variable 'completion-at-point-functions)
(add-to-list 'completion-at-point-functions
#'inf-clojure-completion-at-point))
(defun inf-clojure--endpoint-p (x)
"Return non-nil if and only if X is a valid endpoint.
A valid endpoint consists of a host and port
number (e.g. (\"localhost\" . 5555))."
(and
(listp x)
(stringp (car x))
(numberp (cdr x))))
(defcustom inf-clojure-custom-startup
nil
"Form to be used to start `inf-clojure'.
Can be a cons pair of (host . port) where host is a string and
port is an integer, or a string to startup an interpreter like
\"planck\"."
:type '(choice (cons string integer) (const nil)))
(defcustom inf-clojure-custom-repl-type
nil
"REPL type to use for `inf-clojure' process buffer.
Should be a symbol that is a key in `inf-clojure-repl-features'."
:package-version '(inf-clojure . "3.0.0")
:type '(choice (const :tag "clojure" clojure)
(const :tag "cljs" cljs)
(const :tag "lumo" lumo)
(const :tag "planck" planck)
(const :tag "joker" joker)
(const :tag "babashka" babashka)
(const :tag "determine at startup" nil)))
(defun inf-clojure--whole-comment-line-p (string)
"Return non-nil iff STRING is a whole line semicolon comment."
(string-match-p "^\s*;" string))
(defun inf-clojure--sanitize-command (command)
"Sanitize COMMAND for sending it to a process.
An example of things that this function does is to add a final
newline at the end of the form. Return an empty string if the
sanitized command is empty."
(let ((sanitized (string-trim-right command)))
(if (string-blank-p sanitized)
""
(concat sanitized "\n"))))
(defun inf-clojure--send-string (proc string)
"A custom `comint-input-sender` / `comint-send-string`.
It performs the required side effects on every send for PROC and
STRING (for example set the buffer local REPL type). It should
always be preferred over `comint-send-string`. It delegates to
`comint-simple-send` so it always appends a newline at the end of
the string for evaluation. Refer to `comint-simple-send` for
customizations."
(let ((sanitized (inf-clojure--sanitize-command string)))
(inf-clojure--log-string sanitized "----CMD->")
(comint-send-string proc sanitized)))
(defcustom inf-clojure-reload-form "(require '%s :reload)"
"Format-string for building a Clojure expression to reload a file.
Reload forces loading of all the identified libs even if they are
already loaded.
This format string should use `%s' to substitute a namespace and
should result in a Clojure form that will be sent to the inferior
Clojure to load that file."
:type 'string
:safe #'stringp
:package-version '(inf-clojure . "2.2.0"))
;; :reload forces loading of all the identified libs even if they are
;; already loaded
;; :reload-all implies :reload and also forces loading of all libs that the
;; identified libs directly or indirectly load via require or use
(defun inf-clojure-reload-form (_proc)
"Return the form to query the Inf-Clojure PROC for reloading a namespace.
If you are using REPL types, it will pickup the most appropriate
`inf-clojure-reload-form` variant."
inf-clojure-reload-form)
(defcustom inf-clojure-reload-all-form "(require '%s :reload-all)"
"Format-string for building a Clojure expression to :reload-all a file.
Reload-all implies :reload and also forces loading of all libs
that the identified libs directly or indirectly load via require
or use.
This format string should use `%s' to substitute a namespace and
should result in a Clojure form that will be sent to the inferior
Clojure to load that file."
:type 'string
:safe #'stringp
:package-version '(inf-clojure . "2.2.0"))
(defun inf-clojure-reload-all-form (_proc)
"Return the form to query the Inf-Clojure PROC for :reload-all of a namespace.
If you are using REPL types, it will pickup the most appropriate
`inf-clojure-reload-all-form` variant."
inf-clojure-reload-all-form)
(defcustom inf-clojure-prompt "^[^=> \n]+=> *"
"Regexp to recognize prompts in the Inferior Clojure mode."
:type 'regexp)
(defcustom inf-clojure-subprompt " *#_=> *"
"Regexp to recognize subprompts in the Inferior Clojure mode."
:type 'regexp)
(defcustom inf-clojure-comint-prompt-regexp "^\\( *#_\\|[^=> \n]+\\)=> *"
"Regexp to recognize both main prompt and subprompt for comint.
This should usually be a combination of `inf-clojure-prompt' and
`inf-clojure-subprompt'."
:type 'regexp)
(defcustom inf-clojure-repl-use-same-window nil
"Controls whether to display the REPL buffer in the current window or not."
:type '(choice (const :tag "same" t)
(const :tag "different" nil))
:safe #'booleanp
:package-version '(inf-clojure . "2.0.0"))
(defcustom inf-clojure-auto-mode t
"When non-nil, automatically enable inf-clojure-minor-mode for all Clojure buffers."
:type 'boolean
:safe #'booleanp
:package-version '(inf-clojure . "3.1.0"))
(defun inf-clojure--clojure-buffers ()
"Return a list of all existing `clojure-mode' buffers."
(cl-remove-if-not
(lambda (buffer) (with-current-buffer buffer (derived-mode-p 'clojure-mode)))
(buffer-list)))
(defun inf-clojure-enable-on-existing-clojure-buffers ()
"Enable inf-clojure's minor mode on existing Clojure buffers.
See command `inf-clojure-minor-mode'."
(interactive)
(add-hook 'clojure-mode-hook #'inf-clojure-minor-mode)
(dolist (buffer (inf-clojure--clojure-buffers))
(with-current-buffer buffer
(inf-clojure-minor-mode +1))))
(defun inf-clojure-disable-on-existing-clojure-buffers ()
"Disable command `inf-clojure-minor-mode' on existing Clojure buffers."
(interactive)
(dolist (buffer (inf-clojure--clojure-buffers))
(with-current-buffer buffer
(inf-clojure-minor-mode -1))))
(define-derived-mode inf-clojure-mode comint-mode "Inferior Clojure"
"Major mode for interacting with an inferior Clojure process.
Runs a Clojure interpreter as a subprocess of Emacs, with Clojure
I/O through an Emacs buffer. Variables of the type
`inf-clojure-*-cmd' combined with the project type controls how
a Clojure REPL is started. Variables `inf-clojure-prompt',
`inf-clojure-filter-regexp' and `inf-clojure-load-form' can
customize this mode for different Clojure REPLs.
For information on running multiple processes in multiple buffers, see
documentation for variable `inf-clojure-buffer'.
\\{inf-clojure-mode-map}
Customization: Entry to this mode runs the hooks on `comint-mode-hook' and
`inf-clojure-mode-hook' (in that order).
You can send text to the inferior Clojure process from other buffers containing
Clojure source.
`inf-clojure-switch-to-repl' switches the current buffer to the Clojure process buffer.
`inf-clojure-eval-defun' sends the current defun to the Clojure process.
`inf-clojure-eval-region' sends the current region to the Clojure process.
Prefixing the inf-clojure-eval/defun/region commands with
a \\[universal-argument] causes a switch to the Clojure process buffer after sending
the text.
Commands:\\
\\[comint-send-input] after the end of the process' output sends the text from the
end of process to point.
\\[comint-send-input] before the end of the process' output copies the sexp ending at point
to the end of the process' output, and sends it.
\\[comint-copy-old-input] copies the sexp ending at point to the end of the process' output,
allowing you to edit it before sending it.
If `comint-use-prompt-regexp' is nil (the default), \\[comint-insert-input] on old input
copies the entire old input to the end of the process' output, allowing
you to edit it before sending it. When not used on old input, or if
`comint-use-prompt-regexp' is non-nil, \\[comint-insert-input] behaves according to
its global binding.
\\[backward-delete-char-untabify] converts tabs to spaces as it moves back.
\\[clojure-indent-line] indents for Clojure; with argument, shifts rest
of expression rigidly with the current line.
\\[indent-sexp] does \\[clojure-indent-line] on each line starting within following expression.
Paragraphs are separated only by blank lines. Semicolons start comments.
If you accidentally suspend your process, use \\[comint-continue-subjob]
to continue it."
(setq comint-input-sender 'inf-clojure--send-string)
(setq comint-prompt-regexp inf-clojure-comint-prompt-regexp)
(setq mode-line-process '(":%s"))
(clojure-mode-variables)
(clojure-font-lock-setup)
(when inf-clojure-enable-eldoc
(inf-clojure-eldoc-setup))
(setq comint-get-old-input #'inf-clojure-get-old-input)
(setq comint-input-filter #'inf-clojure-input-filter)
(setq-local comint-prompt-read-only inf-clojure-prompt-read-only)
(add-hook 'comint-preoutput-filter-functions #'inf-clojure-preoutput-filter nil t)
(add-hook 'completion-at-point-functions #'inf-clojure-completion-at-point nil t)
(ansi-color-for-comint-mode-on)
(when inf-clojure-auto-mode
(inf-clojure-enable-on-existing-clojure-buffers)))
(defun inf-clojure-get-old-input ()
"Return a string containing the sexp ending at point."
(save-excursion
(let ((end (point)))
(backward-sexp)
(buffer-substring (max (point) (comint-line-beginning-position)) end))))
(defun inf-clojure-input-filter (str)
"Return t if STR does not match `inf-clojure-filter-regexp'."
(not (string-match inf-clojure-filter-regexp str)))
(defun inf-clojure-chomp (string)
"Remove final newline from STRING."
(if (string-match "[\n]\\'" string)
(replace-match "" t t string)
string))
(defun inf-clojure-remove-subprompts (string)
"Remove subprompts from STRING."
(replace-regexp-in-string inf-clojure-subprompt "" string))
(defun inf-clojure-preoutput-filter (str)
"Preprocess the output STR from interactive commands."
(inf-clojure--log-string str "<-RES----")
(cond
((string-prefix-p "inf-clojure-" (symbol-name (or this-command last-command)))
;; Remove subprompts and prepend a newline to the output string
(inf-clojure-chomp (concat "\n" (inf-clojure-remove-subprompts str))))
(t str)))
(defun inf-clojure-clear-repl-buffer ()
"Clear the REPL buffer."
(interactive)
(with-current-buffer (if (derived-mode-p 'inf-clojure-mode)
(current-buffer)
inf-clojure-buffer)
(let ((comint-buffer-maximum-size 0))
(comint-truncate-buffer))))
(defun inf-clojure--swap-to-buffer-window (to-buffer)
"Switch to `TO-BUFFER''s window."
(let ((pop-up-frames
;; Be willing to use another frame
;; that already has the window in it.
(or pop-up-frames
(get-buffer-window to-buffer t))))
(pop-to-buffer to-buffer '(display-buffer-reuse-window . ()))))
(defun inf-clojure-switch-to-repl (eob-p)
"Switch to the inferior Clojure process buffer.
With prefix argument EOB-P, positions cursor at end of buffer."
(interactive "P")
(if (get-buffer-process inf-clojure-buffer)
(inf-clojure--swap-to-buffer-window inf-clojure-buffer)
(call-interactively #'inf-clojure))
(when eob-p
(push-mark)
(goto-char (point-max))))
(defun inf-clojure-switch-to-recent-buffer ()
"Switch to the most recently used `inf-clojure-minor-mode' buffer."
(interactive)
(let ((recent-inf-clojure-minor-mode-buffer (seq-find (lambda (buf)
(with-current-buffer buf (bound-and-true-p inf-clojure-minor-mode)))
(buffer-list))))
(if recent-inf-clojure-minor-mode-buffer
(inf-clojure--swap-to-buffer-window recent-inf-clojure-minor-mode-buffer)
(message "inf-clojure: No recent buffer known."))))
(defun inf-clojure-quit (&optional buffer)
"Kill the REPL buffer and its underlying process.
You can pass the target BUFFER as an optional parameter
to suppress the usage of the target buffer discovery logic."
(interactive)
(let ((target-buffer (or buffer (inf-clojure-select-target-repl))))
(when (get-buffer-process target-buffer)
(delete-process target-buffer))
(kill-buffer target-buffer)))
(defun inf-clojure-restart (&optional buffer)
"Restart the REPL buffer and its underlying process.
You can pass the target BUFFER as an optional parameter
to suppress the usage of the target buffer discovery logic."
(interactive)
(let* ((target-buffer (or buffer (inf-clojure-select-target-repl)))
(target-buffer-name (buffer-name target-buffer)))
;; TODO: Try to recycle the old buffer instead of killing and recreating it
(inf-clojure-quit target-buffer)
(call-interactively #'inf-clojure)
(rename-buffer target-buffer-name)))
(defun inf-clojure--project-name (dir)
"Extract a project name from a project DIR.
The name is simply the final segment of the path."
(file-name-nondirectory (directory-file-name dir)))
;;;###autoload
(defun inf-clojure (cmd)
"Run an inferior Clojure process, input and output via buffer `*inf-clojure*'.
If there is a process already running in `*inf-clojure*', just
switch to that buffer.
CMD is a string which serves as the startup command or a cons of
host and port.
Prompts user for repl startup command and repl type if not
inferrable from startup command. Uses `inf-clojure-custom-repl-type'
and `inf-clojure-custom-startup' if those are set.
Use a prefix to prevent using these when they
are set.
Runs the hooks from `inf-clojure-mode-hook' (after the
`comint-mode-hook' is run). \(Type \\[describe-mode] in the
process buffer for a list of commands.)"
(interactive (list (or (unless current-prefix-arg
inf-clojure-custom-startup)
(completing-read "Select Clojure REPL startup command: "
(mapcar #'cdr inf-clojure-startup-forms)
nil
'confirm-after-completion))))
(let* ((project-dir (clojure-project-dir))
(process-buffer-name (if project-dir
(format "inf-clojure %s" (inf-clojure--project-name project-dir))
"inf-clojure"))
;; comint adds the asterisks to both sides
(repl-buffer-name (format "*%s*" process-buffer-name)))
;; Create a new comint buffer if needed
(unless (comint-check-proc repl-buffer-name)
;; run the new process in the project's root when in a project folder
(let ((default-directory (or project-dir default-directory))
(cmdlist (if (consp cmd)
(list cmd)
(split-string-and-unquote cmd)))
(repl-type (or (unless prefix-arg
inf-clojure-custom-repl-type)
(car (rassoc cmd inf-clojure-startup-forms))
(inf-clojure--prompt-repl-type))))
(message "Starting Clojure REPL via `%s'..." cmd)
(with-current-buffer (apply #'make-comint
process-buffer-name (car cmdlist) nil (cdr cmdlist))
(inf-clojure-mode)
(set-syntax-table clojure-mode-syntax-table)
(setq-local inf-clojure-repl-type repl-type)
(hack-dir-local-variables-non-file-buffer))))
;; update the default comint buffer and switch to it
(setq inf-clojure-buffer (get-buffer repl-buffer-name))
(if inf-clojure-repl-use-same-window
(pop-to-buffer-same-window repl-buffer-name)
(pop-to-buffer repl-buffer-name))))
;;;###autoload
(defun inf-clojure-connect (host port)
"Connect to a running socket REPL server via `inf-clojure'.
HOST is the host the process is running on, PORT is where it's listening."
(interactive "shost: \nnport: ")
(inf-clojure (cons host port)))
(defun inf-clojure--forms-without-newlines (str)
"Remove newlines between toplevel forms.
STR is a string of contents to be evaluated. When sending
multiple forms to a REPL, each newline triggers a prompt.
So we replace all newlines between top level forms but not inside
of forms."
(condition-case nil
(with-temp-buffer
(progn
(clojurec-mode)
(insert str)
(whitespace-cleanup)
(goto-char (point-min))
(while (not (eobp))
(while (looking-at "\n")
(delete-char 1))
(unless (eobp)
(clojure-forward-logical-sexp))
(unless (eobp)
(forward-char)))
(buffer-substring-no-properties (point-min) (point-max))))
(scan-error str)))
(defun inf-clojure-eval-region (start end &optional and-go)
"Send the current region to the inferior Clojure process.
Sends substring between START and END. Prefix argument AND-GO
means switch to the Clojure buffer afterwards."
(interactive "r\nP")
(let* ((str (buffer-substring-no-properties start end))
;; newlines over a socket repl between top level forms cause
;; a prompt to be returned. so here we dump the region into a
;; temp buffer, and delete all newlines between the forms
(formatted (inf-clojure--forms-without-newlines str)))
(inf-clojure--send-string (inf-clojure-proc) formatted))
(when and-go (inf-clojure-switch-to-repl t)))
(defun inf-clojure-eval-string (code)
"Send the string CODE to the inferior Clojure process to be executed."
(inf-clojure--send-string (inf-clojure-proc) code))
(defun inf-clojure--defun-at-point (&optional bounds)
"Return text or range of defun at point.
If BOUNDS is truthy return a dotted pair of beginning and end of
current defun else return the string.."
(save-excursion
(end-of-defun)
(let ((end (point))
(case-fold-search t)
(func (if bounds #'cons #'buffer-substring-no-properties)))
(beginning-of-defun)
(funcall func (point) end))))
(defun inf-clojure-eval-defun (&optional and-go)
"Send the current defun to the inferior Clojure process.
Prefix argument AND-GO means switch to the Clojure buffer afterwards."
(interactive "P")
(save-excursion
(let ((bounds (inf-clojure--defun-at-point t)))
(inf-clojure-eval-region (car bounds) (cdr bounds) and-go))))
(defun inf-clojure-eval-buffer (&optional and-go)
"Send the current buffer to the inferior Clojure process.
Prefix argument AND-GO means switch to the Clojure buffer afterwards."
(interactive "P")
(save-excursion
(widen)
(let ((case-fold-search t))
(inf-clojure-eval-region (point-min) (point-max) and-go))))
(defun inf-clojure-eval-last-sexp (&optional and-go)
"Send the previous sexp to the inferior Clojure process.
Prefix argument AND-GO means switch to the Clojure buffer afterwards."
(interactive "P")
(inf-clojure-eval-region (save-excursion (backward-sexp) (point)) (point) and-go))
(defun inf-clojure-eval-form-and-next ()
"Send the previous sexp to the inferior Clojure process and move to the next one."
(interactive "")
(while (not (zerop (car (syntax-ppss))))
(up-list))
(inf-clojure-eval-last-sexp)
(forward-sexp))
(defun inf-clojure-insert-and-eval (form)
"Insert FORM into process and evaluate.
Indent FORM. FORM is expected to have been trimmed."
(let ((clojure-process (inf-clojure-proc)))
(with-current-buffer (process-buffer clojure-process)
(comint-goto-process-mark)
(let ((beginning (point)))
(insert (format "%s" form))
(let ((end (point)))
(goto-char beginning)
(indent-sexp end)
;; font-lock the inserted code
(font-lock-ensure beginning end)))
(comint-send-input t t))))
(defun inf-clojure-insert-defun ()
"Send current defun to process."
(interactive)
(inf-clojure-insert-and-eval (string-trim (inf-clojure--defun-at-point))))
(defun inf-clojure-insert-last-sexp ()
"Send last sexp to process."
(interactive)
(inf-clojure-insert-and-eval
(buffer-substring-no-properties (save-excursion (backward-sexp) (point))
(point))))
;; Now that inf-clojure-eval-/defun/region takes an optional prefix arg,
;; these commands are redundant. But they are kept around for the user
;; to bind if he wishes, for backwards functionality, and because it's
;; easier to type C-c e than C-u C-c C-e.
(defun inf-clojure-eval-region-and-go (start end)
"Send the current region to the inferior Clojure, and switch to its buffer.
START and END are the beginning and end positions in the buffer to send."
(interactive "r")
(inf-clojure-eval-region start end t))
(defun inf-clojure-eval-defun-and-go ()
"Send the current defun to the inferior Clojure, and switch to its buffer."
(interactive)
(inf-clojure-eval-defun t))
(defvar inf-clojure-prev-l/c-dir/file nil
"Record last directory and file used in loading or compiling.
This holds a cons cell of the form `(DIRECTORY . FILE)'
describing the last `inf-clojure-load-file' command.")
(defcustom inf-clojure-source-modes '(clojure-mode)
"Used to determine if a buffer contains Clojure source code.
If it's loaded into a buffer that is in one of these major modes, it's
considered a Clojure source file by `inf-clojure-load-file'.
Used by this command to determine defaults."
:type '(repeat symbol))
(defun inf-clojure-load-file (&optional switch-to-repl file-name)
"Load a Clojure file into the inferior Clojure process.
The prefix argument SWITCH-TO-REPL controls whether to switch to
REPL after the file is loaded or not. If the argument FILE-NAME
is present it will be used instead of the current file."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(file-name (or file-name
(car (comint-get-source "Load Clojure file: " inf-clojure-prev-l/c-dir/file
;; nil because doesn't need an exact name
inf-clojure-source-modes nil))))
(load-form (inf-clojure-get-feature proc 'load)))
(comint-check-source file-name) ; Check to see if buffer needs saved.
(setq inf-clojure-prev-l/c-dir/file (cons (file-name-directory file-name)
(file-name-nondirectory file-name)))
(inf-clojure--send-string proc (format load-form file-name))
(when switch-to-repl
(inf-clojure-switch-to-repl t))))
(defun inf-clojure-reload (arg)
"Send a query to the inferior Clojure for reloading the namespace.
See variable `inf-clojure-reload-form' and
`inf-clojure-reload-all-form'.
The prefix argument ARG can change the behavior of the command:
- C-u M-x `inf-clojure-reload': prompts for a namespace name.
- M-- M-x `inf-clojure-reload': executes (require ... :reload-all).
- M-- C-u M-x `inf-clojure-reload': reloads all AND prompts."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(reload-all-p (or (equal arg '-) (equal arg '(-4))))
(prompt-p (or (equal arg '(4)) (equal arg '(-4))))
(ns (if prompt-p
(car (inf-clojure-symprompt "Namespace" (clojure-find-ns)))
(clojure-find-ns)))
(form (if (not reload-all-p)
(inf-clojure-reload-form proc)
(inf-clojure-reload-all-form proc))))
(inf-clojure--send-string proc (format form ns))))
(defun inf-clojure-connected-p ()
"Return t if inferior Clojure is currently connected, nil otherwise."
(not (null inf-clojure-buffer)))
;;; Ancillary functions
;;; ===================
(defun inf-clojure-symprompt (prompt default)
"Read a string from the user.
It allows to specify a PROMPT string and a DEFAULT string to
display."
(list (let* ((prompt (if default
(format "%s (default %s): " prompt default)
(concat prompt ": ")))
(ans (read-string prompt)))
(if (zerop (length ans)) default ans))))
;; Adapted from function-called-at-point in help.el.
(defun inf-clojure-fn-called-at-pt ()
"Return the name of the function called in the current call.
The value is nil if it can't find one."
(condition-case nil
(save-excursion
(save-restriction
(narrow-to-region (max (point-min) (- (point) 1000)) (point-max))
(backward-up-list 1)
(forward-char 1)
(let ((obj (read (current-buffer))))
(and (symbolp obj) obj))))
(error nil)))
(defun inf-clojure-symbol-at-point ()
"Return the name of the symbol at point, otherwise nil."
(or (thing-at-point 'symbol) ""))
;;; Documentation functions: var doc and arglists.
;;; ======================================================================
(defun inf-clojure-show-var-documentation (prompt-for-symbol)
"Send a form to the inferior Clojure to give documentation for VAR.
See function `inf-clojure-var-doc-form'. When invoked with a
prefix argument PROMPT-FOR-SYMBOL, it prompts for a symbol name."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(var (if prompt-for-symbol
(car (inf-clojure-symprompt "Var doc" (inf-clojure-symbol-at-point)))
(inf-clojure-symbol-at-point)))
(doc-form (inf-clojure-get-feature proc 'doc)))
(inf-clojure--send-string proc (format doc-form var))))
(defun inf-clojure-show-var-source (prompt-for-symbol)
"Send a command to the inferior Clojure to give source for VAR.
See variable `inf-clojure-var-source-form'. When invoked with a
prefix argument PROMPT-FOR-SYMBOL, it prompts for a symbol name."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(var (if prompt-for-symbol
(car (inf-clojure-symprompt "Var source" (inf-clojure-symbol-at-point)))
(inf-clojure-symbol-at-point)))
(source-form (inf-clojure-get-feature proc 'source)))
(inf-clojure--send-string proc (format source-form var))))
;;;; Response parsing
;;;; ================
(defvar inf-clojure--redirect-buffer-name " *Inf-Clojure Redirect Buffer*"
"The name of the buffer used for process output redirection.")
(defvar inf-clojure--log-file-name ".inf-clojure.log"
"The name of the file used to log process activity.")
(defvar inf-clojure-log-activity nil
"Log process activity?.
Inf-Clojure will create a log file in the project folder named
`inf-clojure--log-file-name' and dump the process activity in it
in case this is not nil." )
(defun inf-clojure--log-string (string &optional tag)
"Log STRING to file, according to `inf-clojure-log-response'.
The optional TAG will be converted to string and printed before
STRING if present."
(when inf-clojure-log-activity
(write-region (concat "\n"
(when tag
(if (stringp tag)
(concat tag "\n")
(concat (prin1-to-string tag) "\n")))
(let ((print-escape-newlines t))
(prin1-to-string (substring-no-properties string))))
nil
(expand-file-name inf-clojure--log-file-name
(clojure-project-dir))
'append
'no-annoying-write-file-in-minibuffer)))
(defun inf-clojure--string-boundaries (string prompt &optional beg-regexp end-regexp)
"Calculate the STRING boundaries, including PROMPT.
Return a list of positions (beginning end prompt). If the
optional BEG-REGEXP and END-REGEXP are present, the boundaries
are going to match those."
(list (or (and beg-regexp (string-match beg-regexp string)) 0)
(or (and end-regexp (when (string-match end-regexp string)
(match-end 0)))
(length string))
(or (string-match prompt string) (length string))))
(defun inf-clojure--get-redirect-buffer ()
"Get the redirection buffer, creating it if necessary.
It is the buffer used for processing REPL responses, see variable
\\[inf-clojure--redirect-buffer-name]."
(or (get-buffer inf-clojure--redirect-buffer-name)
(let ((buffer (generate-new-buffer inf-clojure--redirect-buffer-name)))
(with-current-buffer buffer
(hack-dir-local-variables-non-file-buffer)
buffer))))
;; Originally from:
;; https://github.com/glycerine/lush2/blob/master/lush2/etc/lush.el#L287
(defun inf-clojure--process-response (command process &optional beg-regexp end-regexp)
"Send COMMAND to PROCESS and return the response.
Return the result of COMMAND, filtering it from BEG-REGEXP to the
end of the matching END-REGEXP if non-nil.
If BEG-REGEXP is nil, the result string will start from (point)
in the results buffer. If END-REGEXP is nil, the result string
will end at (point-max) in the results buffer. It cuts out the
output from and including the `inf-clojure-prompt`."
(let ((redirect-buffer-name inf-clojure--redirect-buffer-name)
(sanitized-command (inf-clojure--sanitize-command command)))
(when (not (string-empty-p sanitized-command))
(inf-clojure--log-string command "----CMD->")
(with-current-buffer (inf-clojure--get-redirect-buffer)
(erase-buffer)
(comint-redirect-send-command-to-process sanitized-command redirect-buffer-name process nil t))
;; Wait for the process to complete
(with-current-buffer (process-buffer process)
(while (and (null comint-redirect-completed)
(accept-process-output process 1 0 t))
(sleep-for 0.01)))
;; Collect the output
(with-current-buffer redirect-buffer-name
(goto-char (point-min))
(let* ((buffer-string (buffer-substring-no-properties (point-min) (point-max)))
(boundaries (inf-clojure--string-boundaries buffer-string inf-clojure-prompt beg-regexp end-regexp))
(beg-pos (car boundaries))
(end-pos (car (cdr boundaries)))
(prompt-pos (car (cdr (cdr boundaries))))
(response-string (substring buffer-string beg-pos (min end-pos prompt-pos))))
(inf-clojure--log-string buffer-string "<-RES----")
response-string)))))
(defun inf-clojure--nil-string-match-p (string)
"Return non-nil iff STRING is not nil.
This function also takes into consideration weird escape
character and matches if nil is anywhere within the input
string."
(string-match-p "\\Ca*nil\\Ca*" string))
(defun inf-clojure--some (data)
"Return DATA unless nil or includes \"nil\" as string."
(cond
((null data) nil)
((and (stringp data)
(inf-clojure--nil-string-match-p data)) nil)
(t data)))
(defun inf-clojure--read-or-nil (response)
"Read RESPONSE and return it as data.
If response is nil or includes the \"nil\" string return nil
instead.
Note that the read operation will always return the first
readable sexp only."
;; The following reads the first LISP expression
(inf-clojure--some
(when response
(ignore-errors (read response)))))
(defun inf-clojure--process-response-match-p (match-p proc form)
"Eval MATCH-P on the response of sending to PROC the input FORM.
Note that this function will add a \n to the end of the string
for evaluation, therefore FORM should not include it."
(let ((response (inf-clojure--process-response form proc)))
(when response (funcall match-p response))))
(defun inf-clojure--some-response-p (proc form)
"Return non-nil iff PROC's response after evaluating FORM is not nil."
(inf-clojure--process-response-match-p
(lambda (string)
(not (inf-clojure--nil-string-match-p (string-trim string))))
proc form))
;;;; Commands
;;;; ========
(defun inf-clojure-arglists (fn)
"Send a query to the inferior Clojure for the arglists for function FN.
See variable `inf-clojure-arglists-form'."
(when-let ((proc (inf-clojure-proc 'no-error)))
(when-let ((arglists-form (inf-clojure-get-feature proc 'arglists)))
(thread-first (format arglists-form fn)
(inf-clojure--process-response proc "(" ")")
(inf-clojure--some)))))
(defun inf-clojure-show-arglists (prompt-for-symbol)
"Show the arglists for function FN in the mini-buffer.
See variable `inf-clojure-arglists-form'. When invoked with a
prefix argument PROMPT-FOR-SYMBOL, it prompts for a symbol name."
(interactive "P")
(let* ((fn (if prompt-for-symbol
(car (inf-clojure-symprompt "Arglists" (inf-clojure-fn-called-at-pt)))
(inf-clojure-fn-called-at-pt)))
(eldoc (inf-clojure-arglists fn)))
(if eldoc
(message "%s: %s" fn eldoc)
(message "Arglists not supported for this repl"))))
(defun inf-clojure-show-ns-vars (prompt-for-ns)
"Send a query to the inferior Clojure for the public vars in NS.
See variable `inf-clojure-ns-vars-form'. When invoked with a
prefix argument PROMPT-FOR-NS, it prompts for a namespace name."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(ns (if prompt-for-ns
(car (inf-clojure-symprompt "Ns vars" (clojure-find-ns)))
(clojure-find-ns)))
(ns-vars-form (inf-clojure-get-feature proc 'ns-vars)))
(inf-clojure--send-string proc (format ns-vars-form ns))))
(defun inf-clojure-set-ns (prompt-for-ns)
"Set the ns of the inferior Clojure process to NS.
See variable `inf-clojure-set-ns-form'. It defaults to the ns of
the current buffer. When invoked with a prefix argument
PROMPT-FOR-NS, it prompts for a namespace name."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(ns (if prompt-for-ns
(car (inf-clojure-symprompt "Set ns to" (clojure-find-ns)))
(clojure-find-ns)))
(set-ns-form (inf-clojure-get-feature proc 'set-ns)))
(when (or (not ns) (equal ns ""))
(user-error "No namespace selected"))
(inf-clojure--send-string proc (format set-ns-form ns))))
(defun inf-clojure-apropos (expr)
"Send an expression to the inferior Clojure for apropos.
EXPR can be either a regular expression or a stringable
thing. See variable `inf-clojure-apropos-form'."
(interactive (inf-clojure-symprompt "Var apropos" (inf-clojure-symbol-at-point)))
(let* ((proc (inf-clojure-proc))
(apropos-form (inf-clojure-get-feature proc 'apropos)))
(inf-clojure--send-string proc (format apropos-form expr))))
(defun inf-clojure-macroexpand (&optional macro-1)
"Send a form to the inferior Clojure for macro expansion.
See variable `inf-clojure-macroexpand-form'.
With a prefix arg MACRO-1 uses function `inf-clojure-macroexpand-1-form'."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(last-sexp (buffer-substring-no-properties (save-excursion (backward-sexp) (point)) (point)))
(macroexpand-form (inf-clojure-get-feature proc
(if macro-1
'macroexpand-1
'macroexpand))))
(inf-clojure--send-string
proc
(format macroexpand-form last-sexp))))
(defun inf-clojure--list-or-nil (data)
"Return DATA if and only if it is a list."
(when (listp data) data))
(defun inf-clojure-list-completions (response-str)
"Parse completions from RESPONSE-STR.
Its only ability is to parse a Lisp list of candidate strings,
every other EXPR will be discarded and nil will be returned."
(thread-first
response-str
(inf-clojure--read-or-nil)
(inf-clojure--list-or-nil)))
(defcustom inf-clojure-completions-fn 'inf-clojure-list-completions
"The function that parses completion results.
It is a single-arity function that will receive the REPL
evaluation result of \\[inf-clojure-completion-form] as string and
should return elisp data compatible with your completion mode.
The easiest possible data passed in input is a list of
candidates (e.g.: (\"def\" \"defn\")) but more complex libraries
like `alexander-yakushev/compliment' can return other things like
edn.
The expected return depends on the mode that you use for
completion: usually it is something compatible with
\\[completion-at-point-functions] but other modes like
`company-mode' allow an even higher level of sophistication.
The default value is the `inf-clojure-list-completions' function,
which is able to parse results in list form only. You can peek
at its implementation for getting to know some utility functions
you might want to use in your customization."
:type 'function
:package-version '(inf-clojure . "2.1.0"))
(defun inf-clojure-completions (expr)
"Return completions for the Clojure expression starting with EXPR.
Under the hood it calls the function
\\[inf-clojure-completions-fn] passing in the result of
evaluating \\[inf-clojure-completion-form] at the REPL."
(let* ((proc (inf-clojure-proc 'no-error))
(completion-form (inf-clojure-get-feature proc 'completion t)))
(when (and proc completion-form (not (string-blank-p expr)))
(let ((completion-expr (format completion-form (substring-no-properties expr))))
(funcall inf-clojure-completions-fn
(inf-clojure--process-response completion-expr proc "(" ")"))))))
(defconst inf-clojure-clojure-expr-break-chars "^[] \"'`><,;|&{()[@\\^]"
"Regexp are hard.
This regex has been built in order to match the first of the
listed chars. There are a couple of quirks to consider:
- the ] is always a special in elisp regex so you have to put it
directly AFTER [ if you want to match it as literal.
- The ^ needs to be escaped with \\^.
Tests and `re-builder' are your friends.")
(defun inf-clojure--kw-to-symbol (kw)
"Convert the keyword KW to a symbol.
This guy was taken from CIDER, thanks folks."
(when kw
(replace-regexp-in-string "\\`:+" "" kw)))
(defun inf-clojure-completion-bounds-of-expr-at-point ()
"Return bounds of expression at point to complete."
(when (not (memq (char-syntax (following-char)) '(?w ?_)))
(save-excursion
(let* ((end (point))
(skipped-back (skip-chars-backward inf-clojure-clojure-expr-break-chars))
(start (+ end skipped-back))
(chars (or (thing-at-point 'symbol)
(inf-clojure--kw-to-symbol (buffer-substring start end)))))
(when (> (length chars) 0)
(let ((first-char (substring-no-properties chars 0 1)))
(when (string-match-p "[^0-9]" first-char)
(cons (point) end))))))))
(defun inf-clojure-completion-expr-at-point ()
"Return expression at point to complete."
(let ((bounds (inf-clojure-completion-bounds-of-expr-at-point)))
(and bounds
(buffer-substring (car bounds) (cdr bounds)))))
(defun inf-clojure-completion-at-point ()
"Retrieve the list of completions and prompt the user.
Returns the selected completion or nil."
(let ((bounds (inf-clojure-completion-bounds-of-expr-at-point)))
(when (and bounds (inf-clojure-get-feature (inf-clojure-proc) 'completion 'no-error))
(list (car bounds) (cdr bounds)
(if (fboundp 'completion-table-with-cache)
(completion-table-with-cache #'inf-clojure-completions)
(completion-table-dynamic #'inf-clojure-completions))))))
;;;; ElDoc
;;;; =====
(defvar inf-clojure-extra-eldoc-commands '("yas-expand")
"Extra commands to be added to eldoc's safe commands list.")
(defvar-local inf-clojure-eldoc-last-symbol nil
"The eldoc information for the last symbol we checked.")
(defun inf-clojure-eldoc-format-thing (thing)
"Format the eldoc THING."
(propertize thing 'face 'font-lock-function-name-face))
(defun inf-clojure-eldoc-beginning-of-sexp ()
"Move to the beginning of current sexp.
Return the number of nested sexp the point was over or after."
(let ((parse-sexp-ignore-comments t)
(num-skipped-sexps 0))
(condition-case _
(progn
;; First account for the case the point is directly over a
;; beginning of a nested sexp.
(condition-case _
(let ((p (point)))
(forward-sexp -1)
(forward-sexp 1)
(when (< (point) p)
(setq num-skipped-sexps 1)))
(error))
(while
(let ((p (point)))
(forward-sexp -1)
(when (< (point) p)
(setq num-skipped-sexps (1+ num-skipped-sexps))))))
(error))
num-skipped-sexps))
(defun inf-clojure-eldoc-info-in-current-sexp ()
"Return a list of the current sexp and the current argument index."
(save-excursion
(let ((argument-index (1- (inf-clojure-eldoc-beginning-of-sexp))))
;; If we are at the beginning of function name, this will be -1.
(when (< argument-index 0)
(setq argument-index 0))
;; Don't do anything if current word is inside a string, vector,
;; hash or set literal.
(if (member (or (char-after (1- (point))) 0) '(?\" ?\{ ?\[))
nil
(list (inf-clojure-symbol-at-point) argument-index)))))
(defun inf-clojure-eldoc-arglists (thing)
"Return the arglists for THING."
(when (and thing
(not (string= thing ""))
(not (string-prefix-p ":" thing)))
;; check if we can used the cached eldoc info
(if (string= thing (car inf-clojure-eldoc-last-symbol))
(cdr inf-clojure-eldoc-last-symbol)
(let ((arglists (inf-clojure-arglists (substring-no-properties thing))))
(when arglists
(setq inf-clojure-eldoc-last-symbol (cons thing arglists))
arglists)))))
(defun inf-clojure-eldoc ()
"Backend function for eldoc to show argument list in the echo area."
;; todo: this never gets unset once connected and is a lie
(when (and (inf-clojure-connected-p)
inf-clojure-enable-eldoc
;; don't clobber an error message in the minibuffer
(not (member last-command '(next-error previous-error))))
(let* ((info (inf-clojure-eldoc-info-in-current-sexp))
(thing (car info))
(value (inf-clojure-eldoc-arglists thing)))
(when value
(format "%s: %s"
(inf-clojure-eldoc-format-thing thing)
value)))))
(defun inf-clojure-eldoc-setup ()
"Turn on eldoc mode in the current buffer."
(setq-local eldoc-documentation-function #'inf-clojure-eldoc)
(apply #'eldoc-add-command inf-clojure-extra-eldoc-commands))
(defun inf-clojure-display-version ()
"Display the current `inf-clojure' in the minibuffer."
(interactive)
(message "inf-clojure (version %s)" inf-clojure-version))
(defun inf-clojure-select-target-repl ()
"Find or select an ‘inf-clojure’ buffer to operate on.
Useful for commands that can invoked outside of an ‘inf-clojure’ buffer
\\(e.g. from a Clojure buffer\\)."
;; if we're in a inf-clojure buffer we simply return in
(if (eq major-mode 'inf-clojure-mode)
(current-buffer)
;; otherwise we sift through all the inf-clojure buffers that are available
(let ((repl-buffers (cl-remove-if-not (lambda (buf)
(with-current-buffer buf
(eq major-mode 'inf-clojure-mode)))
(buffer-list))))
(cond
((null repl-buffers) (user-error "No inf-clojure buffers found"))
((= (length repl-buffers) 1) (car repl-buffers))
(t (get-buffer (completing-read "Select target inf-clojure buffer: "
(mapcar #'buffer-name repl-buffers))))))))
(defun inf-clojure--response-match-p (form match-p proc)
"Send FORM and apply MATCH-P on the result of sending it to PROC.
Note that this function will add a \n to the end of the string
for evaluation, therefore FORM should not include it."
(funcall match-p (inf-clojure--process-response form proc nil)))
(provide 'inf-clojure)
;; Local variables:
;; coding: utf-8
;; indent-tabs-mode: nil
;; End:
;;; inf-clojure.el ends here
inf-clojure-3.2.1/todo.org 0000644 0001752 0001753 00000012752 14105036072 014046 0 ustar elpa elpa * Core
** DONE set repl type on connection not first command
For some reason ~inf-clojure--set-repl-type~ is called in:
1. inf-clojure--send-string
2. inf-clojure-reload-form
3. inf-clojure-reload-all-form
Seems better to do this on the two different connection methods and then be done with it?
** DONE do we need repl type in both source buffer and connection?
these can get out of sync and lead to confusing errors when closing a repl and opening a new one. It seems like we keep the repl-type in the source buffer to prevent a single ~(with-current-buffer (process-buffer proc) inf-clojure-repl-type)~
** DONE Better dispatch for the implementations
Right now the functions are kinda clunky cond statements:
#+BEGIN_SRC emacs-lisp
(defun inf-clojure-cljs-features-dispatch (feature)
(case feature
;; ((load) "(cljs.core/load-file \"%s\")")
((doc) "(cljs.repl/doc %s)")
((source) "(cljs.repl/source %s)")
((arglists) "(try (->> '%s cljs.core/resolve cljs.core/meta :arglists) (catch :default _ nil))")
((apropos) "(cljs.repl/apropos \"%s\")")
((ns-vars) "(cljs.repl/dir %s)")
((set-ns) "(cljs.core/in-ns '%s)")
((macroexpand) "(cljs.core/macroexpand '%s)")
((macroexpand-1) "(cljs.core/macroexpand-1 '%s)")
;; ((completion) inf-clojure-completion-form-lumo)
))
#+END_SRC
I really want something similar to ~(defprotocol Inf-clojure-REPL (doc ...)(source ...))~ rather than just this super open ended dispatch on symbols. I just don't know enough elisp at the moment. Also, the current function version prevents introspection and providing a way of listing the repl's capabilities without duplicating the keys. Or maybe a hack of sending all of the known keys in and seeing which return strings. Still not great.
** TODO Nicer interface to create a command
Right now everything is just a format string with a _single_ ~%s~ in it and that's called with ~(format feature-form e)~ where ~e~ is a symbol, or a form, or a whatever makes sense for that type of feature. This isn't super elegant although it does keep inf-clojure honest in that _all_ it does it format commands to ask a simple repl. But there could probably be a better way.
** DONE Simpler way to define an implementation
This first pass is very mechanical and just rearranging so we can easily see which features are where. In the future we should look into just providing the repl namespace and seeing how far we can get with that. For instance, an API like ~(inf-clojure-register 'bb "bb.repl")~ and this would tell us where ~source~, ~doc~, ~apropos~ ...etc live. No reason to duplicate all of these.
** DONE ability to update repl commands
we had this feature originally but now they are all literals. This is almost entirely fine but one problem. It would be common to toss clojure completions on the class path and then add the completions form for clojure.
This should come back but only in a sense: if you don't have this on the classpath its obviously unacceptable to throw errors every time the user hits tab. Do we need some state recording if this is on the classpath or not maybe?
#+BEGIN_SRC emacs-lisp
(defcustom inf-clojure-completion-form
"(complete.core/completions \"%s\")"
"Form to query inferior Clojure for completion candidates."
:type 'string
:safe #'stringp
:package-version '(inf-clojure . "2.0.0"))
#+END_SRC
** TODO Multiple connections
As proven by CIDER, multiple connections are just a pain. Scoping, navigating into dependencies, etc. This is a monster lurking
** TODO navigation to source
The source primitive is quite nice but we most likely need a way to navigate to source. Possibly punt on this and just suggest people use with clojure-lsp?
** TODO PREPL
Be nice to implement this now that we have parseedn in elisp to understand edn.
** DONE inhibit custom repl-type and startup form
its nice to have these in dir-locals to just start up. but if you normally have ~clojure -m cljs.main -r~ as the startup command but you want to crank up a clj repl there's no way without removing those dir locals.
* Nice-to-haves
** TODO Put repl type in modeline
Rather than just ~*inf-clojure*~ we could put the repl type. Make it easy to follow and makes it easy to see when it gets it wrong.
** TODO How do CIDER and inf-clojure play nice on the same emacs?
inf-clojure and CIDER are fighting over the keymappings. I've been doing a bit of a kludge to remove CIDER's tentacles from my clojure files for developing:
#+BEGIN_SRC emacs-lisp
(seq-doseq (buffer (buffer-list))
(with-current-buffer buffer
(cider-mode -1))
(remove-hook 'clojure-mode-hook #'cider-mode))
#+END_SRC
Seems a bit heavy handed but its working for me so far.
** TODO is disabling color still required?
in the readme it mentions that color should be turned off. in my usage I haven't run into this problem at all. perhaps no longer true?
** TODO nice startup
There's some project detection but that's becoming less and less useful as time goes on. Shadow, lein, deps.edn can all easily be mixed in the same project. And then lumo, planck, or bb scripts could live side by side. Rather than trying to guess the project type, I think i'd like to mimic geiser's style of handling multiple scheme backends. Perhaps ~m-x inf-clojure-run-planck~ and similar could help out.
Some considerations:
- is this path aware? IE, don't show an option to run planck, lumo, etc, if they aren't visible or installed?
- should it have a rebuild function so that user registered implementations can show up in the ~m-x~ menu as well?
inf-clojure-3.2.1/inf-clojure-pkg.el 0000644 0001752 0001753 00000000640 14266464005 015710 0 ustar elpa elpa ;; Generated package description from inf-clojure.el -*- no-byte-compile: t -*-
(define-package "inf-clojure" "3.2.1" "Run an external Clojure process in an Emacs buffer" '((emacs "25.1") (clojure-mode "5.11")) :commit "151b20ba9d3ae39b88f91aecbab98bd5a5215f1a" :maintainer '("Bozhidar Batsov" . "bozhidar@batsov.dev") :keywords '("processes" "comint" "clojure") :url "http://github.com/clojure-emacs/inf-clojure")
inf-clojure-3.2.1/README.md 0000644 0001752 0001753 00000046544 14262763733 013674 0 ustar elpa elpa [![Circle CI][circleci-badge]][circleci]
[![MELPA][melpa-badge]][melpa-package]
[![MELPA Stable][melpa-stable-badge]][melpa-stable-package]
[![NonGNU ELPA](https://elpa.nongnu.org/nongnu/inf-clojure.svg)](https://elpa.nongnu.org/nongnu/inf-clojure.html)
[![Discord](https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true)](https://discord.com/invite/nFPpynQPME)
[![License GPL 3][badge-license]][copying]
# inf-clojure
This package provides basic interaction with a Clojure subprocess (REPL).
It's based on ideas from the popular `inferior-lisp` package.
`inf-clojure` has two components - a nice REPL buffer (`inf-clojure-mode`) and a REPL
interaction minor mode (`inf-clojure-minor-mode`), which extends `clojure-mode`
with commands to evaluate forms directly in the REPL.
-----------
**This documentation tracks the `master` branch of `inf-clojure`. Some of
the features and settings discussed here might not be available in
older releases (including the current stable release). Please, consult
the relevant git tag (e.g. 2.2.0) if you need documentation for a
specific `inf-clojure` release.**
***
## Overview
`inf-clojure` aims to expose the extensive self-documenting features of Clojure
REPLs via an Emacs package. `inf-clojure` is extremely simple and does not require special tooling.
It supports the following REPLs:
- Clojure
- ClojureScript
- [Planck](http://planck-repl.org/)
- [Lumo](https://github.com/anmonteiro/lumo)
- [Joker](https://joker-lang.org/)
- [babashka](https://github.com/borkdude/babashka)
`inf-clojure` provides a set of essential features for interactive
Clojure(Script) development:
* Enhanced REPL
* Interactive code evaluation
* Code completion
* Definition lookup
* Documentation lookup
* ElDoc
* Apropos
* Macroexpansion
* Reloading a namespace (via `require :reload`/`require :reload-all`)
* Connecting to socket REPLs
For a more powerful/full-featured solution see [CIDER](https://github.com/clojure-emacs/cider).
## Rationale
`inf-clojure`'s goal is to provide the simplest possible way to interact with a Clojure REPL.
In Emacs terminology "inferior" process is a subprocess started by Emacs (it being the "superior" process, of course).
`inf-clojure` doesn't require much of setup, as at its core it simply runs a terminal REPL process, pipes input to it, and
processes its output. As the Clojure socket REPL works in exactly the same manner `inf-clojure` can also interact with it.
Functionality like code completion and eldoc is powered by evaluation of predefined code snippets that provide the necessary results.
As different Clojure REPLs have different capabilities, `inf-clojure` tracks the type of a REPL and invokes
the right code for each REPL type.
`inf-clojure` is built on top of Emacs's [comint](https://github.com/emacs-mirror/emacs/blob/master/lisp/comint.el). Unfortunately `comint` is pretty light on official documentation, but there is a good overview/tutorial [here](https://www.masteringemacs.org/article/comint-writing-command-interpreter).
## Installation
**Note:** `inf-clojure` requires Emacs 25 or newer.
`inf-clojure` is available on the official [NonGNU ELPA](https://elpa.nongnu.org/nongnu/inf-clojure.html) `package.el` repo and on the community-maintained
[MELPA Stable][] and [MELPA][] repos.
NonGNU ELPA and MELPA Stable are recommended as they have the latest stable version.
MELPA has a development snapshot for users who don't mind breakage but
don't want to run `inf-clojure` from a git checkout.
You can install `inf-clojure` using the following command:
M-x package-install [RET] inf-clojure [RET]
or if you'd rather keep it in your Emacs config:
```emacs-lisp
(unless (package-installed-p 'inf-clojure)
(package-refresh-contents)
(package-install 'inf-clojure))
```
If the installation doesn't work try refreshing the package list:
M-x package-refresh-contents
`inf-clojure-minor-mode` will be auto-enabled for Clojure source buffers after you do
`M-x inf-clojure`. You can disable this behavior by setting `inf-clojure-auto-mode` to
`nil`.
You can also add the following to your Emacs config to enable
`inf-clojure-minor-mode` for Clojure source buffers, regardless of whether there's an `inf-clojure` REPL running:
```emacs-lisp
(add-hook 'clojure-mode-hook #'inf-clojure-minor-mode)
```
**Warning:** Don't enable `inf-clojure-minor-mode` and `cider-mode` at the same time. They
have overlapping functionality and keybindings and the result will be nothing
short of havoc.
## Basic Usage
Just invoke `M-x inf-clojure` or press `C-c C-z` within a Clojure
source file. You should get a prompt with the supported REPL types and
common startup forms. You can select one of these or type in your own
custom startup. This will start a REPL process for the current project
and you can start interacting with it.
If you've already started a socket REPL server, use `M-x inf-clojure-connect`
and enter its host and port numbers.
Inf-clojure aims to be very simple and offer tooling that the REPL
itself exposes. A few commands are:
- eval last sexp (`C-x C-e`)
- show arglists for function (`C-c C-a`)
- show var documentation (`C-c C-v`)
- show source (`C-c C-s`)
- insert top level form into REPL (`C-c C-j d`)
For a list of all available commands in `inf-clojure-mode` (a.k.a. the
REPL) and `inf-clojure-minor-mode` you can either invoke `C-h f RET
inf-clojure-mode` and `C-h f RET inf-clojure-minor-mode` or simply
browse their menus.
Many `inf-clojure-minor-mode` commands by default act on the symbol at
point. You can, however, change this behaviour by invoking such
commands with a prefix argument. For instance: `C-u C-c C-v` will ask
for the symbol you want to show the docstring for.
## Configuration
**Note:** The configuration options were changed massively in `inf-clojure` 3.0.
In the time-honoured Emacs tradition `inf-clojure`'s behaviour is extremely
configurable.
You can set custom values to `inf-clojure` variables on a
per-project basis using [directory
variables](https://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html)
or by setting them in in your init file.
You can see all the configuration options available using the command
`M-x customize-group RET inf-clojure`.
### Startup
While `inf-clojure` is capable of starting many common REPLs out of the box, it's
fairly likely you will want to set some custom REPL startup command
(e.g. because you need to include some `tools.deps` profile) and the REPL type
that goes with it. This is most easily achieved with the following `.dir-locals.el`:
```emacs-lisp
((nil
(inf-clojure-custom-startup . "clojure -A:compliment")
(inf-clojure-custom-repl-type . clojure)))
```
**Note:** This file has to be in the directory in which you're invoking `inf-clojure` or a parent
directory.
There are two important configuration variables here:
1. `inf-clojure-custom-startup`: Which startup command to use so
inf-clojure can run the inferior Clojure process (REPL).
2. `inf-clojure-custom-repl-type`: The type of the REPL started by the above command (e.g. `lumo`).
If these are set and you wish to prevent inf-clojure from using them,
use a prefix arg when invoking `inf-clojure` (`C-u M-x inf-clojure`).
### REPL Features
The supported REPL-features are in an alist called
`inf-clojure-repl-features` and it has the following shape:
```emacs-lisp
'((cljs . ((doc . "(cljs.repl/doc %s)")
(source . "(cljs.repl/source %s)")
(arglists . "(try (->> '%s cljs.core/resolve cljs.core/meta :arglists) (catch :default _ nil))")
(apropos . "(cljs.repl/apropos \"%s\")")
(ns-vars . "(cljs.repl/dir %s)")
(set-ns . "(in-ns '%s)")
(macroexpand . "(cljs.core/macroexpand '%s)")
(macroexpand-1 . "(cljs.core/macroexpand-1 '%s)"))))
```
If you want to add a new REPL type, just do something like:
``` emacs-lisp
(add-to-list 'inf-clojure-repl-features
(cons new-repl-type '((doc . "(myrepl/doc-command %s")
(source . "...")
...)))
```
The `inf-clojure-repl-features` data structure is just an
alist of alists, so you can manipulate it in numerous ways.
If you want to update a specific form there is a function
`inf-clojure-update-repl-feature` which can be used like so:
```emacs-lisp
(inf-clojure-update-feature 'clojure 'completion "(incomplete.core/completions \"%s\")")
```
#### Caveats
As `inf-clojure` is built on top of `comint` it has all the usual comint limitations -
namely it can't handle well some fancy terminal features (e.g. ANSI colours).
In general the "dumber" your terminal REPL is, the better (e.g. `clojure` vs `clj`).
Connecting to a socket REPL is one simple way to avoid dealing with this type of
problems.
If you decide _not_ to use the socket REPL, it is highly recommended
you disable output coloring and/or `readline` facilities: `inf-clojure` does not
filter out ASCII escape characters at the moment and will not behave correctly.
For Leiningen, there are no command-line switches and you need to add
a custom [`project.clj`
option](https://github.com/technomancy/leiningen/blob/master/sample.project.clj):
```clojure
...
:repl-options {:color false}
...
```
#### Clojure Command Line Socket REPL
If you have the new [Clojure CLI tools][] installed you can use the `clojure` command:
_do not use `clj` because it adds readline support_
``` shellsession
$ clojure -J-Dclojure.server.repl="{:port 5555 :accept clojure.core.server/repl}"
```
Then either `C-c M-c RET localhost RET 5555` from within Emacs or add the following to your `.dir-locals.el`:
```emacs-lisp
((nil . ((inf-clojure-custom-startup . ("localhost" . 5555)))))
```
#### Leiningen Socket REPL
For Leiningen, add the following option to your `~/.lein/profiles.clj` or your `project.clj`:
```clojure
:jvm-opts ["-Dclojure.server.repl={:port 5555 :accept clojure.core.server/repl}"]
```
Then run `lein repl` from within your project directory to start the
REPL. To connect, you can either `m-x inf-clojure-connect [RET]
localhost [RET] 5555` or you can put in a dir local file the
information on how connect:
```emacs-lisp
((nil (inf-clojure-custom-startup "localhost" . 5555)))
```
The socket server REPL configuration options are described [here](https://clojure.org/reference/repl_and_main#_launching_a_socket_server).
#### Lumo Socket REPL
Lumo is decoupled from `inf-clojure-project-type` and therefore the command used depends on what you are using for dependency resolution.
For example if a `project.clj` is present in the project root folder, `inf-clojure-lein-cmd` will be used.
After you launch `lumo ... -n 5555`, as customary, either `C-c M-c RET localhost RET 5555` from within Emacs or add the following to your `.dir-locals.el`:
```emacs-lisp
((nil (inf-clojure-custom-startup "localhost" . 5555)))
```
#### Multiple Process Support
To run multiple Clojure processes, you start the first up
with `inf-clojure`. It will be in a buffer named `*inf-clojure*`.
Rename this buffer with `rename-buffer`. You may now start up a new
process with another `inf-clojure`. It will be in a new buffer,
named `*inf-clojure*`. You can switch between the different process
buffers with `switch-to-buffer`.
**Note:** If you're starting `inf-clojure` within a Clojure project directory
the name of the project will be incorporated into the name of the REPL buffer
- e.g. `*inf-clojure my-project*`.
Commands that send text from source buffers to Clojure processes (like `inf-clojure-eval-defun`
or `inf-clojure-show-arglists`) have to choose a process to send to, when you have more than
one Clojure process around. This is determined by the global variable `inf-clojure-buffer`.
Suppose you have three inferior Clojures running:
```
Buffer Process
------ -------
foo inf-clojure
bar inf-clojure<2>
*inf-clojure* inf-clojure<3>
```
If you do a `inf-clojure-eval-defun` command on some Clojure source code,
what process do you send it to?
- If you're in a process buffer (foo, bar, or `*inf-clojure*`),
you send it to that process.
- If you're in some other buffer (e.g., a source file), you
send it to the process attached to buffer `inf-clojure-buffer`.
This process selection is performed by function `inf-clojure-proc`.
Whenever `inf-clojure` fires up a new process, it resets
`inf-clojure-buffer` to be the new process's buffer. If you only run
one process, this does the right thing. If you run multiple
processes, you might need to change `inf-clojure-buffer` to
whichever process buffer you want to use.
You can use the helpful function `inf-clojure-set-repl`. If called in
an `inf-clojure` REPL buffer, it will assign that buffer as the current
REPL (`(setq inf-clojure-buffer (current-buffer)`). If you are
not in an `inf-clojure` REPL buffer, it will offer a choice of
acceptable buffers to set as the REPL buffer. If called with a prefix,
it will always give the list even if you are currently in an
acceptable REPL buffer.
**Tip:** Renaming buffers will greatly improve the
functionality of this list; the list "project-1: clojure repl",
"project-2: cljs repl" is far more understandable than "inf-clojure",
"inf-clojure<2>".
#### REPL Type
An `inf-clojure` REPL has an associated type. The available types can be
obtained from `inf-clojure-repl-features`:
```emacs-lisp
(mapcar 'car inf-clojure-repl-features)
;; => (cljs lumo planck joker clojure babashka)
```
What does it mean that a REPL type is supported? Well, it means that
`inf-clojure` would use the proper Clojure(Script) code internally to power
commands like definition lookup and friends. Those differ from REPL to REPL and
can't be implemented in a REPL-independent way. The REPL type is inferred on
startup when using the `inf-clojure` command or is specified manually when using
`inf-clojure-connect`.
#### ElDoc
`eldoc-mode` is supported in Clojure source buffers and `*inferior-clojure*`
buffers which are running a Clojure REPL.
When ElDoc is enabled and there is an active REPL, it will show the argument
list of the function call you are currently editing in the echo area. It
accomplishes this by evaluating forms to get the metadata for the vars under
your cursor. One side effect of this is that it can mess with repl vars like
`*1` and `*2`. You can disable inf-clojure's Eldoc functionality with `(setq
inf-clojure-enable-eldoc nil)`.
ElDoc should be enabled by default in Emacs 26.1+. If it is not active by
default, you can activate ElDoc with `M-x eldoc-mode` or by adding the following
to you Emacs config:
```emacs-lisp
(add-hook 'clojure-mode-hook #'eldoc-mode)
(add-hook 'inf-clojure-mode-hook #'eldoc-mode)
```
ElDoc currently doesn't work with ClojureScript buffers and REPL's.
You can leave it enabled, it just won't show anything in the echo area.
#### Code Completion
Code completion is a tricky aspect if you are trying to be as close to
a generic REPL as possible. Planck and lumo REPL implementations
explicitly provide completion functions in their REPL namespaces. For
clojure, you will need to have a library on your classpath. If you are
using a recent version of Leiningen, you already have
[incomplete](https://github.com/nrepl/incomplete). You
could alternatively use `compliment {:mvn/version "0.3.10"}`.
```emacs-lisp
;; for incomplete
(inf-clojure-update-feature 'clojure 'completion "(incomplete.core/completions \"%s\")")
;; or
;; for compliment
(inf-clojure-update-feature 'clojure 'completion "(compliment.core/completions \"%s\")")
```
If you give a form for the completion form, it is your responsibility
to ensure that this namespace is on the classpath and required. If
using Leiningen, this is done for you with `incomplete`. If adding
`compliment`, the following sample `deps.edn` can conveniently add the dep
to your program:
```clojure
{:aliases {:compliment {:extra-deps {compliment {:mvn/version "0.3.10"}}}}}
```
Use the startup command: `clojure -A:compliment`. Then require the ns
once so that the completion machinery will work: `(require
'compliment.core)`. Now tab completion should work.
For more advanced customization, code completion is particularly open
to customization. Not only you can `setq` the customary
`inf-clojure-completion-form`, `inf-clojure-completion-form-lumo`,
`inf-clojure-completion-form-planck` and
`inf-clojure-completion-form-joker` - the form to send to the REPL -
but you can also use `inf-clojure-completions-fn` for specifying a
function that given the REPL response should return Elisp data
compatible with
[`completion-at-point-functions`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion-in-Buffers.html).
For more info run `M-x describe-variable RET
inf-clojure-completions-fn`. Another option is to have a look at [how
cider does
it](https://github.com/clojure-emacs/cider/blob/3e9ed12e8cfbad04d7618e649322765dc9bff5d6/cider-interaction.el#L595).
#### Lumo Setup
For an optimal Lumo experience the `-d` needs to be passed to Lumo
when launched from the command line. This disable `readline` support
in order to play nicely with Emacs.
## Troubleshooting
### Things seem broken
Inf-clojure is intentionally quite simple and just sends commands to a
REPL on your behalf to provide features. In order to do this
inf-clojure largely needs to know the REPL type so it can format the
correct calls. Most end up in `(lumo.repl/doc [symbol])` or
`(cljs.repl/doc ...)` so its important that the REPL type is set
correctly. This REPL type exists in the process buffer (REPL) and the
source buffers as a cache. If you have problems, run `m-x
inf-clojure-set-repl-type` from the source buffer to set the REPL type
in both buffers. To see how simple inf-clojure is, look at
`inf-clojure-repl-features` to see largely how things are laid out.
### REPL not responsive in Windows OS
In Windows, the REPL is not returning anything. For example, type `(+
1 1)` and press `ENTER`, the cursor just drops to a new line and
nothing is shown.
The explanation of this problem and solution can be found [here](https://groups.google.com/forum/#!topic/leiningen/48M-xvcI2Ng).
The solution is to create a file named `.jline.rc` in your `$HOME`
directory and add this line to that file:
```
jline.terminal=unsupported
```
### Log process activity
Standard Emacs debugging turns out to be difficult when an asynchronous process is involved. In this case try to enable logging:
```emacs-lisp
(setq inf-clojure-log-activity t)
```
This creates `.inf-clojure.log` in the project directory so that you can `tail -f` on it.
## License
Copyright © 2014-2022 Bozhidar Batsov and [contributors][].
Distributed under the GNU General Public License; type C-h C-c to view it.
[badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg
[melpa-badge]: http://melpa.org/packages/inf-clojure-badge.svg
[melpa-stable-badge]: http://stable.melpa.org/packages/inf-clojure-badge.svg
[melpa-package]: http://melpa.org/#/inf-clojure
[melpa-stable-package]: http://stable.melpa.org/#/inf-clojure
[COPYING]: http://www.gnu.org/copyleft/gpl.html
[circleci]: https://circleci.com/gh/clojure-emacs/inf-clojure
[circleci-badge]: https://circleci.com/gh/clojure-emacs/inf-clojure.svg?style=svg
[CIDER]: https://github.com/clojure-emacs/cider
[Leiningen]: http://leiningen.org
[contributors]: https://github.com/clojure-emacs/inf-clojure/contributors
[melpa]: http://melpa.org
[melpa stable]: http://stable.melpa.org
[Emacs init file]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Init-File.html
[Clojure cli tools]: https://clojure.org/guides/getting_started
[Boot]: http://boot-clj.com
inf-clojure-3.2.1/.circleci/ 0000755 0001752 0001753 00000000000 14105036072 014214 5 ustar elpa elpa inf-clojure-3.2.1/.circleci/config.yml 0000644 0001752 0001753 00000001301 14105036072 016177 0 ustar elpa elpa version: 2.1
# Default actions to perform on each Emacs version
default: &default-steps
steps:
- checkout
- run: apt-get update && apt-get install make
- run: make test
# Enumerated list of Emacs versions
jobs:
test-emacs-26:
docker:
- image: silex/emacs:26-ci-cask
entrypoint: bash
<<: *default-steps
test-emacs-27:
docker:
- image: silex/emacs:27-ci-cask
entrypoint: bash
<<: *default-steps
test-emacs-master:
docker:
- image: silex/emacs:master-ci-cask
entrypoint: bash
<<: *default-steps
workflows:
version: 2
ci-test-matrix:
jobs:
- test-emacs-26
- test-emacs-27
- test-emacs-master
inf-clojure-3.2.1/CHANGELOG.md 0000644 0001752 0001753 00000015715 14266464004 014213 0 ustar elpa elpa # Changelog
## master (unreleased)
## 3.2.1 (2022-07-22)
### Bugs fixed
* Address some small issues with NonGNU ELPA (e.g. missing maintainer metadata).
## 3.2.0 (2022-07-15)
### New features
* [#168](https://github.com/clojure-emacs/inf-clojure/pull/197): New helper function `inf-clojure-switch-to-recent-buffer` to select the last buffer an inf-clojure process buffer was swapped to from.
* [#187](https://github.com/clojure-emacs/inf-clojure/pull/197): New defcustom `inf-clojure-enable-eldoc` to disable eldoc interaction.
### Bugs fixed
* [#185](https://github.com/clojure-emacs/inf-clojure/issues/185): Improve cmd string splitting.
* [#193](https://github.com/clojure-emacs/inf-clojure/pull/193): Set syntax table in REPL buffer.
* Fix `inf-clojure-display-version` (it wasn't extracting properly the package version).
## 3.1.0 (2021-07-23)
### New features
* [#190](https://github.com/clojure-emacs/inf-clojure/pull/190): Helper function `inf-clojure-set-repl` to select inf-clojure process buffer.
* Auto-enable `inf-clojure-minor-mode` after invoking `inf-clojure`. This behaviour is controlled via `inf-clojure-auto-mode`.
* Include the project name automatically in the REPL buffer name.
### Bugs fixed
* [#152](https://github.com/clojure-emacs/inf-clojure/issues/152): Sanitize should only remove whitespace at the end of a command.
* [#188](https://github.com/clojure-emacs/inf-clojure/pull/188): Handle newlines between forms for `inf-clojure-eval-buffer`.
* [#189](https://github.com/clojure-emacs/inf-clojure/pull/189): Font-lock code inserted in the REPL from a source buffer.
## 3.0.0 (2020-08-01)
### New features
* [#174](https://github.com/clojure-emacs/inf-clojure/pull/174): Invoke `inf-clojure` with a prefix argument to prevent using `inf-clojure-custom-startup` and `inf-clojure-custom-repl-type`.
* Made it possible to add user-defined REPL types (by modifying `inf-clojure-repl-features`).
### Changes
* **(Breaking)** Restructure massively the configuration. See `inf-clojure-repl-features` for details.
* [#174](https://github.com/clojure-emacs/inf-clojure/pull/174): Set REPL type from startup form or prompt at startup, introduce `inf-clojure-custom-repl-type` defcustom.
* [#173](https://github.com/clojure-emacs/inf-clojure/issues/173): Use clojure-mode's project detection instead of duplicate version in inf-clojure.
### Bugs fixed
* [#178](https://github.com/clojure-emacs/inf-clojure/issues/178): Ensure a valid directory is used when starting process.
## 2.2.0 (2020-04-15)
### New features
* [#170](https://github.com/clojure-emacs/inf-clojure/pull/170): Add insert defun and last sexp commands.
* [#160](https://github.com/clojure-emacs/inf-clojure/pull/160): Support [Joker](https://joker-lang.org/).
### Bugs fixed
* [#164](https://github.com/clojure-emacs/inf-clojure/pull/164): Fix for eldoc-mode on ClojureCLR.
* [#135](https://github.com/clojure-emacs/inf-clojure/pull/135): Improve command sanitation code.
* Fix `info-clojure-apropos`.
## 2.1.0 (2018-01-02)
### New Features
* [#114](https://github.com/clojure-emacs/inf-clojure/pull/114): Introduce `inf-clojure-project-type` defcustom.
* [#117](https://github.com/clojure-emacs/inf-clojure/pull/117): Introduce `tools.deps` project type and `inf-clojure-tools-deps-cmd`.
* [#122](https://github.com/clojure-emacs/inf-clojure/pull/122): Introduce `inf-clojure-completions-fn` defcustom.
* [#128](https://github.com/clojure-emacs/inf-clojure/pull/128): Expose `inf-clojure-apropos` as `C-c C-S-a` in `inf-clojure-mode` (the REPL).
* [#125](https://github.com/clojure-emacs/inf-clojure/pull/125): Avoid throwing an error for frequent operations like completion.
* [#130](https://github.com/clojure-emacs/inf-clojure/pull/130): Support loading directory locals in our buffers.
* [#129](https://github.com/clojure-emacs/inf-clojure/pull/129): Improve the completion bounds detection (now with keywords).
* [#132](https://github.com/clojure-emacs/inf-clojure/pull/132): Introduce inf-clojure-reload.
### Bugs Fixed
* [#79](https://github.com/clojure-emacs/inf-clojure/pull/82): Eldoc error when running boot repl.
* [#83](https://github.com/clojure-emacs/inf-clojure/pull/85): No such namespace: complete.core in lumo REPL.
* [#93](https://github.com/clojure-emacs/inf-clojure/pull/93): Slow response from inf-clojure (completions, arglists, ...).
* [#101](https://github.com/clojure-emacs/inf-clojure/pull/101): `inf-clojure-set-ns` hangs Emacs.
* [#119](https://github.com/clojure-emacs/inf-clojure/pull/119): Set inf-clojure-buffer REPL type on detect.
* [#120](https://github.com/clojure-emacs/inf-clojure/pull/120): Send REPL string always, even if empty.
* [#128](https://github.com/clojure-emacs/inf-clojure/pull/128): Fix inf-clojure-apropos.
* [#131](https://github.com/clojure-emacs/inf-clojure/pull/131): Add macroexpand forms for Lumo.
## 2.0.1 (2017-05-18)
### Bugs Fixed
* [#77](https://github.com/clojure-emacs/inf-clojure/pull/77): Fix request "Eval expression:" if arglists return is `nil`.
## 2.0.0 (2017-05-01)
### New Features
* [#63](https://github.com/clojure-emacs/inf-clojure/pull/69): Fix spurious process output on init.
* [#57](https://github.com/clojure-emacs/inf-clojure/pull/68): Add `inf-clojure-connect`.
* [#66](https://github.com/clojure-emacs/inf-clojure/pull/56): Add Planck support.
* [#51](https://github.com/clojure-emacs/inf-clojure/pull/51): Commands do not prompt by default anymore, unless they receive a non-nil prefix argument.
* [#44](https://github.com/clojure-emacs/inf-clojure/pull/44): Add REPL types and Lumo support.
* [#50](https://github.com/clojure-emacs/inf-clojure/pull/50): Rename defcustoms to `inf-clojure-*-form` where appropriate.
* [#34](https://github.com/clojure-emacs/inf-clojure/pull/34): Add support for socket REPL connections.
* New interactive command `inf-clojure-display-version`.
* [#42](https://github.com/clojure-emacs/inf-clojure/issues/42): Add a defcustom controlling the window in which the REPL buffer is displayed (`inf-clojure-repl-use-same-window`).
* Font-lock the code in the REPL.
* Handle properly ANSI color escape sequences in the REPL.
* [#41](https://github.com/clojure-emacs/inf-clojure/issues/41): Add a command to quit the REPL (it's bound to `C-c C-q`).
* [#29](https://github.com/clojure-emacs/inf-clojure/issues/29): Add a command to restart the REPL.
* [#31](https://github.com/clojure-emacs/inf-clojure/issues/31): Invoke different init command based on the project type (boot, lein or generic).
### Changes
* Display the REPL in a different window by default (it used to be displayed in the current window).
* [#26](https://github.com/clojure-emacs/inf-clojure/issues/26): Make switching to the REPL optional on `inf-clojure-load-file` (it's now controlled via a prefix argument).
* Removed the `inf-clojure` alias `run-clojure`.
### Bugs Fixed
* [#35](https://github.com/clojure-emacs/inf-clojure/issues/35): Fix prompt being included in input history.
## 1.4.0 (2016-01-17)
### New Features
* [#22](https://github.com/clojure-emacs/inf-clojure/pull/22): Add ElDoc support.