On the back of my blog post about a simple dev inner loops, I decided I need an extension that would open the internal browser in VS Code when my project opened, or my http server started. Seems simple right?
- Wait for task to run
- Call command to open browser
- Profit
Well, in this endeavour I’ve been down multiple rabbit holes, and I’m both struck by the time it takes to make good software and the complexity of even the simplest idea. Its demoralising when you look over the precipice, and the abyss stares back.
I tweeted about it:
Software development can be incredibly demoralising. You have an idea. On the surface it seems simple. As you get to the details distinct from the idea itself (ecosystem & infra), you’re dragged into a maze resembling an Escher drawing.
Brought to you by VS Code’s extension API.
It’s not that it’s overwhelming, or discouraging (ok, maybe a little demoralising), but it does take some of your energy, and wonder away.
What follows is effectively a rant. Sorry.
Demoralisation, Step By Step
- If someone doesn’t configure a URL to open, you need to present a message. I can direct them to the right part of settings with an explicit command. But it’s not explicitly documented, so I had to search the VS Code implementation to reverse engineer the parameters
- Additionally, it seems that button handling of alert-type message boxes is done by getting a promise that completes with the displayed button text. Not an abstract ID, but the actual user text. I’m assuming there is a better option here, but it’s not immediately obvious, and I don’t need another rabbit hole stopping me from getting to my actual idea
- How would I know the task being executed is the one you want to match for? We don’t want to open it on every task that starts. So the user needs to define a set of criteria. But the task declaration is v. rich, and varies by task type (i.e. there is no single schema). But the API to inspect tasks doesn’t present in the same structure as the declaration, so it’s messy. Came up with
_.isMatch
to get me close (Task added: “Command to help dump task information in a format people can just paste into the config”) - What if the task has already started? Ok, enumerate executing tasks. 👍️ But what if it hasn’t started? Well, now I need to ‘wait’ for that if it happens. Need to only do one of those, since I don’t want to open it every time the task executes. Or do I? Depends on the user! (Task added: “Allow configuration of opening triggers”)
- But how do you know it’s started? In the extension itself the code, thats OK since I don’t really care when it’s started there. But, for testing, I needed to wait for the service to start up to make sure it really started — but the task starting API completes when the process starts, not when it’s ready. So I have to poll the http service… which also takes a while to shutdown, and
terminate
doesn’t return a promise, so doesn’t let me await the shutdown. Suppose I’d better poll again!- Thanks to a friends insightful question, I realise for termination I can listen to some more events to see when the task process terminates. (Task added: “Update tests to monitor task for ending”)
- But how do you know it’s started? In the extension itself the code, thats OK since I don’t really care when it’s started there. But, for testing, I needed to wait for the service to start up to make sure it really started — but the task starting API completes when the process starts, not when it’s ready. So I have to poll the http service… which also takes a while to shutdown, and
- For testing, I would like a known state for my ‘sample’ workspace. Except that there isn’t a clear way to run ‘setup’ scaffolding before each test because VS Code testing doesn’t support reloading the project while the tests are running. So instead of ‘reset to known state’ or ‘copy project in known state’, I’m writing clearing of all settings for my extension and a helper to explicitly set them to a known state. This also means I refactor 99% of my extension class out of ‘extension’ into something I can drive from the tests
- This is mostly due to different configuration options I need / would like to test. Maybe I don’t need to test those, but I don’t want to manual test a bunch of scenarios
- I might be able to support more complex / multiple sample workspaces when the tests are run from the command line tools (Task Added: “Check command line test execution passes”, and “Add multi-root workspace tests to command line”
- VS Code supports opening multiple folders in a single window (“multi root workspaces”). These look to the user like multiple projects in a single window. Yay! Except they don’t merge the config (for obv. reasons) from across the folders. Which means the extension itself now has to work out which root folder is actually the intended target
- You can derive it from the active editor, ’cause you can pass the URI of the document to the configuration API
- But what if the task is executing from a workspace that isn’t the active editor? Well, thankfully, the task API does define scope. But now I need to kinda smuggle that around. Not so bad, but you know “eh”.
- What if there is no active editor and the user invokes the command manually? Well, theres another API that shows a workspace picker async API that will let you get that scope
- Should I rely on the active editor and use that, or if you’re in a multi-root workspace always prompt if manually invoked? No ‘cause that doesn’t help disambiguate between workspace setting and per-folder setting, which might both be set
- I can inspect a setting with
inspect
, and determine if it’s set on a folder, or workspace- If I iterate over all the workspaces and only show the prompt if there is more than one configuration for my extension, that seems good, right?
- Tasks can be defined globally, but I don’t have a global setting. So if the source is global, I need to… decide which workspace to get it from? Can I show that picker when it wasn’t user invoked? Thats a problem for another day
- This feels useful in a web instance of VS Code, like github.dev. Turns out you gotta bundle a bunch of your dependencies into a single file, and can’t rely on Node APIs — for obvious reasons. Now you need to have a separate build pipeline to pull in different depend. I think? Can I constrain my API to only use web-available, or shimmed? This is a problem for another day.
All I wanted was have a browser tab open in VS Code when my project opens! I think I’m finally at the point where I can get to the real meat of monitoring tasks. I think.