|
| 1 | +# Interactive introduction to the codebase |
| 2 | + |
| 3 | +This is a technical deep dive into how OpenVSCode Server turns VS Code into a web IDE, with interactive code search queries and snippets. This is [best viewed on Sourcegraph](https://sourcegraph.com/github.com/sourcegraph/openvscode-server/-/blob/doc/sourcedive.snb.md). |
| 4 | + |
| 5 | +The code snippets in this file correspond to search queries and can be displayed by clicking the blue "Run search" button to the right of each query. For example, here is a snippet that shows off an instance of dependency injection within VS Code: |
| 6 | + |
| 7 | +```sourcegraph |
| 8 | +patterntype:structural repo:^github\.com/gitpod-io/openvscode-server$@5c8a1f file:^src/vs/code/browser/workbench/workbench\.ts create(document.body, {:[1]}) |
| 9 | +``` |
| 10 | + |
| 11 | +## Architectural overview |
| 12 | + |
| 13 | +OpenVSCode Server is a fork of VS Code that extends the editor to be runnable in the browser, speaking to a web server that provides a remote dev environment. |
| 14 | + |
| 15 | +Upstream VS Code consists of [layers](https://github.com/microsoft/vscode/wiki/Source-Code-Organization): |
| 16 | + |
| 17 | +* `base`: Provides general utilities and user interface building blocks. |
| 18 | +* `platform`: Defines service injection support and the base services for VS Code. |
| 19 | +* `editor`: The "Monaco" editor is available as a separate downloadable component. |
| 20 | +* `workbench`: Hosts the "Monaco" editor and provides the framework for "viewlets" like the Explorer, Status Bar, or Menu Bar, leveraging Electron to implement the VS Code desktop application. |
| 21 | +* `code`: The entry point to the desktop app that stitches everything together, this includes the Electron main file and the CLI for example. |
| 22 | + |
| 23 | +OpenVSCode Server adds an additional [`server` layer](https://github.com/gitpod-io/openvscode-server/tree/main/src/vs/server). The client side remains largely unchanged, save for the injection of RPC-based handlers for things like filesystem and terminal interactions, in place of local handlers. The `server` layer has 3 main components: |
| 24 | + |
| 25 | +* Web-based workbench |
| 26 | +* Remote server |
| 27 | +* Remote CLI |
| 28 | + |
| 29 | +The web-based workbench lives on the client side and is the place where the RPC-based dependencies are injected. VS Code's codebase is modular and makes heavy use of dependency injection, which makes it easier to substitute different implementations. The entrypoint into the web-based workbench is in [workbench.ts](https://sourcegraph.com/github.com/gitpod-io/openvscode-server/-/blob/src/vs/code/browser/workbench/workbench.ts). In that file, the `create` function creates the workbench using dependency injection: |
| 30 | + |
| 31 | +```sourcegraph |
| 32 | +patterntype:structural repo:/gitpod-io/openvscode-server$@5c8a1f fork:yes file:server/browser/workbench/workbench.ts create(document.body, :[2]) |
| 33 | +``` |
| 34 | + |
| 35 | +The workbench talks to the remote server via RPC. There are 2 RPC channels: |
| 36 | + |
| 37 | +* The management connection handles filesystem and terminal requests |
| 38 | +* The extension connection creates the remote extension host process and handles extension-related requests |
| 39 | + |
| 40 | +Let's take a look at how those connections are set up on the server side. The main entrypoint into the server lives in [`server.main.ts`](https://sourcegraph.com/github.com/gitpod-io/openvscode-server@5c8a1f/-/blob/src/vs/server/node/server.main.ts?L11): |
| 41 | + |
| 42 | +```sourcegraph |
| 43 | +repo:^github\.com/gitpod-io/openvscode-server$@5c8a1f file:^src/vs/server/node/server\.main\.ts export async function main |
| 44 | +``` |
| 45 | + |
| 46 | +Of particular note in the `main` function is `channelServer`, which registers different service channels for handling different types of requests received from the client, such as logging, debugging, and filesystem requests. |
| 47 | + |
| 48 | + |
| 49 | +```sourcegraph |
| 50 | +repo:^github\.com/gitpod-io/openvscode-server$@5c8a1f file:^src/vs/server/node/server\.main\.ts const channelServer = |
| 51 | +``` |
| 52 | + |
| 53 | +These channels then relay the requests to the appropriate service implementations. Lower in the `main` function, a `ServiceCollection` instance is used as a dependency injection container that holds all the concrete implementations of the various service types: |
| 54 | + |
| 55 | +```sourcegraph |
| 56 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 file:^src/vs/server/node/server\.main\.ts Const services = new ServiceCollection() |
| 57 | +``` |
| 58 | + |
| 59 | +Then the whole bundle is wrapped by an HTTP server, which is the outermost container that handles requests from the client: |
| 60 | + |
| 61 | +```sourcegraph |
| 62 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 file:^src/vs/server/node/server\.main\.ts const server = http.createServer |
| 63 | +``` |
| 64 | + |
| 65 | +Some of the handler endpoints correspond to static resources: |
| 66 | + |
| 67 | +```sourcegraph |
| 68 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 file:^src/vs/server/node/server\.main\.ts if (pathname === '/vscode-remote-resource') |
| 69 | +``` |
| 70 | + |
| 71 | +Then there are the endpoints that upgrade a HTTP request to a WebSocket connection: |
| 72 | + |
| 73 | +```sourcegraph |
| 74 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 file:^src/vs/server/node/server\.main\.ts server.on('upgrade', |
| 75 | +``` |
| 76 | + |
| 77 | +The aforementioned management connection and extension connection use these WebSocket connections. The management connection connects `channelServer` with your editor window, including requests for handling terminal and fileystem requests. |
| 78 | + |
| 79 | +On the extension side, there's a special protocol over WebSocket that initiates a handshake to set up the remote extension host process: |
| 80 | + |
| 81 | +```sourcegraph |
| 82 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 file:^src/vs/server/node/server\.main\.ts const controlListener = protocol.onControlMessage |
| 83 | +``` |
| 84 | + |
| 85 | +The extension connection will fork the extension host process and connect the user's editor window. Among other things, keystrokes are sent down this connection, as they may be relevant to extensions in use. |
| 86 | + |
| 87 | + |
| 88 | +## Startup |
| 89 | + |
| 90 | +Now, let's walk through what happens at startup. |
| 91 | + |
| 92 | +On server startup, first we create the channel server and register channels to handle RPC calls and events from the web workbench: |
| 93 | + |
| 94 | +```sourcegraph |
| 95 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 const channelServer = new IPCServer |
| 96 | +``` |
| 97 | + |
| 98 | + |
| 99 | +Then we create the service collection (effectively the dependency injection container for service implementations, as described earlier) with all services required for the RPC channels: |
| 100 | + |
| 101 | +```sourcegraph |
| 102 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 file:^src/vs/server/node/server\.main\.ts services.set(IRawURITransformerFactory, rawURITransformerFactory) |
| 103 | +``` |
| 104 | + |
| 105 | +Then we instantiate these services and start the HTTP server: |
| 106 | + |
| 107 | +```sourcegraph |
| 108 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 file:^src/vs/server/node/server\.main\.ts const clients = new Map<string, Client>() |
| 109 | +``` |
| 110 | + |
| 111 | +When a user tries to access the server, the web workbench is served by HTTP listener: |
| 112 | + |
| 113 | +```sourcegraph |
| 114 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 file:^src/vs/server/node/server\.main\.ts return handleRoot(req, res, devMode ? options.mainDev || |
| 115 | +``` |
| 116 | + |
| 117 | +The web workbench first loads 3rd party dependencies like xterm: |
| 118 | + |
| 119 | +```sourcegraph |
| 120 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 xterm': `${window.location.origin} file:workbench-dev.html |
| 121 | +``` |
| 122 | + |
| 123 | +...and then itself: |
| 124 | + |
| 125 | +```sourcegraph |
| 126 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 require(['vs/server/browser/workbench/workbench'], |
| 127 | +``` |
| 128 | + |
| 129 | +The web workbench uses dependency injection to configure how to establish WebSockets, load static resources, load webviews, and so on: |
| 130 | + |
| 131 | +```sourcegraph |
| 132 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 create(document.body, { file:src/vs/server/ |
| 133 | +``` |
| 134 | + |
| 135 | +When the web workbench is created, it opens WebSocket connections to the server: |
| 136 | + |
| 137 | +```sourcegraph |
| 138 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 if (req.headers['upgrade'] !== 'websocket' || !req.url) |
| 139 | +``` |
| 140 | + |
| 141 | +There is one connection to the RPC channel server to notify about a new client: |
| 142 | + |
| 143 | +```sourcegraph |
| 144 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 onDidClientConnectEmitter.fire({ protocol, onDidClientDisconnect: onDidClientDisconnectEmitter.event }) |
| 145 | +``` |
| 146 | + |
| 147 | +...and another for the extension host process which is running remote extensions: |
| 148 | + |
| 149 | +```sourcegraph |
| 150 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 const extensionHost = cp.fork(FileAccess.asFileUri |
| 151 | +``` |
| 152 | + |
| 153 | +When a user creates a new terminal the management connection is used to call the remote terminal channel: |
| 154 | + |
| 155 | +```sourcegraph |
| 156 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 return this.createProcess(context.remoteAuthority, args); |
| 157 | +``` |
| 158 | + |
| 159 | +...which delegates to pseudo terminal service: |
| 160 | + |
| 161 | +```sourcegraph |
| 162 | +repo:^github\.com/gitpod-io/openvscode-server$@8b3e975 const persistentTerminalId = await this.ptyService.createProcess |
| 163 | +``` |
| 164 | + |
| 165 | +The terminal service then enacts the corresponding action and relays the response back through the request chain covered above. |
| 166 | + |
| 167 | +## Diving in |
| 168 | + |
| 169 | +This hopefully gives you a good overview of how OpenVSCode Server turns VS Code into a web-based IDE. The code is completely open-source and released by Gitpod. You can try out [Gitpod](https://www.gitpod.io/) as a service or dive into more of the [source code on Sourcegraph](https://sourcegraph.com/github.com/gitpod-io/openvscode-server). |
0 commit comments