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.
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.
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)
Yes, exactly like that.
If the extensions load in the right order ;)
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?
I would guess so, but my guess is as good as yours :)
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)
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.
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. 😃
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 '' @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
Can you show an example of how to do it? Seems we should write something about it in the Joyride docs.
Yes:
(require '["ext..." :as ext])
(ext/foo)
but that was too long for youThat 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.
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?
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.
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)
I am talking about the cases when you do not hit a key.
Can you educate me more about what such a case looks like?
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 ;)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.
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
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.
Yes. Before we can do that we first have to find out best practices ourselves
Which is why we are having this conversation. 😃
What we could also do is have the ext:// loading thing attempt a few times before it says the extension isn't there. 🤷
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.
But there we will actually already need async loading (if ext://) is going to do this).
But yes, creating an async loop in user space can also work right now
I'll play some with it tomorrow.
Nice. Perhaps we could also have joyride.on-extension-loaded(fn [])
which will time out after some time
g'night
In joyride.core?
yeah, perhaps
It could back-off to something seldom that can go on without timing out completely maybe.
Good night to you as well, Michiel!
you mean, that, if the extension gets eventually loaded, it will still go off? hmyeah, could work
It also seems possible to force-activate the extension although that may be too invasive/unexpected
Depends on what the extension activates on.
If it is done in user space it won't be unexpected at least. 😃
If you explicitely activate it in userspace yes. If the require implicitely activates it, it may be unexpected
Indeed.
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 scriptNot that it matters, since it is arbitrary. 😃
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.
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.
🤔 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
Well, I was not aware of that one. Nice!
That's exactly what we want, right? At least for the user space experiments I was planning for, for tomorrow.
If explicit activation should happen, i guess yes
If we use this in the require code, we need just document that this will happen if you require an extension, I think.
At least one of the meanings of require allows for it. 😃
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
Yes, this will be possible with sci.async
❤️ sci.async ❤️
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?
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...
...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?
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.
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.