diff --git a/README.md b/README.md index 1a0cd15..89ef7bd 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ See the [examples](./examples) for examples including: * Calva Structural Editing enhancements * Opening and showing project files * Workspace activation script +* The Joyride Extension API +* The `joyride.core` namespace ## Support and feedback @@ -110,6 +112,8 @@ You'll find us in the `#joyride` channel on the [Clojurians Slack](http://clojur ## News +* Show HN: https://news.ycombinator.com/item?id=31203024#31206003 + ### Twitter Follow the [#vsjoyride](https://twitter.com/search?q=%23vsjoyride&src=typed_query&f=live) hashtag on Twitter! diff --git a/doc/api.md b/doc/api.md index ddc0a8c..d1ed673 100644 --- a/doc/api.md +++ b/doc/api.md @@ -7,6 +7,8 @@ The Joyride API consist of: * Included clojure library namespaces 1. The Joyride *Extension API* +Give the [joyride_api.cljs](../examples/.joyride/scripts/joyride_api.cljs) example a spin, why don't ya! + Please note that Joyride's *Extension API* is also available to *Joyride scripts*. ## Scripting API @@ -28,27 +30,42 @@ the following namespaces: #### `joyride.core` -- `*file*`: dynamic var representing the currently executing file -- `get-invoked-script`: function returning the absolute path of the invoked script when running as a script. Otherwise returns `nil`. Together with `*file*` this can be used to create a guard that avoids running certain code when you load a file in the REPL: +- `*file*`: dynamic var holding the absolute path of file where the current evaluation is taking place +- `invoked-script`: function returning the absolute path of the invoked script when running as a script. Otherwise returns `nil`. Together with `*file*` this can be used to create a guard that avoids running certain code when you load a file in the REPL: ```clojure - (when (= (joyride/get-invoked-script) joyride/*file*) + (when (= (joyride/invoked-script) joyride/*file*) (main)) ``` -- `get-extension-context`: function returning the Joyride [extension context](https://code.visualstudio.com/api/references/vscode-api#ExtensionContext) object +- `extension-context`: function returning the Joyride [ExtensionContext](https://code.visualstudio.com/api/references/vscode-api#ExtensionContext) instance +- `output-channel`: function returning the Joyride [OutputChannel](https://code.visualstudio.com/api/references/vscode-api#OutputChannel) instance -NB: While using `*file*` bare works, it will probably stop working soon. Always use it from `joyride.core`, e.g.: +Here's a snippet from the [joyride_api.cljs](../examples/.joyride/scripts/joyride_api.cljs) example. ```clojure (ns your-awesome-script - (:require [joyride.core :refer [*file*]] - ...)) + (:require [joyride.core :as joyride] + ...) + +(doto (joyride/output-channel) + (.show true) + (.append "Writing to the ") + (.appendLine "Joyride output channel.") + (.appendLine (str "Joyride extension path: " + (-> (joyride/extension-context) + .-extension + .-extensionPath))) + (.appendLine (str "joyride/*file*: " joyride/*file*)) + (.appendLine (str "Invoked script: " (joyride/invoked-script))) + (.appendLine "🎉")) ``` +**NB**: Currently, using bar `*file*` works. But it will probably stop working soon. Always use it from `joyride.core`. + #### promesa.core See [promesa docs](https://cljdoc.org/d/funcool/promesa/6.0.2/doc/user-guide). -**``**: `p/->>`, `p/->`, `p/all`, `p/any`, `p/catch`, `p/chain`, `p/create`, `p/deferred`, `p/delay`, `p/do`, `p/do`, `p/done`, `p/finally`, `p/let`, `p/map`, `p/mapcat`, `p/pending`, `p/promise`, `p/promise`, `p/race`, `p/rejected`, `p/rejected`, `p/resolved`, `p/resolved`, `p/run`, `p/then`, `p/thenable`, `p/with`, and `p/wrap` +**``**: `p/->>`, `p/->`, `p/all`, `p/any`, `p/catch`, `p/chain`, `p/create`, `p/deferred`, `p/delay`, `p/do`, `p/do!`, `p/done?`, `p/finally`, `p/let`, `p/map`, `p/mapcat`, `p/pending`, `p/promise`, `p/promise?`, `p/race`, `p/rejected`, `p/rejected?`, `p/resolved`, `p/resolved?`, `p/run!`, `p/then`, `p/thenable?`, `p/with`, and `p/wrap` ### Possibly coming additions diff --git a/examples/.joyride/scripts/activate.cljs b/examples/.joyride/scripts/activate.cljs index 8c1a8a3..fccfd87 100644 --- a/examples/.joyride/scripts/activate.cljs +++ b/examples/.joyride/scripts/activate.cljs @@ -18,7 +18,7 @@ ;; Joyride extension is deactivated. (defn- push-disposable [disposable] (swap! !db update :disposables conj disposable) - (-> (joyride/get-extension-context) + (-> (joyride/extension-context) .-subscriptions (.push disposable))) @@ -35,5 +35,5 @@ "document opened:" (.-fileName doc)))))) -(when (= (joyride/get-invoked-script) joyride/*file*) +(when (= (joyride/invoked-script) joyride/*file*) (my-main)) diff --git a/examples/.joyride/scripts/fontsize.cljs b/examples/.joyride/scripts/fontsize.cljs index 0bdb0ff..3c37309 100644 --- a/examples/.joyride/scripts/fontsize.cljs +++ b/examples/.joyride/scripts/fontsize.cljs @@ -7,7 +7,7 @@ (.update "editor.fontSize" pts true)) nil) -(when (= (joyride/get-invoked-script) joyride/*file*) +(when (= (joyride/invoked-script) joyride/*file*) (set-global-fontsize 12)) ;; live demo here: https://twitter.com/borkdude/status/1519709769157775360 diff --git a/examples/.joyride/scripts/ignore_form.cljs b/examples/.joyride/scripts/ignore_form.cljs index dfb98f8..ec9ecd8 100644 --- a/examples/.joyride/scripts/ignore_form.cljs +++ b/examples/.joyride/scripts/ignore_form.cljs @@ -17,5 +17,5 @@ (p/do! (eu/delete-range! editor range-before-insert-position)) (p/do! (eu/insert-text!+ "#_" editor insert-position))))) -(when (= (joyride/get-invoked-script) joyride/*file*) +(when (= (joyride/invoked-script) joyride/*file*) (main)) diff --git a/examples/.joyride/scripts/joyride_api.cljs b/examples/.joyride/scripts/joyride_api.cljs index def74fa..e56f95e 100644 --- a/examples/.joyride/scripts/joyride_api.cljs +++ b/examples/.joyride/scripts/joyride_api.cljs @@ -1,6 +1,7 @@ (ns joyride-api (:require ["vscode" :as vscode] - [promesa.core :as p])) + [promesa.core :as p] + [joyride.core :as joyride])) (def joyrideExt (vscode/extensions.getExtension "betterthantomorrow.joyride")) (def joyApi (.-exports joyrideExt)) @@ -33,10 +34,30 @@ (comment ;; Joyride scripts can also reach the Joyride extension ;; through `joyride.core` - (require '[joyride.core :as joyride]) - (require '[clojure.repl :refer [doc]]) - (-> (joyride/get-extension-context) + (-> (joyride/extension-context) .-extension .-exports) - (doc joyride/get-extension-context) + (require '[clojure.repl :refer [doc]]) + (doc joyride/extension-context) ) + +;; in addition to the extension context, joyride.core also has: +;; * *file* - the absolute path of the file where an +;; evaluation takes place +;; * invoked-script - the absolute path of a script being run +;; `nil` in other execution contexts +;; * output-channel - Joyride's output channel + +(doto (joyride/output-channel) + (.show true) + (.append "Writing to the ") + (.appendLine "Joyride output channel.") + (.appendLine (str "Joyride extension path: " + (-> (joyride/extension-context) + .-extension + .-extensionPath))) + (.appendLine (str "joyride/*file*: " joyride/*file*)) + (.appendLine (str "Invoked script: " (joyride/invoked-script))) + (.appendLine "🎉")) + +;; Try both invoking this file as a script, and loading it in the REPL \ No newline at end of file diff --git a/examples/.joyride/scripts/open_document.cljs b/examples/.joyride/scripts/open_document.cljs index 12cce79..a3afb6b 100644 --- a/examples/.joyride/scripts/open_document.cljs +++ b/examples/.joyride/scripts/open_document.cljs @@ -18,5 +18,5 @@ "No" :no :none))))) -(when (= (joyride/get-invoked-script) joyride/*file*) +(when (= (joyride/invoked-script) joyride/*file*) (my-main)) \ No newline at end of file diff --git a/examples/.joyride/scripts/terminal.cljs b/examples/.joyride/scripts/terminal.cljs index ebee1e3..dea63dc 100644 --- a/examples/.joyride/scripts/terminal.cljs +++ b/examples/.joyride/scripts/terminal.cljs @@ -13,7 +13,7 @@ ;; send an initial command to it (.sendText terminal "npx nbb"))) -(when (= (joyride/get-invoked-script) joyride/*file*) +(when (= (joyride/invoked-script) joyride/*file*) (main)) ;; see live demo here: diff --git a/examples/.joyride/scripts/webview/example.cljs b/examples/.joyride/scripts/webview/example.cljs index 077b589..e6220a8 100644 --- a/examples/.joyride/scripts/webview/example.cljs +++ b/examples/.joyride/scripts/webview/example.cljs @@ -16,7 +16,7 @@ html (.decode (js/TextDecoder. "utf-8") data)] (set! (.. panel -webview -html) (str html)))) -(when (= (joyride/get-invoked-script) joyride/*file*) +(when (= (joyride/invoked-script) joyride/*file*) (main)) ;; live demo here: https://twitter.com/borkdude/status/1519607386218053632 diff --git a/examples/README.md b/examples/README.md index 1d9dc1c..c63c458 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,7 +10,7 @@ In the `.joyride/scripts` folder you'll find mostly small examples: A Workspace [activate.cljs] script that registers a `vscode/workspace.onDidOpenTextDocument` event handler. Demonstrates: -* Using the `joyride.core/get-extension-context` to push disposables on its `subscriptions` array. Making VS Code dispose of them when Joyride is deactivated. +* Using the `joyride.core/extension-context` to push disposables on its `subscriptions` array. Making VS Code dispose of them when Joyride is deactivated. * A re-runnable recipe to avoid re-registering the event handler. (By disposing it and then re-register.) ## Create a Webview @@ -68,10 +68,14 @@ the default Windows comment keyboard shortcut. ## Joyride API -Yes, you can script Joyride with Joyride. See the [Joyride API docs](../doc/api.md) for more about this. +Joyride comes with the `joyride.core` namespace, giving you access to things as the extension context, the Joyride output channel, and some info about the evaluation environment. + +And, you can also script Joyride with Joyride using its Extension API. Example script: [`.joyride/scripts/joyride_api.cljs`](.joyride/scripts/joyride_api.cljs) +See also: the [Joyride API docs](../doc/api.md) + ## Opening a file The [open_document.cljs](.joyride/scripts/open_document.cljs) script asks if you want to open one of the examples and then opens a random `.cljs` file from the `scripts` folder. diff --git a/playground/hello-joyride/.joyride/scripts/hello.cljs b/playground/hello-joyride/.joyride/scripts/hello.cljs index 78943ea..6aacf8a 100644 --- a/playground/hello-joyride/.joyride/scripts/hello.cljs +++ b/playground/hello-joyride/.joyride/scripts/hello.cljs @@ -11,7 +11,7 @@ (comment j/*file* - (def ext-ctx (joyride/get-extension-context)) + (def ext-ctx (joyride/extension-context)) (.-extensionPath ext-ctx) ) diff --git a/src/main/joyride/db.cljs b/src/main/joyride/db.cljs index b002bde..b65fa2e 100644 --- a/src/main/joyride/db.cljs +++ b/src/main/joyride/db.cljs @@ -1,3 +1,26 @@ (ns joyride.db) -(defonce !app-db (atom {})) \ No newline at end of file +(def init-db {:output-channel nil + :extension-context nil + :invoked-script nil + :disposables []}) + +(defonce !app-db (atom init-db)) + +(defn extension-context + "Returns the Joyride ExtensionContext instance. + See: https://code.visualstudio.com/api/references/vscode-api#ExtensionContext" + [] + (:extension-context @!app-db)) + +(defn invoked-script + "Returns the absolute path of the invoked script when the evaluation is made + through *Run Script*, otherwise returns `nil`." + [] + (:invoked-script @!app-db)) + +(defn output-channel + "Returns the Joyride OutputChannel instance. + See: https://code.visualstudio.com/api/references/vscode-api#OutputChannel" + [] + (:output-channel @!app-db)) \ No newline at end of file diff --git a/src/main/joyride/extension.cljs b/src/main/joyride/extension.cljs index 026879b..6734db4 100644 --- a/src/main/joyride/extension.cljs +++ b/src/main/joyride/extension.cljs @@ -88,15 +88,16 @@ (def api (jsify {:startNReplServer start-nrepl-server+ :getContextValue (fn [k] - (when-contexts/get-context k))})) + (when-contexts/context k))})) (defn ^:export activate [^js context] (when context - (reset! db/!app-db {:output-channel (vscode/window.createOutputChannel "Joyride") - :extension-context context - :disposables []}) - (utils/say "🟢 Joyride VS Code with Clojure. 🚗") - (p/-> (life-cycle/maybe-run-init-script+ run-user-script+ + (swap! db/!app-db assoc + :output-channel (vscode/window.createOutputChannel "Joyride") + :extension-context context) + (binding [utils/*show-when-said?* true] + (utils/say "🟢 Joyride VS Code with Clojure. 🚗")) + (p/-> (life-cycle/maybe-run-init-script+ run-user-script+ (:user life-cycle/init-scripts)) (p/then (fn [_result] diff --git a/src/main/joyride/nrepl.cljs b/src/main/joyride/nrepl.cljs index bc8d116..d06d1d3 100644 --- a/src/main/joyride/nrepl.cljs +++ b/src/main/joyride/nrepl.cljs @@ -197,7 +197,7 @@ (vscode/workspace.fs.delete uri))))))) (defn server-running? [] - (when-contexts/get-context ::when-contexts/joyride.isNReplServerRunning)) + (when-contexts/context ::when-contexts/joyride.isNReplServerRunning)) (defn- start-server'+ "Start nRepl server. Accepts options either as JS object or Clojure map." diff --git a/src/main/joyride/sci.cljs b/src/main/joyride/sci.cljs index 6061a28..41ec7dc 100644 --- a/src/main/joyride/sci.cljs +++ b/src/main/joyride/sci.cljs @@ -13,54 +13,45 @@ (def joyride-ns (sci/create-ns 'joyride.core nil)) -(defn get-extension-context - "Returns the Joyride extension context object. - See: https://code.visualstudio.com/api/references/vscode-api#ExtensionContext" - [] - (:extension-context @db/!app-db)) - -(defn get-invoked-script - "Returns the absolute path of theinvoked script when the evaluation is made - through *Run Script*, otherwise returns `nil`." - [] - (:invoked-script @db/!app-db)) - (defn ns->path [namespace] (-> (str namespace) (munge) (str/replace "." "/") (str ".cljs"))) -(def !ctx (volatile! - (sci/init {:classes {'js goog/global - :allow :all} - :namespaces (assoc (:namespaces pconfig/config) - 'joyride.core {'*file* sci/file - 'get-extension-context (sci/copy-var get-extension-context joyride-ns) - 'get-invoked-script (sci/copy-var get-invoked-script joyride-ns)}) - :load-fn (fn [{:keys [namespace opts]}] - (cond - (symbol? namespace) - {:source - (let [path (ns->path namespace)] - (str - (fs/readFileSync - (path/join - (utils/workspace-root) - workspace-scripts-path - path))))} - (string? namespace) ;; node built-in or npm library - (if (= "vscode" namespace) - (do (sci/add-class! @!ctx 'vscode vscode) - (sci/add-import! @!ctx (symbol (str @sci/ns)) 'vscode (:as opts)) - {:handled true}) - (let [mod (js/require namespace) - ns-sym (symbol namespace)] - (sci/add-class! @!ctx ns-sym mod) - (sci/add-import! @!ctx (symbol (str @sci/ns)) ns-sym - (or (:as opts) - ns-sym)) - {:handled true}))))}))) +(def !ctx + (volatile! + (sci/init {:classes {'js goog/global + :allow :all} + :namespaces (assoc + (:namespaces pconfig/config) + 'joyride.core {'*file* sci/file + '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]}] + (cond + (symbol? namespace) + {:source + (let [path (ns->path namespace)] + (str + (fs/readFileSync + (path/join + (utils/workspace-root) + workspace-scripts-path + path))))} + (string? namespace) ;; node built-in or npm library + (if (= "vscode" namespace) + (do (sci/add-class! @!ctx 'vscode vscode) + (sci/add-import! @!ctx (symbol (str @sci/ns)) 'vscode (:as opts)) + {:handled true}) + (let [mod (js/require namespace) + ns-sym (symbol namespace)] + (sci/add-class! @!ctx ns-sym mod) + (sci/add-import! @!ctx (symbol (str @sci/ns)) ns-sym + (or (:as opts) + ns-sym)) + {:handled true}))))}))) (def !last-ns (volatile! @sci/ns)) diff --git a/src/main/joyride/utils.cljs b/src/main/joyride/utils.cljs index 86c273c..c28ba83 100644 --- a/src/main/joyride/utils.cljs +++ b/src/main/joyride/utils.cljs @@ -47,7 +47,7 @@ (def ^{:dynamic true :doc "Should the Joyride output channel be revealed after `say`? Default: `true`"} - *show-when-said?* true) + *show-when-said?* false) (defn say [message] (let [channel ^js (:output-channel @db/!app-db)] diff --git a/src/main/joyride/when_contexts.cljs b/src/main/joyride/when_contexts.cljs index ee64461..4939b71 100644 --- a/src/main/joyride/when_contexts.cljs +++ b/src/main/joyride/when_contexts.cljs @@ -8,10 +8,10 @@ (swap! !db assoc-in [:contexts k] v) (vscode/commands.executeCommand "setContext" (name k) v)) -(defn get-context [k] +(defn context [k] (get-in @!db [:contexts (if (string? k) (keyword (str "joyride.when-contexts/" k)) k)])) (comment - (get-context "joyride.isActive")) \ No newline at end of file + (context "joyride.isActive")) \ No newline at end of file