Skip to content

vkocubinsky/clojure-repl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 

Repository files navigation

clojure-repl.el: Clojure REPL interaction

This package provides basic interaction with a Clojure subprocess (REPL). It’s based on ideas from inferior-lisp and inf-clojure packages. clojure-repl provides a set of essential features for interactive Clojure development:

  • REPL
  • Interactive code evaluation
  • Print source
  • Print documentation
  • Macroexpansion
  • Run tests
  • Automatically load and switch namespace
  • Automatically install repl functions
  • Print stack trace
  • Multiple projects support

Note: The package is independent from clojure lsp, but clojure lsp is a good complement to clojure-repl.

Unwanted effects

Development tools has features which I called Unwanted effects. It is not bugs, it is just how tool works. My statement is that some time we can fix Unwanted effects, but some time it is better to accept Unwanted effects rather than fix it, because may be a fix introduce new bugs or other Unwanted effects. It is kind of limitation if existing tool and attempt to fix all Unwanted effects just raise other Unwanted Effects or even produce incorrect result.

Let me give an example for clojure. For instance let’s run clj interpreter and run code (future (do (Thread/sleep 10) (println "Hello")))

user=> (future (do (Thread/sleep 10) (println "Hello")))
#object[clojure.core$future_call$reify__8578 0x153f66e7 {:status :pending, :val nil}]
user=> Hello
_

clojure is ready for input but there is no prompt. You can immediate type new expression for eval, let say (+ 1 2) and then click enter or click enter to get a prompt and then type (+ 1 2). The fact that clojure is ready for input but there is no prompt is Unwanted effects.

I will give other example of Unwanted effects for emacs comint mode. Suppose we run clojure in comint mode and then like to send (+ 1 2) into clojure process.

(comint-send-string (get-buffer-process (current-buffer)) "(+ 1 2)\n")

The answer in comint buffer will be

user=> 3  

But we want to have

3
user=>   

From other hand if we just type (+ 1 2) in repl everything works as expected, because we click enter in repl and we will see as expected

user=> (+ 1 2)
3
user=> _  

There are several ways to fix this Unwanted effects. If we agree to see input we can do this

(progn
 (comint-goto-process-mark)
 (insert "(+ 1 2)")
 (comint-send-input)
 )  

And output will be exaclty as in clojure repl

user=> (+ 1 2)
3
user=> _

But we probably don’t like to see input when send string from clojure buffer, for this case we can use this

(progn
  (comint-goto-process-mark)
  (insert "\n")
  (comint-set-process-mark)
  (comint-send-string (get-buffer-process (current-buffer)) "(+ 1 2)\n"))

And output will be

user=> 
3
user=> 

Other way to print result of (+ 1 2) in new line is specify preoutput filter, and try to format appropriate by regular expression

(add-hook 'comint-preoutput-filter-functions #'my-preoutput-filter nil t)

I believe that this way will produce bugs and other Unwanted Effects if one for instance try to execute below code

(println "user=> ")
(println "user=> user=>")

Clojure repl flows

Package clojure-repl just try to automate some routines which of course possible do with separate REPL and separate editor which is not connected to REPL.

So how I would work if I have separate editor and REPL. I usually have a project. Suppose I like to work with some namespace, let say vk.assistant.gql. First what I do is load namespace

user=> (require 'vk.assistant.gql)
nil

If no errors I switch to vk.assistant.gql namespace

user=> (in-ns 'vk.assistant.gql )
#object[clojure.lang.Namespace 0x6fe5da76 "vk.assistant.gql"]  

I prefer work from namespace which I currently work rather than with user namespace. Last things which I like is install REPL functions such as doc or source which available immediate in user namespace

vk.assistant.gql=> (apply require clojure.main/repl-requires)
nil  

Now I am ready to define and execute new function in namespace vk.assistant.gql

vk.assistant.gql=> (defn foo "foo function" [] :foo)
#'vk.assistant.gql/foo
vk.assistant.gql=> (doc foo)
-------------------------
vk.assistant.gql/foo
([])
  foo function
nil  

You can find yourself some Unwanted effects here. For instance if namespace vk.assistant.gql contains error, you can find something weird

user=> (require 'vk.assistant.gql)
Syntax error compiling at (vk/assistant/gql.clj:1:1).
Unable to resolve symbol: fff in this context
user=> (require 'vk.assistant.gql)
nil
user=> (in-ns 'vk.assistant.gql)
#object[clojure.lang.Namespace 0x6fe5da76 "vk.assistant.gql"]
vk.assistant.gql=> 
vk.assistant.gql=> (when true 1)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: when in this context  

It also described here https://clojure.org/guides/repl/navigating_namespaces

It is not mandatory restart REPL, if I fix error in my namespace I can fix everything as

vk.assistant.gql=> (in-ns 'user)
#object[clojure.lang.Namespace 0x1f4d38f9 "user"]
user=> (require 'vk.assistant.gql :reload)
nil
user=>   

Emacs flows

By default clojure-repl automatically evaluate sexp, defun, region, paragraph, buffer in current namespace(buffer namespace). Suppose you open file src/vk/assistant/gql.clj and click C-x C-e (clojure-repl-eval-sexp).

Package clojure-repl try to auto load current namespace. It happened only if auto load is enabled. That represented by variable clojure-repl-auto-load, which is t by default.

Then clojure-repl compare current buffer namespace with REPL namespace. If these are the same no auto load required. This comparison happened only if variable clojure-repl-smart-auto-load is t. Value t is the default.

If clojure-repl recognized that auto load required it execute form like this

(let [target-ns 'vk.assistant.clojure
    install-repl? true]
(if (or (= target-ns 'user) (= target-ns (ns-name *ns*)))
  [:clojure-repl/auto :skip]
  (try
    (in-ns 'user)
    (clojure.core/require target-ns)
    (in-ns target-ns)
    (when install-repl?
      (require 'clojure.main)
      (apply require clojure.main/repl-requires))
    [:clojure-repl/auto :success]
    (catch Exception e
      (in-ns 'user)
      [:clojure-repl/auto :fail (.getMessage e)]))))

By default it will be executed once after you switch to new clojure buffer. You will see output like this for first time

[:clojure-repl/auto :success]

There are some of evaluation functions available

FunctionKeyDescription
clojure-repl-eval-defunC-M-xEval defun
clojure-repl-eval-paragraphC-c C-cEval paragraph
clojure-repl-eval-sexpC-c C-eEval sexp
C-x C-e
clojure-repl-pprint-sexpC-c C-pPretty print sexp
clojure-repl-eval-bufferC-c C-bEval buffer
clojure-repl-eval-regionC-c C-rEval region

All this functions use emacs things-at-point to get text.

Note: Function clojure-repl-eval-sexp and clojure-repl-pprint-sexp use current sexp, not last sexp used by packages inf-clojure or CIDER, I selected this approach because it is convenient way to extract text, just use things-at-point, and found it not worse than use last sexp.

There is also special function clojure-repl-eval-newline (C-c C-n). It is fix for Unwanted effects like

(future (do (Thread/sleep 10) (println "Hello")))  

If you try execute this, and then try to type anything in REPL or send some text, you will see message from emacs “Text is read-only”. That’s because I set comint-prompt-read-only to t. But if you click clojure-repl-eval-newline (C-c C-n) you will see prompt again.

You may prefer do not auto load current namespace. You can switch this dynamically with

FunctionDescription
clojure-repl-auto-load-disabledisable auto load
clojure-repl-auto-load-enableenable auto load

Or set custom variables

VariableDescription
clojure-repl-smart-auto-loadCompare buffer namespace with REPL namespace before try auto load
clojure-repl-auto-loadEnable/Disable auto load
clojure-repl-auto-replInstall REPL functions after auto load

In case of manual mode(and auto load) you can use

FunctionDescription
clojure-repl-load-fileload current file
clojure-repl-loadload current namespace
clojure-repl-reloadreload current namespace
clojure-repl-reload-allreload namespace and dependencies
clojure-repl-load-replinstall REPL functions

There are many other functions exists, you can see everything in menu or with describe mode.

Installation

Currently, the recommended ways to install clojure-repl are with use-package and :vc, or from a local checkout.

Install with use-package and vc

(use-package clojure-repl
  :vc (:url "https://github.com/vkocubinsky/clojure-repl"
       :rev :newest)
  :hook (clojure-mode . clojure-repl-minor-mode))

Install from local source checkout

(use-package clojure-repl
  :load-path "/path-to-repo/clojure-repl"
  :hook (clojure-mode . clojure-repl-minor-mode))

Configuration

TODO

About

Comint mode for Clojure.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors