Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .dir-locals.el

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
/out/
/.lsp/sqlite*.db
/.calva/output-window/*.calva-repl
/.shadow-cljs
14 changes: 9 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ clean:
.cpcache \
target \
out \
.cljs_nashorn_repl \
.cljs_node_repl \
nashorn_code_cache \
.rebel_readline_history

test: ${SRC_FILES}
Expand All @@ -38,8 +36,14 @@ deploy: all
-Durl=https://clojars.org/repo \
-DrepositoryId=clojars

# starts a figwheel repl with suitable enabled
fig-repl:
clojure -A:fig-repl
clojure -M:fig-repl

nrepl:
clojure -A:dev -R:test:nrepl
# useful for development, see comment in src/dev/suitable/nrepl_figwheel.clj
nrepl-figwheel:
clojure -M:test:dev-figwheel

# useful for development, see comment in src/dev/suitable/nrepl_.clj
nrepl-shadow:
clojure -M:test:dev-shadow
76 changes: 21 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,16 @@

It provides two complementary completion sources:

- It integrates with the CLJS analyzer and using the compilation state for
"static" symbol completion. This functionality was briefly part of
[compliment](https://github.com/alexander-yakushev/compliment), and before this - [Orchard](https://github.com/clojure-emacs/orchard) and [cljs-tooling](https://github.com/clojure-emacs/cljs-tooling).
- It can use a CLJS REPL session to query and inspect JavaScript runtime state,
allowing code completion for JavaScript objects and interfaces.
- It integrates with the CLJS analyzer and using the compilation state for "static" symbol completion. This functionality was briefly part of [compliment](https://github.com/alexander-yakushev/compliment), and before this - [Orchard](https://github.com/clojure-emacs/orchard) and [cljs-tooling](https://github.com/clojure-emacs/cljs-tooling).
- It can use a CLJS REPL session to query and inspect JavaScript runtime state, allowing code completion for JavaScript objects and interfaces.

## Static code completion

The static code completion is based on analysis of the ClojureScript compiler state.
This approach was pioneered by `cljs-tooling` and the completion logic was subsequently moved to `orchard`, `compliment` and finally here.
The static code completion is based on analysis of the ClojureScript compiler state. This approach was pioneered by `cljs-tooling` and the completion logic was subsequently moved to `orchard`, `compliment` and finally here.

Why here? Because it's very convenient from the user perspective to have a single library providing both types of completion.

This type of completion provides a [compliment custom
source](https://github.com/alexander-yakushev/compliment/wiki/Custom-sources)
for ClojureScript, so it's easy to plug with the most popular completion framework out there.
This type of completion provides a [compliment custom source](https://github.com/alexander-yakushev/compliment/wiki/Custom-sources) for ClojureScript, so it's easy to plug with the most popular completion framework out there.

``` clojure
(ns suitable.demo
Expand All @@ -39,45 +33,29 @@ for ClojureScript, so it's easy to plug with the most popular completion framewo
(complete/completions prefix (merge completion-opts {:sources cljs-sources})))
```

Note that you'll need to establish a binding to `suitable-sources/*compiler-env*`
for the completion to work.
Note that you'll need to establish a binding to `suitable-sources/*compiler-env*` for the completion to work.

## Dynamic code completion for CLJS repls

The dynamic code completion features allow for exploratory development by
inspecting the runtime. For example you work with DOM objects but can't remember
how to query for child elements. Type `(.| js/document)` (with `|` marking the
postion of your cursor) and press TAB. Methods and properties of `js/document`
will appear — including `querySelector` and `querySelectorAll`.
The dynamic code completion features allow for exploratory development by inspecting the runtime. For example you work with DOM objects but can't remember how to query for child elements. Type `(.| js/document)` (with `|` marking the postion of your cursor) and press TAB. Methods and properties of `js/document` will appear — including `querySelector` and `querySelectorAll`.

### Beware the Side-Effects

The dynamic code completion *evaluates* code on completion requests! It does
this by trying to [enumerate the
properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors)
of JavaScript objects, so in the example above it would fetch all properties of
the `js/document` object. This might cause side effects: Even just reading property values of an object can run arbitrary code if that object defines getter functions.
The dynamic code completion *evaluates* code on completion requests! It does this by trying to [enumerate the properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors) of JavaScript objects, so in the example above it would fetch all properties of the `js/document` object. This might cause side effects: Even just reading property values of an object can run arbitrary code if that object defines getter functions.

Moreover, also chains of methods and properties will be evaluated upon
completion requests. So for example, asking for completions for the code /
cursor position `(-> js/some-object (.deleteAllMyFilesAndStartAWar) .|)` will
evaluate the JavaScript code `some-object.deleteAllMyFilesAndStartAWar()`!
This only applies to JavaSript interop code, i.e. JavaScript methods and properties. Pure ClojureScript is not inspected or evaluated. Please be aware of this behavior when using the dynamic code completion features.
Moreover, also chains of methods and properties will be evaluated upon completion requests. So for example, asking for completions for the code / cursor position `(-> js/some-object (.deleteAllMyFilesAndStartAWar) .|)` will evaluate the JavaScript code `some-object.deleteAllMyFilesAndStartAWar()`! This only applies to JavaSript interop code, i.e. JavaScript methods and properties. Pure ClojureScript is not inspected or evaluated. Please be aware of this behavior when using the dynamic code completion features.

### Dynamic completion Demo

The animation shows how various properties and methods of the native DOM can be
accessed (Tab is used to show completions for the expression at the cursor):
The animation shows how various properties and methods of the native DOM can be accessed (Tab is used to show completions for the expression at the cursor):

![](doc/2019_07_22_suitable-figwheel.gif)

## Setup

### figwheel.main with rebel-readline

Please note that you need to use
[rebel-readline](https://github.com/bhauman/rebel-readline) with figwheel for
that to work. Plain repls have no completion feature.
Please note that you need to use [rebel-readline](https://github.com/bhauman/rebel-readline) with figwheel for that to work. Plain repls have no completion feature.

#### Tools CLI

Expand All @@ -90,8 +68,9 @@ Then modify `deps.edn` and `dev.cljs.edn`, you should end up with the files look
```clojure
{:deps {com.bhauman/figwheel-main {:mvn/version "RELEASE"}
com.bhauman/rebel-readline-cljs {:mvn/version "RELEASE"}}
:paths ["src" "resources" "target"]
:aliases {:suitable {:extra-deps {org.rksm/suitable {:mvn/version "RELEASE"}}
:paths ["src" "target" "resources"]
:aliases {:build-dev {:main-opts ["-m" "figwheel.main" "-b" "dev" "-r"]}
:suitable {:extra-deps {org.rksm/suitable {:mvn/version "RELEASE"}}
:main-opts ["-e" "(require,'suitable.hijack-rebel-readline-complete)"
"-m" "figwheel.main"
"--build" "dev" "--repl"]}}}
Expand All @@ -110,24 +89,21 @@ Then modify `deps.edn` and `dev.cljs.edn`, you should end up with the files look
(ns example.core)
```

You can now start a figwheel repl via `clj -A:suitable` and use TAB to complete.
You can now start a figwheel repl via `clj -M:suitable` and use TAB to complete.

#### leiningen

First make sure that the [normal leiningen setup](https://figwheel.org/#setting-up-a-build-with-leiningen) works.

Add `[org.rksm/suitable "0.3.5"]` to your dependencies vector.
Add `[org.rksm/suitable "0.4.0"]` to your dependencies vector.

Then you can start a repl with `lein trampoline run -m suitable.figwheel.main -- -b dev -r`

### Emacs CIDER

Suitable is used by CIDER's code completion middleware (as of CIDER 0.22.0), so no extra
installation steps are required.
Suitable is used by CIDER's code completion middleware (as of CIDER 0.22.0), so no extra installation steps are required.

CIDER will always use the static code completion provided by suitable, regardless of the ClojureScript runtime,
but the dynamic completion is **not** currently available with `shadow-cljs`. (See [this ticket](https://github.com/clojure-emacs/clj-suitable/issues/15) for
more details)
CIDER will always use the static code completion provided by suitable, regardless of the ClojureScript runtime, but the dynamic completion is **not** currently available with `shadow-cljs`. (See [this ticket](https://github.com/clojure-emacs/clj-suitable/issues/15) for more details)

In case you run into any issues with suitable's dynamic completion in CIDER you can disable it like this:

Expand All @@ -148,7 +124,7 @@ Suitable is always-on, just as with CIDER.
To load suitable into a custom server you can load it using this monstrosity:

```shell
clj -Sdeps '{:deps {cider/cider-nrepl {:mvn/version "0.22.0"} cider/piggieback {:mvn/version"0.4.1"}}}' -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware,cider.piggieback/wrap-cljs-repl]"
clj -Sdeps '{:deps {cider/cider-nrepl {:mvn/version "RELEASE"} cider/piggieback {:mvn/version "RELEASE"}}}' -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware,cider.piggieback/wrap-cljs-repl]"
```

Or from within Clojure:
Expand All @@ -175,19 +151,9 @@ Please see [issue #2](https://github.com/rksm/clj-suitable/issues/2#issuecomment
## How does it work?

suitable uses the same input as the widely used
[compliment](https://github.com/alexander-yakushev/compliment). This means it
gets a prefix string and a context form from the tool it is connected to. For
example you type `(.l| js/console)` with "|" marking where your cursor is. The
input we get would then be: prefix = `.l` and context = `(__prefix__ js/console)`.

suitable recognizes various ways how CLJS can access properties and methods,
such as `.`, `..`, `doto`, and threading forms. Also direct global access is
supported such as `js/console.log`. suitable will then figure out the expression
that defines the "parent object" that the property / method we want to use
belongs to. For the example above it would be `js/console`. The system then uses
the [EcmaScript property descriptor API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)
to enumerate the object members. Those are converted into completion candidates
and send back to the tooling.
[compliment](https://github.com/alexander-yakushev/compliment). This means it gets a prefix string and a context form from the tool it is connected to. For example you type `(.l| js/console)` with "|" marking where your cursor is. The input we get would then be: prefix = `.l` and context = `(__prefix__ js/console)`.

suitable recognizes various ways how CLJS can access properties and methods, such as `.`, `..`, `doto`, and threading forms. Also direct global access is supported such as `js/console.log`. suitable will then figure out the expression that defines the "parent object" that the property / method we want to use belongs to. For the example above it would be `js/console`. The system then uses the [EcmaScript property descriptor API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) to enumerate the object members. Those are converted into completion candidates and send back to the tooling.

## License

Expand Down
18 changes: 12 additions & 6 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
{:deps {org.clojure/clojurescript {:mvn/version "[1.9.908,)"}
compliment {:mvn/version "0.3.10"}}
compliment/compliment {:mvn/version "0.3.10"}}

:paths ["src/main"]

:aliases {;; for starting nrepl clj & cljs servers for live development
:dev {:extra-paths ["src/dev" "resources" "target"]
:extra-deps {cider/piggieback {:mvn/version "RELEASE"}
com.bhauman/figwheel-main {:mvn/version "RELEASE"}
cider/cider-nrepl {:mvn/version "RELEASE"}}
:main-opts ["-m" "suitable.nrepl"]}
:dev-figwheel {:extra-paths ["src/dev" "resources" "target"]
:extra-deps {cider/piggieback {:mvn/version "RELEASE"}
cider/cider-nrepl {:mvn/version "RELEASE"}
com.bhauman/figwheel-main {:mvn/version "RELEASE"}}
:main-opts ["-m" "suitable.nrepl-figwheel"]}

:dev-shadow {:extra-paths ["src/dev" "resources" "target"]
:extra-deps {cider/piggieback {:mvn/version "RELEASE"}
cider/cider-nrepl {:mvn/version "RELEASE"}
thheller/shadow-cljs {:mvn/version "RELEASE"}}
:main-opts ["-m" "suitable.nrepl-shadow"]}

:fig-repl {:extra-paths ["resources" "target" "src/dev" "src/test"]
:main-opts ["-e" "(require,'suitable.hijack-rebel-readline-complete)"
Expand Down
9 changes: 7 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.rksm</groupId>
<artifactId>suitable</artifactId>
<version>0.3.5</version>
<version>0.4.0</version>
<name>suitable</name>
<description>An addon for Figwheel and Emacs Cider to aid live exploratory development. Queries objects in ClojureScript for their properties to use as part of nREPL completion handlers.</description>
<url>http://github.com/rksm/clj-suitable</url>
Expand All @@ -25,7 +25,12 @@
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojurescript</artifactId>
<version>[1.9.908,)</version>
<version>1.10.844</version>
</dependency>
<dependency>
<groupId>compliment</groupId>
<artifactId>compliment</artifactId>
<version>0.3.10</version>
</dependency>
</dependencies>

Expand Down
11 changes: 11 additions & 0 deletions shadow-cljs.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
;; shadow-cljs configuration
{:source-paths
["src/dev"
"src/main"
"src/test"]

:dependencies
[]

:builds
{}}
92 changes: 1 addition & 91 deletions src/dev/suitable/nrepl.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
(:require cider.nrepl
cider.piggieback
[clojure.pprint :refer [cl-format pprint]]
figwheel.main
figwheel.main.api
nrepl.core
nrepl.server
[suitable.middleware :refer [wrap-complete]]))
Expand All @@ -13,7 +11,7 @@
;; a la https://github.com/nrepl/piggieback/issues/91
;; 1. start nrepl server with piggieback
;; 2. get session
;; 3. send cljs start form (e.g. nashorn or figwheel)
;; 3. send cljs start form (e.g. figwheel)
;; 4. ...profit!

;; 1. start nrepl server with piggieback
Expand Down Expand Up @@ -59,91 +57,3 @@

(defn cljs-send-eval [code]
(@cljs-send-msg {:op :eval :code code}))

;; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

(defn restart-cljs-server []
(when @cljs-nrepl-server
(nrepl.server/stop-server @cljs-nrepl-server))
(require 'figwheel.main.api)
(try (figwheel.main.api/stop-all) (catch Exception e (prn e)))

(start-cljs-nrepl-server)
(start-cljs-nrepl-client))

(defn -main [& args]
(start-clj-nrepl-server)

(start-cljs-nrepl-server)
(start-cljs-nrepl-client)
;; (cljs-send-eval "(require 'figwheel.main) (figwheel.main/start :fig)")
)

;; (restart-cljs-server)



;; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

(comment
(pprint (doall (nrepl.core/message sess1 {:op :eval :code "(require 'cider.piggieback) (require 'cljs.repl.nashorn) (cider.piggieback/cljs-repl (cljs.repl.nashorn/repl-env))"})))
(pprint (doall (nrepl.core/message sess1 {:op :eval :code "(require 'figwheel.main) (figwheel.main/start :fig)"})))
(pprint (doall (nrepl.core/message sess1 {:op :eval :code "(require 'figwheel.main) (figwheel.main/stop-builds :fig)"})))
(pprint (doall (nrepl.core/message sess1 {:op :eval :code ":cljs/quit"})))
(pprint (doall (nrepl.core/message sess1 {:op :eval :code "js/console"})))
(pprint (doall (nrepl.core/message sess1 {:op :eval :code "123"})))
(nrepl.core/message sess1 {:op :eval :code "(list 1 2 3)"})
)



(comment

(do (start-nrepl-server)
(start-nrepl-client)
(send-eval "(require 'figwheel.main) (figwheel.main/start :fig)"))

(require 'figwheel.main.api) (figwheel.main.api/cljs-repl "fig")
123
(send-eval "(require 'suitable.core)")
(send-eval "123")

(send-eval "(require 'figwheel.main.api) (figwheel.main.api/cljs-repl \"fig\")")
(send-eval ":cljs/quit")

(@send-msg {:op :close})

(@send-msg {:op :complete :symbol "js/co" :ns "cljs.user" :context nil})
(@send-msg {:op :complete :symbol "cljs." :ns "suitable.core" :context nil})

(require '[cider.nrepl.inlined-deps.cljs-tooling.v0v3v1.cljs-tooling.complete :as cljs-complete])
(require '[cider.nrepl.middleware.util.cljs :as cljs])

(let [ns (symbol "cljs.user")
prefix (str "cljs.co")
extra-metadata (set (map keyword nil))
cljs-env (some-> (figwheel.main.api/repl-env "fig") :repl-options deref :compiler-env deref)]
(cljs-complete/completions cljs-env prefix {:context-ns ns
:extra-metadata extra-metadata}))

;; -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-



(send-eval "123")


(send-eval "(require 'figwheel.main) (figwheel.main/start :fig)")
(send-eval "*ns*")
(send-eval "js/console")


(-> @figwheel.main/build-registry (get "fig"))
(figwheel.main.watching/reset-watch!)

(def server (-> @figwheel.main/build-registry (get "fig") :repl-env :server deref))
(.stop server)
(figwheel.main/stop-builds :fig)

)

Loading