Skip to content

Commit

Permalink
Enable requiring extension exports as JS lib (#72)
Browse files Browse the repository at this point in the history
Enable requiring extension exports as JS lib

Fixes #71

* Make refer work

* Fix potential require overwrite

* One apply less

* Prefix vscode extension requires with a dot

* Change to `ext://` for prefixing ext requires

* Add information about reuiring extension APIs

* Inline path

* Use .split

* munge the ns

* Bump sci dep
  • Loading branch information
PEZ authored May 30, 2022
1 parent 47b2e9d commit 1260323
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 33 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changes to Joyride

## [Unreleased]

- [Less boilerplate-y require of APIs from VS Code extensions](https://github.com/BetterThanTomorrow/joyride/issues/71)

## [0.0.12] - 2022-05-18

- [Enable access to more `promesa.core` vars](https://github.com/BetterThanTomorrow/joyride/issues/68)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ https://user-images.githubusercontent.com/30010/165934412-ffeb5729-07be-4aa5-b29

Joyride is Powered by [SCI](https://github.com/babashka/sci) (Small Clojure Interpreter).

See [doc/api.md](https://github.com/BetterThanTomorrow/joyride/blob/master/doc/api.md) for documentation about the Joyride API.

## WIP

You are entering a construction yard. Things are going to change and break your configs while we are searching for good APIs and UI/Ux.
Expand Down
2 changes: 1 addition & 1 deletion deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
org.babashka/sci
#_{:local/root "../babashka/sci"}
{:git/url "https://github.com/babashka/sci"
:git/sha "07e21bdab3c1365f7b2a06377e1c56fa09e95a15"}
:git/sha "37722fb5e49970fe428781b287f478d8310fd3df"}
org.babashka/sci-configs
#_{:local/root "../sci.configs"}
{:git/url "https://github.com/babashka/sci-configs"
Expand Down
28 changes: 27 additions & 1 deletion doc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,33 @@ You can make some code run when Joyride activates, by naming the scripts `activa

Look at the [Activation example](../examples/.joyride/scripts/activate.cljs) script for a way to use this, and for a way to make the script re-runnable.

### Namespaces
### NPM modules

TBD.

### VS Code, and Extension ”namespaces”

Joyride exposes its `vscode` module for scripts to consume. You require it like so:

```clojure
(ns joyride-api
(:require ...
["vscode" :as vscode]
...))
```

VS Code Extensions that export an API can be required using the `ext://` prefix followed by the extension identifier. For instance, to require [Calva's Extension API](https://calva.io/api/) use `"ext://betterthantomorrow.calva"`. Optionally you can specify any submodules in the exported API by suffixing the namespace with a `$` followed by the dotted path of the submodule. You can also `refer` objects in the API and submodules. Like so:

```clojure
(ns z-joylib.calva-api
(:require ...
["ext://betterthantomorrow.calva$v0" :as calva :refer [repl ranges]])
...)

(def current-form-text (second (ranges.currentForm)))
```

### ClojureScript Namespaces

In addition to `clojure.core`, `clojure.set`, `clojure.edn`, `clojure.string`,
`clojure.walk`, `clojure.data`, Joyride exposes
Expand Down
15 changes: 7 additions & 8 deletions examples/.joyride/scripts/joyride_api.cljs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
(ns joyride-api
(:require ["vscode" :as vscode]
["ext://betterthantomorrow.joyride" :as joy-api]
[promesa.core :as p]
[joyride.core :as joyride]))

(def joyrideExt (vscode/extensions.getExtension "betterthantomorrow.joyride"))
(def joyApi (.-exports joyrideExt))

(comment
;; Starting the nREPL server
(-> (.startNReplServer joyApi)
(-> (joy-api/startNReplServer)
(p/catch (fn [e] (println (.-message e) e))))
;; (Oh, yes, it's already started, of course.)
;; Try first stopping the server? That will not help,
Expand All @@ -18,13 +18,13 @@

(comment
;; Getting contexts
(.getContextValue joyApi "joyride.isNReplServerRunning")
(joy-api/getContextValue "joyride.isNReplServerRunning")

;; Non-Joyride context keys returns `nil`
(.getContextValue joyApi "foo.bar")
(joy-api/getContextValue "foo.bar")

;; NB: Use the extension instance for this!
(.getContextValue joyApi "joyride.isActive")
(joy-api/getContextValue "joyride.isActive")
;; Like so:
(.-isActive joyrideExt)
;; (Not that it matters, you can't deactivate Joyride,
Expand All @@ -38,8 +38,7 @@
.-extension
.-exports)
(require '[clojure.repl :refer [doc]])
(doc joyride/extension-context)
)
(doc joyride/extension-context))

;; in addition to the extension context, joyride.core also has:
;; * *file* - the absolute path of the file where an
Expand Down
21 changes: 7 additions & 14 deletions examples/.joyride/scripts/z_joylib/calva_api.cljs
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
(ns z-joylib.calva-api
(:require ["vscode" :as vscode]
["ext://betterthantomorrow.calva$v0" :as calva]
[joyride.core :as joyride]
[promesa.core :as p]
[z-joylib.editor-utils :as editor-utils]))

(def oc (joyride.core/output-channel))
(def calva (vscode/extensions.getExtension "betterthantomorrow.calva"))
(def calvaApi (-> calva
.-exports
.-v0
(js->clj :keywordize-keys true)))

(defn text-for-ranges-key [ranges-key]
(second ((get-in calvaApi [:ranges ranges-key]))))

(defn evaluate-in-session+ [session-key code]
(p/let [result ((get-in [:repl :evaluateCode] calvaApi)
(p/let [result (calva/repl.evaluateCode
session-key
code
#js {:stdout #(.append oc %)
:stderr #(.append oc (str "Error: " %))})]
(.-result result)))
(.-result result)))

(defn clj-evaluate+ [code]
(evaluate-in-session+ "clj" code))
Expand All @@ -31,7 +24,7 @@
(defn evaluate+
"Evaluates `code` in whatever the current session is."
[code]
(evaluate-in-session+ ((get-in calvaApi [:repl :currentSessionKey])) code))
(evaluate-in-session+ (calva/repl.currentSessionKey) code))

(defn evaluate-selection+ []
(p/let [code (editor-utils/current-selection-text)
Expand All @@ -41,10 +34,10 @@
;; Utils for REPL-ing Joyride code, when connected to a project REPL.

(defn joyride-eval-current-form+ []
(vscode/commands.executeCommand "joyride.runCode" (text-for-ranges-key :currentForm)))
(vscode/commands.executeCommand "joyride.runCode" (second (calva/ranges.currentForm))))

(defn joyride-eval-top-level-form+ []
(vscode/commands.executeCommand "joyride.runCode" (text-for-ranges-key :currentTopLevelForm)))
(vscode/commands.executeCommand "joyride.runCode" (second (calva/ranges.currentTopLevelForm))))

;; Bind to some nice keyboard shortcuts, e.g. like so:
;; {
Expand All @@ -62,4 +55,4 @@

(defn restart-clojure-lsp []
(p/do (vscode/commands.executeCommand "calva.clojureLsp.stop")
(vscode/commands.executeCommand "calva.clojureLsp.start")))
(vscode/commands.executeCommand "calva.clojureLsp.start")))
57 changes: 48 additions & 9 deletions src/joyride/sci.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
["path" :as path]
["vscode" :as vscode]
[clojure.string :as str]
[goog.object :as gobject]
[joyride.db :as db]
[joyride.config :as conf]
[sci.configs.funcool.promesa :as pconfig]
Expand Down Expand Up @@ -31,6 +32,27 @@
{:file ns-path
:source (str (fs/readFileSync path-to-load))})))

(def ^:private extension-namespace-prefix "ext://")

(defn- extract-extension-name [namespace]
(subs namespace (count extension-namespace-prefix)))

(defn- active-extension? [namespace]
(when (.startsWith namespace extension-namespace-prefix)
(let [[extension-name _module-name] (.split (extract-extension-name namespace) "$")
extension (vscode/extensions.getExtension extension-name)]
(and extension
(.-isActive extension)))))

(defn- extension-module [namespace]
(let [[extension-name module-name] (.split (extract-extension-name namespace) "$")
extension (vscode/extensions.getExtension extension-name)]
(when extension
(when-let [exports (.-exports extension)]
(if module-name
(gobject/getValueByKeys exports (.split module-name "."))
exports)))))

(def !ctx
(volatile!
(sci/init {:classes {'js goog/global
Expand All @@ -41,19 +63,36 @@
'extension-context (sci/copy-var db/extension-context joyride-ns)
'invoked-script (sci/copy-var db/invoked-script joyride-ns)
'output-channel (sci/copy-var db/output-channel joyride-ns)})
:load-fn (fn [{:keys [namespace opts]}]
:load-fn (fn [{:keys [ns libname opts]}]
(cond
(symbol? namespace)
(source-script-by-ns namespace)
(string? namespace) ;; node built-in or npm library
(if (= "vscode" namespace)
(symbol? libname)
(source-script-by-ns libname)
(string? libname) ;; node built-in or npm library
(cond
(= "vscode" libname)
(do (sci/add-class! @!ctx 'vscode vscode)
(sci/add-import! @!ctx (symbol (str @sci/ns)) 'vscode (:as opts))
(sci/add-import! @!ctx ns 'vscode (:as opts))
{:handled true})
(let [mod (js/require namespace)
ns-sym (symbol namespace)]

(active-extension? libname)
(let [module (extension-module libname)
munged-ns (symbol (munge libname))
refer (:refer opts)]
(sci/add-class! @!ctx munged-ns module)
(sci/add-import! @!ctx ns munged-ns (:as opts))
(when refer
(doseq [sym refer]
(let [prop (gobject/get module sym)
sub-sym (symbol (str munged-ns "$" sym))]
(sci/add-class! @!ctx sub-sym prop)
(sci/add-import! @!ctx ns sub-sym sym))))
{:handled true})

:else
(let [mod (js/require libname)
ns-sym (symbol libname)]
(sci/add-class! @!ctx ns-sym mod)
(sci/add-import! @!ctx (symbol (str @sci/ns)) ns-sym
(sci/add-import! @!ctx ns ns-sym
(or (:as opts)
ns-sym))
{:handled true}))))})))
Expand Down

0 comments on commit 1260323

Please sign in to comment.