Skip to content

Commit

Permalink
Activation scripts (#35)
Browse files Browse the repository at this point in the history
* Add user and workspace activate script functionality
* Add workspace activate example

Fixes #8
  • Loading branch information
PEZ authored May 8, 2022
1 parent a28479e commit 93c1d91
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 55 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ See the [examples](./examples) for examples including:
* Terminal creation and control
* Fontsize manipulation
* Calva Structural Editing enhancements
* Opening and showing project files
* Workspace activation script

## Support and feedback

Expand Down
27 changes: 18 additions & 9 deletions doc/api.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
# APIs

There are two different APIs here:
The Joyride API consist of:

1. The Joyride *Scripting API*
* Scripting life-cycle management
* Included clojure library namespaces
1. The Joyride *Extension API*

Please note that Joyride's *Extension API* is also available to *Joyride scripts*.

## Scripting API

### Scripting life-cycle

You can make some code run when Joyride activates, by naming the scripts `activate.cljs`. The activations script will be run in the order:

1. `<User scripts directory>/activate.cljs`
1. `<Workspace scripts directory>/activate.cljs`

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

In addition to `clojure.core`, `clojure.set`, `clojure.edn`, `clojure.string`,
`clojure.walk`, `clojure.data`, Joyride exposes
the following namespaces:

## Joyride

### `joyride.core`
#### `joyride.core`

- `*file*`: dynamic var representing the currently executing file
- `get-extension-context`: function returning the Joyride [extension context](https://code.visualstudio.com/api/references/vscode-api#ExtensionContext) object
Expand All @@ -28,15 +39,13 @@ NB: While using `*file*` bare works, it will probably stop working soon. Always
...))
```

## Promesa
#### promesa.core

See [promesa docs](https://cljdoc.org/d/funcool/promesa/6.0.2/doc/user-guide).

### `promesa.core`

`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
### Possibly coming additions

We want to add `clojure.test` and `clojure.pprint` as well in the near future. How near/if depends on things like how much time we can spend on it, and how easy/hard it will be to port this over from [nbb](https://github.com/babashka/nbb).

Expand Down
41 changes: 41 additions & 0 deletions examples/.joyride/scripts/activate.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
(ns activate
(:require [joyride.core :as joyride]
["vscode" :as vscode]
[promesa.core :as p]))

(println "Hello World, from Workspace activate.cljs script")

(defonce !db (atom {:disposables []}))

;; To make the activation script re-runnable we dispose of
;; event handlers and such that we might have registered
;; in previous runs.
(defn- clear-disposables! []
(run! (fn [disposable]
(.dispose disposable))
(:disposables @!db))
(swap! !db assoc :disposables []))

;; Pushing the disposables on the extension context's
;; subscriptions will make VS Code dispose of them when the
;; Joyride extension is deactivated.
(defn- push-disposable [disposable]
(swap! !db update :disposables conj disposable)
(-> (joyride/get-extension-context)
.-subscriptions
(.push disposable)))

(defn- my-main []
(clear-disposables!)
(push-disposable
;; It might surprise you to see how often and when this happens,
;; and when it doesn't happen.
(vscode/workspace.onDidOpenTextDocument
(fn [doc]
(println "[Joyride example]"
(.-languageId doc)
"document opened:"
(.-fileName doc))))))

(when true
(my-main))
4 changes: 2 additions & 2 deletions examples/.joyride/scripts/joyride_api.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
;; 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)
.-extension
.-exports)
(require '[clojure.repl :refer [doc]])
(doc joyride/get-extension-context)
)
)
7 changes: 7 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ Demonstrating some ways to [Joyride VS Code](https://marketplace.visualstudio.co

In the `.joyride/scripts` folder you'll find mostly small examples:

## Activation script

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.
* A re-runnable recipe to avoid re-registering the event handler. (By disposing it and then re-register.)

## Create a Webview

`webview/example.cljs`
Expand Down
3 changes: 3 additions & 0 deletions src/main/joyride/db.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(ns joyride.db)

(defonce !app-db (atom {}))
58 changes: 19 additions & 39 deletions src/main/joyride/extension.cljs
Original file line number Diff line number Diff line change
@@ -1,52 +1,27 @@
(ns joyride.extension
(:require ["path" :as path]
["vscode" :as vscode]
[clojure.pprint :as pprint]
[joyride.config :as conf]
[joyride.when-contexts :as when-contexts]
[joyride.db :as db]
[joyride.life-cycle :as life-cycle]
[joyride.nrepl :as nrepl]
[joyride.sci :as jsci]
[joyride.scripts-menu :refer [show-script-picker+]]
[joyride.utils :refer [info vscode-read-uri+ jsify]]
[joyride.utils :as utils :refer [info jsify vscode-read-uri+]]
[joyride.when-contexts :as when-contexts]
[promesa.core :as p]
[sci.core :as sci]))

(defonce !db (atom {}))

(defn- register-command! [^js context command-id var]
(let [disposable (vscode/commands.registerCommand command-id var)]
(swap! !db update :disposables conj disposable)
(swap! db/!app-db update :disposables conj disposable)
(.push (.-subscriptions context) disposable)))

(defn- clear-disposables! []
(swap! !db assoc :disposables [])
(swap! db/!app-db assoc :disposables [])
(p/run! (fn [^js disposable]
(.dispose disposable))
(:disposables @!db)))

(def ^{:dynamic true
:doc "Should the Joyride output channel be revealed after `say`?
Default: `true`"}
*show-when-said?* true)

(defn say [message]
(let [channel ^js (:output-channel @!db)]
(.appendLine channel message)
(when *show-when-said?*
(.show channel true))))

(defn say-error [message]
(say (str "ERROR: " message)))

(defn say-result
([result]
(say-result nil result))
([message result]
(let [prefix (if (empty? message)
"=> "
(str message "\n=> "))]
(.append ^js (:output-channel @!db) prefix)
(say (with-out-str (pprint/pprint result))))))
(:disposables @db/!app-db)))

(defn run-code
([]
Expand All @@ -58,7 +33,7 @@
(run-code input))))
([code]
(p/let [result (jsci/eval-string code)]
(say-result result))))
(utils/say-result result))))

(defn choose-file [default-uri]
(vscode/window.showOpenDialog #js {:canSelectMany false
Expand All @@ -81,9 +56,9 @@
(p/handle (fn [result error]
(if error
(do
(say-error (str title " Failed: " script-path " " (.-message error)))
(utils/say-error (str title " Failed: " script-path " " (.-message error)))
(js/console.error title "Failed: " script-path (.-message error) error))
(do (say-result (str script-path " evaluated.") result)
(do (utils/say-result (str script-path " evaluated.") result)
result)))))))

(def run-workspace-script-args ["Run Workspace Script"
Expand Down Expand Up @@ -115,12 +90,17 @@

(defn ^:export activate [^js context]
(when context
(reset! !db {:output-channel (vscode/window.createOutputChannel "Joyride")
(reset! db/!app-db {:output-channel (vscode/window.createOutputChannel "Joyride")
:extension-context context
:disposables []})
(vreset! jsci/!extension-context context)
(say "🟢 Joyride VS Code with Clojure. 🚗"))
(let [{:keys [extension-context]} @!db]
(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]
(life-cycle/maybe-run-init-script+ run-workspace-script+
(:workspace life-cycle/init-scripts))))))
(let [{:keys [extension-context]} @db/!app-db]
(register-command! extension-context "joyride.runCode" #'run-code)
(register-command! extension-context "joyride.runWorkspaceScript" #'run-workspace-script+)
(register-command! extension-context "joyride.runUserScript" #'run-user-script+)
Expand Down
36 changes: 36 additions & 0 deletions src/main/joyride/life_cycle.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
(ns joyride.life-cycle
(:require ["path" :as path]
["vscode" :as vscode]
[joyride.config :as conf]
[joyride.utils :as utils]
[promesa.core :as p]))

(def user-init-script "activate.cljs")
(def user-init-script-path (path/join conf/user-config-path
conf/user-scripts-path
user-init-script))

(def workspace-init-script "activate.cljs")
(def workspace-init-script-path (path/join conf/workspace-scripts-path
workspace-init-script))
(def workspace-init-script-abs-path (path/join vscode/workspace.rootPath
workspace-init-script-path))

(def init-scripts {:user {:label "User activate"
:script user-init-script
:script-path user-init-script-path
:script-abs-path user-init-script-path}
:workspace {:label "Workspace activate"
:script user-init-script
:script-path workspace-init-script-path
:script-abs-path workspace-init-script-abs-path}})

(defn maybe-run-init-script+ [run-fn {:keys [label script script-path script-abs-path]}]
(utils/say (str label " script: " script-path))
(-> (utils/path-exists?+ script-abs-path)
(p/then (fn [exists?]
(if exists?
(do
(utils/say (str " Running..."))
(run-fn script))
(utils/say (str " No " label " script present")))))))
5 changes: 2 additions & 3 deletions src/main/joyride/sci.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
["path" :as path]
["vscode" :as vscode]
[clojure.string :as str]
[joyride.db :as db]
[joyride.config :refer [workspace-scripts-path]]
[joyride.utils :as utils]
[sci-configs.funcool.promesa :as pconfig]
[sci.core :as sci]))

(sci/alter-var-root sci/print-fn (constantly *print-fn*))

(def !extension-context (volatile! nil))

(defn get-extension-context
"Returns the Joyride extension context object.
See: https://code.visualstudio.com/api/references/vscode-api#ExtensionContext"
[]
@!extension-context)
(:extension-context @db/!app-db))

(def joyride-ns (sci/create-ns 'joyride.core nil))

Expand Down
38 changes: 36 additions & 2 deletions src/main/joyride/utils.cljs
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
(ns joyride.utils
(:require ["vscode" :as vscode]
[promesa.core :as p]
[clojure.string :as str]))
[clojure.pprint :as pprint]
[clojure.string :as str]
[joyride.db :as db]
[promesa.core :as p]))

(defn jsify [clj-thing]
(clj->js clj-thing))

(defn cljify [js-thing]
(js->clj js-thing :keywordize-keys true))

(defn path-exists?+ [path]
(-> (p/let [uri (vscode/Uri.file path)]
(vscode/workspace.fs.stat uri)
true)
(p/catch
(fn [_e]
false))))

(defn vscode-read-uri+ [^js uri-or-path]
(let [uri (if (string? uri-or-path)
(vscode/Uri.file uri-or-path)
Expand All @@ -33,3 +43,27 @@

(defn error [& xs]
(vscode/window.showErrorMessage (str/join " " (mapv str xs))))

(def ^{:dynamic true
:doc "Should the Joyride output channel be revealed after `say`?
Default: `true`"}
*show-when-said?* true)

(defn say [message]
(let [channel ^js (:output-channel @db/!app-db)]
(.appendLine channel message)
(when *show-when-said?*
(.show channel true))))

(defn say-error [message]
(say (str "ERROR: " message)))

(defn say-result
([result]
(say-result nil result))
([message result]
(let [prefix (if (empty? message)
"=> "
(str message "\n=> "))]
(.append ^js (:output-channel @db/!app-db) prefix)
(say (with-out-str (pprint/pprint result))))))

0 comments on commit 93c1d91

Please sign in to comment.