joyride

pez 2022-05-31T20:51:12.172009Z

Regarding require of VS Code extension APIs. Although Calva seems to win consistently over Joyride and be there for the joyride activation scripts*, Calva does not always automatically activate. If there is no Clojure-file in the folder opened (or no folder) Calva will not activate. And many extensions activate on some command(s) of theirs. I think this means we will have to figure some lazy loading out, or at least figure out what the implications are and what the limitations will be. (*) I've just released versions of Joyride and Calva that logs at the beginning and end of their activation functions to help in figuring out how reliable this is.

pez 2022-06-01T07:04:02.918069Z

In a future where we asynchronously activate and load Calva at require, your keybindings will start working once Calva is activated, but not before that. You can add the context calva:connected to the when clause of the binding to avoid even calling that shortcut in the case it requires a connected REPL. We can also add a context for checking if calva is activated.

seancorfield 2022-06-01T16:48:37.542109Z

And if you open a new window, all the relevant extensions activate for that window, yes? So even if you opened, say, a TS project first (and your Joyride activate.cljs failed because it tried to reference Calva), and then, later on, opened a new window for a Clojure project, Joyride would activate and run activate.cljs for that new window (and have Calva available)? (just trying to clarify my mental model of how VS Code extensions work with multiple windows)

pez 2022-06-01T17:42:15.250629Z

Yes, exactly like that.

borkdude 2022-06-01T17:43:51.859019Z

If the extensions load in the right order ;)

seancorfield 2022-06-01T19:20:06.574749Z

And, in light of the latest user_activate.cljs code, if you call (.activate) on an extension that is already activated, it's a safe no-op?

borkdude 2022-06-01T19:20:48.058919Z

I would guess so, but my guess is as good as yours :)

alpox 2022-06-01T19:46:44.197169Z

This seems sadly not documented. I could find this though: https://github.com/microsoft/vscode/blob/d9373893583a1b78df346f4671bc24221d5e5aba/src/vs/workbench/api/common/extHostExtensionActivator.ts#L236 So it appears it should be a safe no-op. (This method is executed when .activate is called from the api)

pez 2022-06-01T19:55:55.745419Z

Yes, it should be safe. You can check the console for the output that Calva and Joyride and the activation script produces, and you will see that there is never any double activation of Calva. In fact the VS Code API docs are very specific about that the activate function will be called at the very most once in the life time of an extension.

pez 2022-06-01T19:59:42.816519Z

The price we pay is that we activate Calva regardless if it is a Clojure project or not. But I think that should be fine for most Clojure devs. At least for me there is always some reason I need to compute something and very seldom does a session end w/o some Clojure REPL being started. 😃

pez 2022-05-31T21:02:25.349169Z

And now this happened for the first time on my machine:

User activate script: /Users/pez/.config/joyride/scripts/activate.cljs.  Running...
ERROR: Run Failed: activate.cljs Cannot find module ''

borkdude 2022-05-31T21:03:11.837999Z

@pez Re lazy loading, I already mentioned that you can just-in-time require namespaces as apposed to early-requiring them in the activations script. That seems the best solution to me

pez 2022-05-31T21:04:34.879469Z

Can you show an example of how to do it? Seems we should write something about it in the Joyride docs.

borkdude 2022-05-31T21:05:38.284599Z

Yes:

(require '["ext..." :as ext])
(ext/foo)
but that was too long for you

pez 2022-05-31T21:07:27.203329Z

That was only for the keyboard shortcuts bindings. I wonder what to do when the activation scripts wants to automate something about an extension, but Joyride happens to initialize before the extension's API is available.

borkdude 2022-05-31T21:08:57.547029Z

I asked about what the use case for activation scripts was. The only answer I got was early require so keyboard shortcuts can be shorter. I replied with: ok, that can bite you as extension order probably matter. Can you elaborate on another use case for activation scripts? What do you mean with "automate something about an extension"? Why does that need to be loaded on activation?

pez 2022-05-31T21:12:47.012149Z

We spoke past each other (as we say in Swedish). One use case is automatically connecting Calva to a REPL when a folder is opened. I think there are an infinite amount use cases. I might want to install providers for code lenses, symbols, definitions, anything using the powers of some extensions.

borkdude 2022-05-31T21:14:26.875379Z

My (recurring) point is that those namespace probably only have to be loaded once you actually do something in the context of those extensions. E.g. if you want to do something Calva-ish, you probably already are in a Calva project, then hit a key, and them boom, the relevant code gets required (on first use)

pez 2022-05-31T21:15:27.148989Z

I am talking about the cases when you do not hit a key.

borkdude 2022-05-31T21:15:39.006209Z

Can you educate me more about what such a case looks like?

borkdude 2022-05-31T21:19:26.177449Z

Regardless, to make things lazy, there is a way. But we're going to move to async eval soon (I hope after ClojureD, that will keep me occupied until then).

(def calva (delay (require '["...])))
(defn foo [] 
  (p/let [repl (.-repl @calva)] 
    ...))
A bit annoying though ;)

pez 2022-05-31T21:19:30.061249Z

Contrived: I might want to install a code lens provider that shows me when there is a github issue on some line in the files I am working with. Some VS Code extension might have the functionality for telling me about this for a file. The activate.cljs script is the place for initializing this.

borkdude 2022-05-31T21:20:23.245969Z

Right. If VSCode had a hook to listen for extensions to become ready or so , or "all extensions loaded" hook, then we could solve this more reliably

pez 2022-05-31T21:20:52.658269Z

I can stand the annoyance there with the delayed require. To me it is more that I want to tell Joyride users about ways to solve things.

borkdude 2022-05-31T21:21:21.873799Z

Yes. Before we can do that we first have to find out best practices ourselves

pez 2022-05-31T21:21:54.722449Z

Which is why we are having this conversation. 😃

😆 1
borkdude 2022-05-31T21:24:51.603019Z

What we could also do is have the ext:// loading thing attempt a few times before it says the extension isn't there. 🤷

pez 2022-05-31T21:24:59.875009Z

In lack of support from VS Code to tell us when an extension is available. The user can create a watcher that polls extensions of interest at some intervals until they become available. When the watcher for some extension triggers, the extension can be required and code that relies on it can be run.

borkdude 2022-05-31T21:26:29.774249Z

But there we will actually already need async loading (if ext://) is going to do this).

borkdude 2022-05-31T21:26:44.107719Z

But yes, creating an async loop in user space can also work right now

pez 2022-05-31T21:27:29.671169Z

I'll play some with it tomorrow.

borkdude 2022-05-31T21:28:50.091079Z

Nice. Perhaps we could also have joyride.on-extension-loaded(fn [])

borkdude 2022-05-31T21:29:04.479329Z

which will time out after some time

borkdude 2022-05-31T21:29:25.599019Z

g'night

pez 2022-05-31T21:29:36.827529Z

In joyride.core?

borkdude 2022-05-31T21:29:44.689859Z

yeah, perhaps

pez 2022-05-31T21:30:33.482919Z

It could back-off to something seldom that can go on without timing out completely maybe.

pez 2022-05-31T21:31:12.165549Z

Good night to you as well, Michiel!

borkdude 2022-05-31T21:31:14.109929Z

you mean, that, if the extension gets eventually loaded, it will still go off? hmyeah, could work

alpox 2022-05-31T21:31:31.557829Z

It also seems possible to force-activate the extension although that may be too invasive/unexpected

pez 2022-05-31T21:31:57.183369Z

Depends on what the extension activates on.

pez 2022-05-31T21:32:37.067529Z

If it is done in user space it won't be unexpected at least. 😃

alpox 2022-05-31T21:34:49.132119Z

If you explicitely activate it in userspace yes. If the require implicitely activates it, it may be unexpected

pez 2022-05-31T21:35:25.395239Z

Indeed.

pez 2022-05-31T21:36:29.020259Z

Got this activation order pretty consistently a few times now, reloading the VS Code window:

console.ts:137 [Extension Host] Calva activate START
console.ts:137 [Extension Host] Joyride activate START
console.ts:137 [Extension Host] Joyride activate END
console.ts:137 [Extension Host] Calva activate END
console.ts:137 [Extension Host] Joyride user activate.cljs starting.
console.ts:137 [Extension Host] Hello World, from my-main in user activate.cljs script
console.ts:137 [Extension Host] Starting clojure-lsp at /Users/pez/.vscode/extensions/betterthantomorrow.calva-2.0.280/clojure-lsp
console.ts:137 [Extension Host] Hello World, from Workspace activate.cljs script

pez 2022-05-31T21:37:04.160859Z

Not that it matters, since it is arbitrary. 😃

alpox 2022-05-31T21:40:16.700159Z

I believe getting calva to activate before joyride is probably possible by listing it as extensionDependency. But one may not always want to require calva and it wouldnt handle other extensions 🤔 I guess asynchronous requires are the only way then. Even with forced activation we get a promise.

pez 2022-05-31T21:40:34.719019Z

Since it will depend on the extension what you can force activate it with, it will have to be done in user space. Studying the extension manifest and figuring what would be a good way to activate it and such.

alpox 2022-05-31T21:43:33.718749Z

🤔 I dont know the API as well as you do, I only saw the activate method at https://code.visualstudio.com/api/references/vscode-api#Extension

pez 2022-05-31T21:45:23.106869Z

Well, I was not aware of that one. Nice!

pez 2022-05-31T21:46:50.990419Z

That's exactly what we want, right? At least for the user space experiments I was planning for, for tomorrow.

alpox 2022-05-31T21:49:26.257659Z

If explicit activation should happen, i guess yes

pez 2022-05-31T21:50:51.430349Z

If we use this in the require code, we need just document that this will happen if you require an extension, I think.

pez 2022-05-31T21:51:16.767739Z

At least one of the meanings of require allows for it. 😃

alpox 2022-05-31T21:51:38.406049Z

For using that in the require code it would have to be synchronous, no? As far as I see it returns a Thenable - a Promise though

borkdude 2022-05-31T21:53:34.921889Z

Yes, this will be possible with sci.async

🎉 1
pez 2022-05-31T21:55:01.116349Z

❤️ sci.async ❤️

seancorfield 2022-06-01T00:01:47.164279Z

Just catching up on this... so this is only an issue if someone's activate.cljs -- either user-level or project-level -- tries to do something directly with Calva? So what happens if I fire up VS Code in a non-Clojure project and Joyride activates and loads and tries to run the user-level activate.cljs and it tries to depend on Calva -- will it hang? Fail? still run the non-Calva activation code?

seancorfield 2022-06-01T00:04:14.052409Z

The scenario I'm thinking of here is that someone writes a library for Joyride/Calva and I "install" that by requiring it in my user-level activate.cljs script (to make those keybindings shorter!), and then I startup VS Code to work on non-Clojure code...

seancorfield 2022-06-01T00:06:39.049259Z

...and if that asynchronously tries to run the code but gives up after a while... and then I open a Clojure file... would that still trigger Calva's activation and leave the keybindings non-functional since Joyride didn't require that library namespace?

pez 2022-06-01T06:49:13.411809Z

The main mechanisms involved here are: • VS Code calls an extension's activate() function when activating the extension. • a VS Code extension returns it's API as an object from its activate() function • There are some different triggers that an extension can request to activate it. • VS Code will open a new extension host for each window opened. At require Joyride will ask for the API object of an extension, if it is presently installed and activated. Failing that the load of the file will croak and nothing more will happen for that file. The only feedback you will get about it is a message in the Joyride output channel + what you might have added to some error handler wrapping the require. Running/loading the script again, once the extension is activated will work.

pez 2022-06-01T06:57:52.251549Z

Another part of the scenario you present, @seancorfield, is that Calva will activate if there is some Clojure project file in the folder opened in the VS Code window, or Clojure file opened or some key Calva commands are issued:

"activationEvents": [
    "onLanguage:clojure",
    "onCommand:calva.startStandaloneRepl",
    "onCommand:calva.startStandaloneHelloRepl",
    "onCommand:calva.startStandaloneCljsBrowserRepl",
    "onCommand:calva.startStandaloneCljsNodeRepl",
    "onCommand:calva.calva.startJoyrideReplAndConnect",
    "onCommand:calva.jackIn",
    "onCommand:calva.startOrConnectRepl",
    "onCommand:calva.connect",
    "onCommand:calva.connectNonProjectREPL",
    "onCommand:calva.convertJs2Cljs",
    "workspaceContains:**/project.clj",
    "workspaceContains:**/shadow-cljs.edn",
    "workspaceContains:**/deps.edn",
    "onDebugResolve:type"
  ],
This all sums up to that starting with some non-Clojure code work in a window containing one or more Clojure projects, will still activate Calva. Only if there is nothing like that in the folder will Calva remain inactive. Opening a Clojure file in that window will activate Calva. All the while Calva was inactive any code assuming that Calva is required/loaded, will fail.