<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://vogella.com/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://vogella.com/blog/" rel="alternate" type="text/html" /><updated>2026-03-31T08:04:48+00:00</updated><id>https://vogella.com/blog/feed.xml</id><title type="html">vogella Blog</title><subtitle>Blog posts about Java, Eclipse, OSGi and  Open Source</subtitle><author><name>Lars</name></author><entry><title type="html">Visual Studio Code GitHub Copilot vs. Eclipse Theia AI</title><link href="https://vogella.com/blog/vscode_copilot_theia_ai_comparison/" rel="alternate" type="text/html" title="Visual Studio Code GitHub Copilot vs. Eclipse Theia AI" /><published>2026-03-31T00:00:00+00:00</published><updated>2026-03-31T00:00:00+00:00</updated><id>https://vogella.com/blog/vscode_copilot_theia_ai_comparison</id><content type="html" xml:base="https://vogella.com/blog/vscode_copilot_theia_ai_comparison/"><![CDATA[<p>If you plan to build applications with AI support, especially for software development, you first need to decide which platform to use. You can build a plain web application from scratch and implement everything yourself, or build on top of an existing platform. In my previous blog posts, I explained how to customize the AI experience in Visual Studio Code and how to create a custom AI extension in Eclipse Theia. While writing those posts, I noticed many similarities, both from a user perspective and from an extension-development perspective. In this post, I compare both approaches to give you a clearer view of where they align and where they differ.
If you have not read my previous blog posts and want more details, have a look at:</p>

<ul>
  <li><a href="https://vogella.com/blog/vscode_copilot_extension/">Extending Copilot in Visual Studio Code</a></li>
  <li><a href="https://vogella.com/blog/theia_ai_getting_started/">Getting Started with Theia AI</a></li>
</ul>

<p><em><strong>Note:</strong></em><br />
This blog post is based on Visual Studio Code 1.113.0 and Eclipse Theia 1.70.0. There may be differences if you read it when newer versions have been released.</p>

<h2 id="strategic-comparison">Strategic Comparison</h2>

<p>In the article <a href="https://blogs.eclipse.org/post/thomas-froment/why-extending-github-copilot-vs-code-may-not-be-best-fit-your-ai-native">Why Extending GitHub Copilot in VS Code May Not Be the Best Fit for Your AI-Native Development Tool</a>, you can already find a comprehensive overview of the differences between GitHub Copilot in Visual Studio Code and Theia AI. The article is around half a year old and is therefore not fully up to date with the latest features. However, the strategic perspective is still well described and remains valid, so the article is still worth reading if you plan to adopt either technology.</p>

<p>The following table extracts and summarizes an initial comparison of key aspects from that article:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>VS Code GitHub Copilot</th>
      <th>Eclipse Theia AI</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Platform</td>
      <td>Product you can extend</td>
      <td>Platform you can own</td>
    </tr>
    <tr>
      <td>License</td>
      <td>Mixture of open source parts (MIT) and proprietary parts (Microsoft products)</td>
      <td>Completely open source (EPL)</td>
    </tr>
    <tr>
      <td>AI customization options</td>
      <td>Chat only</td>
      <td>any part of the application</td>
    </tr>
    <tr>
      <td>Licensing</td>
      <td>Copilot subscription required</td>
      <td>-</td>
    </tr>
    <tr>
      <td>LLM Support</td>
      <td>Depends on Copilot subscription<br /><a href="https://code.visualstudio.com/docs/copilot/customization/language-models">AI language models in VS Code</a></td>
      <td>Depends on existing providers<br /><a href="https://theia-ide.org/docs/user_ai/#llm-providers-overview">LLM Providers Overview</a></td>
    </tr>
  </tbody>
</table>

<h2 id="ai-extension-comparison">AI Extension Comparison</h2>

<p>Visual Studio Code GitHub Copilot extensions and Eclipse Theia AI extensions target similar use cases: extending the AI capabilities of the IDE.
There are differences in terminology, implementation, extension capabilities, and features. In previous blog posts, I explained how to contribute <em>Tools</em>, <em>MCP Servers</em>, and <em>Chat Extensions</em>. The concepts already differ in naming, as shown in the following table:</p>

<table>
  <thead>
    <tr>
      <th>VS Code GitHub Copilot</th>
      <th>Eclipse Theia AI</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Language Model Tool</td>
      <td>Tool Function</td>
    </tr>
    <tr>
      <td>MCP</td>
      <td>MCP</td>
    </tr>
    <tr>
      <td>Chat Participant</td>
      <td>Agent</td>
    </tr>
  </tbody>
</table>

<p>The following sections will explain the differences in more detail.</p>

<h3 id="language-model-tool-vs-tool-function">Language Model Tool vs. Tool Function</h3>

<p><em>Tools</em> provide additional capabilities to perform specialized tasks when interacting with an LLM. In Visual Studio Code, they are called <a href="https://code.visualstudio.com/api/extension-guides/ai/tools"><em>Language Model Tools</em></a>; in Theia, they are called <a href="https://theia-ide.org/docs/theia_ai/#tool-functions"><em>Tool Functions</em></a>.</p>

<h4 id="visual-studio-code">Visual Studio Code</h4>

<p>To contribute a <em>Language Model Tool</em> via Copilot Extension, you follow the typical contribution pattern in Visual Studio Code Extensions:</p>

<ul>
  <li>Configure the contribution via <code class="language-plaintext highlighter-rouge">contributes/languageModelTools</code> section in the extensions <em>package.json</em></li>
  <li>Implement a new class that implements the <code class="language-plaintext highlighter-rouge">vscode.LanguageModelTool</code> interface</li>
  <li>Register it in the extension via <code class="language-plaintext highlighter-rouge">vscode.lm.registerTool()</code></li>
</ul>

<p>Further details can be found in <a href="https://vogella.com/blog/vscode_copilot_extension/#language-model-tool">Extending Copilot in Visual Studio Code - Language Model Tool</a>.</p>

<h4 id="eclipse-theia">Eclipse Theia</h4>

<p>To contribute a <em>Tool Function</em> via Theia Extension, you follow the typical contribution pattern in Theia:</p>

<ul>
  <li>Implement a new class that implements the <code class="language-plaintext highlighter-rouge">ToolProvider</code> interface from the <code class="language-plaintext highlighter-rouge">@theia/ai-core</code> package</li>
  <li>Register it in a <code class="language-plaintext highlighter-rouge">ContainerModule</code> for injection via <code class="language-plaintext highlighter-rouge">bindToolProvider()</code> from <code class="language-plaintext highlighter-rouge">@theia/ai-core/lib/common</code></li>
</ul>

<p>Further details can be found in <a href="https://vogella.com/blog/theia_ai_getting_started/#tool-functions">Getting Started with Theia AI - Tool Functions</a>.</p>

<h3 id="mcp-support">MCP Support</h3>

<p><em>MCP Servers</em> are typically configured by users via a configuration file. The usage comparison is described later in <a href="#configuring-mcp-servers">Configuring MCP Servers</a>. <em>MCP Servers</em> can also be contributed and configured programmatically, for example when you contribute an extension that provides an advanced AI use case requiring dedicated tools via MCP.</p>

<p><em><strong>Note:</strong></em><br />
MCP servers that are programmatically registered via a Visual Studio Code Copilot extension can also be installed in a Theia application if the <code class="language-plaintext highlighter-rouge">@theia/plugin-ext</code> extension is available.</p>

<h4 id="visual-studio-code-1">Visual Studio Code</h4>

<p>To contribute a <em>MCP Server</em> via Copilot Extension, you follow the typical contribution pattern in Visual Studio Code Extensions:</p>

<ul>
  <li>Configure the contribution via <code class="language-plaintext highlighter-rouge">contributes/mcpServerDefinitionProviders</code> section in the extensions <em>package.json</em></li>
  <li>Register a <code class="language-plaintext highlighter-rouge">McpServerDefinitionProvider</code> in the extension via <code class="language-plaintext highlighter-rouge">vscode.lm.registerMcpServerDefinitionProvider()</code></li>
  <li>Add MCP servers via <code class="language-plaintext highlighter-rouge">McpServerDefinitionProvider#provideMcpServerDefinitions()</code>
    <ul>
      <li>A local MCP server can be configured by creating a <a href="https://code.visualstudio.com/api/references/vscode-api#McpStdioServerDefinition"><code class="language-plaintext highlighter-rouge">vscode.McpStdioServerDefinition</code></a></li>
      <li>A remote MCP server can be configured by creating a <a href="https://code.visualstudio.com/api/references/vscode-api#McpHttpServerDefinition"><code class="language-plaintext highlighter-rouge">vscode.McpHttpServerDefinition</code></a></li>
    </ul>
  </li>
  <li>Dynamic resolution (e.g. asking the user for an authorization token) can be added via <code class="language-plaintext highlighter-rouge">McpServerDefinitionProvider#resolveMcpServerDefinition()</code></li>
</ul>

<p>There are some limitations:</p>

<ul>
  <li>To ensure that the MCP servers are registered automatically on start, you need to configure the <code class="language-plaintext highlighter-rouge">activationEvents</code> accordingly, e.g. <code class="language-plaintext highlighter-rouge">onStartupFinished</code></li>
  <li>There is no API to programmatically start or stop an <em>MCP Server</em> in Visual Studio Code.</li>
  <li>Programmatically registered MCP servers do not show up in the <em>MCP Servers</em> section of the <em>Extensions</em> view.</li>
  <li>Programmatically registered MCP servers do not get <a href="https://modelcontextprotocol.info/docs/concepts/roots/">roots</a> set based on the current workspace directory.</li>
</ul>

<p>Further details can be found in <a href="https://vogella.com/blog/vscode_copilot_extension/#add-mcp-server-programmatically-via-vs-code-extension">Extending Copilot in Visual Studio Code - Add MCP server programmatically via VS Code Extension</a>.</p>

<h4 id="eclipse-theia-1">Eclipse Theia</h4>

<p>To contribute a <em>MCP Server</em> via Theia Extension, you follow the typical contribution pattern in Theia:</p>

<ul>
  <li>Implement a <code class="language-plaintext highlighter-rouge">FrontendApplicationContribution</code></li>
  <li>Get the <code class="language-plaintext highlighter-rouge">MCPFrontendService</code> injected</li>
  <li>Add MCP servers via <code class="language-plaintext highlighter-rouge">MCPFrontendService#addOrUpdateServer()</code>
    <ul>
      <li>A local MCP server can be configured by creating a <code class="language-plaintext highlighter-rouge">LocalMCPServerDescription</code></li>
      <li>A remote MCP server can be configured by creating a <code class="language-plaintext highlighter-rouge">RemoteMCPServerDescription</code></li>
    </ul>
  </li>
  <li>Dynamic resolution (e.g. asking the user for an authorization token) can be added directly to the <code class="language-plaintext highlighter-rouge">MCPServerDescription</code> via the <code class="language-plaintext highlighter-rouge">MCPServerDescription#resolve()</code> function</li>
  <li>Register it in a <code class="language-plaintext highlighter-rouge">ContainerModule</code> via <code class="language-plaintext highlighter-rouge">bind()</code></li>
</ul>

<p>Compared to the limitations in Visual Studio Code, programmatically registered MCP servers in Theia</p>

<ul>
  <li>are automatically registered if the <code class="language-plaintext highlighter-rouge">FrontendApplicationContribution</code> is registered correctly in a <code class="language-plaintext highlighter-rouge">ContainerModule</code></li>
  <li>can be started and stopped programmatically via <code class="language-plaintext highlighter-rouge">MCPFrontendService#startServer()</code> and <code class="language-plaintext highlighter-rouge">MCPFrontendService#stopServer()</code></li>
  <li>do show up in the <em>AI Configuration</em> view</li>
  <li>Since Theia 1.69.0, <a href="https://modelcontextprotocol.info/docs/concepts/roots/">roots</a> are supported. This support was added via <a href="https://github.com/eclipse-theia/theia/pull/16911">PR</a>. You can configure whether the workspace should be used as a <em>Root</em> via the preference <code class="language-plaintext highlighter-rouge">ai-features.mcp.useWorkspaceAsRoot</code>.</li>
</ul>

<p>Further details can be found in <a href="https://vogella.com/blog/theia_ai_getting_started/#add-mcp-server-programmatically-via-theia-extension">Getting Started with Theia AI - Add MCP server programmatically via Theia Extension</a>.</p>

<h3 id="chat-participant-vs-custom-agent">Chat Participant vs. Custom Agent</h3>

<p>To extend the AI capabilities of an IDE or application with a specialized assistant that provides domain-specific expert knowledge, you can implement and contribute a <em>Chat Participant</em> in Visual Studio Code or a <em>Custom Agent</em> in Eclipse Theia. In both cases, the benefits of such a programmatically created AI assistant are:</p>

<ul>
  <li>you can manage the end-to-end prompt/response conversation</li>
  <li>you have access to the respective API, which allows deep integration with the underlying platform</li>
  <li>you can distribute and deploy it via extensions</li>
</ul>

<p><em>Custom Agents</em> defined via configuration files can only interact with the underlying platform if the required functionality is available as a <em>Language Model Tool</em> or <em>Tool Function</em>, or via <em>MCP</em>. They are also available only when the corresponding configuration files are present in the user’s workspace. <em>Custom Agents</em> from a user’s perspective are <a href="#custom-agents">covered in a later section</a>. In this section, I focus on the differences in programmatically created AI extensions.</p>

<h4 id="visual-studio-code-2">Visual Studio Code</h4>

<p>As the name implies, a <em>Chat Participant</em> contributed via a Visual Studio Code Copilot extension is intended for and limited to chat. There are no other use cases where a <em>Chat Participant</em> can be integrated, such as the terminal.</p>

<p>To contribute a <em>Chat Participant</em> via Copilot Extension, you follow the typical contribution pattern in Visual Studio Code Extensions:</p>

<ul>
  <li>Configure the contribution via <code class="language-plaintext highlighter-rouge">contributes/chatParticipants</code> section in the extensions <em>package.json</em></li>
  <li>Define a <code class="language-plaintext highlighter-rouge">vscode.ChatRequestHandler</code> which sends the chat request and handles the response</li>
  <li>Create a <code class="language-plaintext highlighter-rouge">vscode.ChatParticipant</code> via <code class="language-plaintext highlighter-rouge">vscode.chat.createChatParticipant()</code> by using the id defined in the <em>package.json</em> and the defined <code class="language-plaintext highlighter-rouge">vscode.ChatRequestHandler</code></li>
  <li>Register the <code class="language-plaintext highlighter-rouge">vscode.ChatParticipant</code> in the extension by pushing it to the <code class="language-plaintext highlighter-rouge">context.subscriptions</code></li>
</ul>

<p>To use the <em>Chat Participant</em>, you need to invoke it with the <code class="language-plaintext highlighter-rouge">@</code> syntax, e.g. <code class="language-plaintext highlighter-rouge">@joker tell me a joke</code>.</p>

<p>Further details can be found in <a href="https://vogella.com/blog/vscode_copilot_extension/#chat-participant">Extending Copilot in Visual Studio Code - Chat Participant</a>.</p>

<h4 id="eclipse-theia-2">Eclipse Theia</h4>

<p>Compared to Visual Studio Code, a <em>Custom Agent</em> in Theia is not limited to the chat. It can also be integrated into other parts of the IDE, such as the editor, the terminal, or a custom widget.</p>

<p>To contribute a <em>Custom Agent</em> via a Theia Extension, you follow the typical contribution pattern in Theia:</p>

<ul>
  <li>Implement an <code class="language-plaintext highlighter-rouge">AbstractStreamParsingChatAgent</code> and define the necessary attributes such as <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">name</code>, <code class="language-plaintext highlighter-rouge">description</code>, prompt(s), <em>Tool Functions</em>, and <em>Agent-Specific Variables</em>, if needed.</li>
  <li>Register it in a <code class="language-plaintext highlighter-rouge">ContainerModule</code> via <code class="language-plaintext highlighter-rouge">bind()</code></li>
</ul>

<p>When implementing a <em>Custom Agent</em>, there are several advanced features that can improve the user experience, for example:</p>

<ul>
  <li><em>Prompt Variants</em></li>
  <li><em>Agent-specific Variables</em></li>
  <li><em>Agent-to-Agent Delegation</em></li>
  <li><em>Custom Response Part Rendering</em></li>
</ul>

<p>To use a <em>Custom Agent</em> in Theia, you need to invoke it with the <code class="language-plaintext highlighter-rouge">@</code> syntax, e.g. <code class="language-plaintext highlighter-rouge">@Joker a joke about scarecrow</code>.</p>

<p>Further details can be found in <a href="https://vogella.com/blog/theia_ai_getting_started/#implement-a-custom-agent">Getting Started with Theia AI - Implement a Custom Agent</a>.</p>

<h2 id="ai-usage-comparison">AI Usage Comparison</h2>

<p>The sections above covered differences in programmatic AI customization. In the following sections, I compare AI usage differences from a user’s point of view.</p>

<h3 id="using-tools-in-prompts">Using tools in prompts</h3>

<p>How tools are used in prompts differs between Visual Studio Code and Theia. In general, there are three types of tools:</p>

<ul>
  <li>Built-in tools provided by the platform</li>
  <li>MCP tools added via MCP servers</li>
  <li>Extension tools provided programmatically (<em>Language Model Tool</em> vs. <em>Tool Function</em>)</li>
</ul>

<h4 id="visual-studio-code-3">Visual Studio Code</h4>

<p>In Visual Studio Code, a <em>Tool</em> is used when processing a prompt if:</p>

<ul>
  <li>the tool is in the list of enabled tools and is selected automatically
    <figure class="">
<img src="/blog/assets/images/copilot_tools_configuration.png" alt="" /></figure>
  </li>
  <li>the tool is used explicitly by mentioning it using a leading <code class="language-plaintext highlighter-rouge">#</code>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#jokeFileCreator create a file that contains a joke in the folder test
</code></pre></div>    </div>
  </li>
</ul>

<p>For tools provided via MCP, the tool is also selected by name. Assuming you added the <em>fetch MCP server</em>, you can use the provided tool via <code class="language-plaintext highlighter-rouge">#fetch</code>. Unfortunately, this is not unique because there is also a built-in <code class="language-plaintext highlighter-rouge">fetch</code> tool in Visual Studio Code.</p>

<p>By default, you will be prompted before a <em>Language Model Tool</em> is executed.</p>

<figure class="">
  <img src="/blog/assets/images/copilot_language_model_tool_allow.png" alt="" /></figure>

<p>In the <em>Allow</em> dropdown, you can choose whether to allow execution once, for the current session, or generally in the current workspace. This setting is not directly accessible at the time of writing this blog post. To reset saved tool approvals, run the <em>Chat: Reset Tool Confirmations</em> command from the Command Palette (<code class="language-plaintext highlighter-rouge">Ctrl+Shift+P</code>).</p>

<p>In Visual Studio Code, it is also possible to configure <a href="https://code.visualstudio.com/docs/copilot/agents/agent-tools#_url-approval">URL approval</a>, which becomes active when a tool attempts to access a URL.</p>

<p>Further details about using tools in chat in Visual Studio Code can be found in <a href="https://code.visualstudio.com/docs/copilot/agents/agent-tools">Use tools with agents</a>.</p>

<h4 id="eclipse-theia-3">Eclipse Theia</h4>

<p>In Eclipse Theia, a <em>Tool</em> is used by mentioning it with a leading <code class="language-plaintext highlighter-rouge">~</code>. There is no automatic tool resolution for prompts, so you always need to mention the tool explicitly.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal create a file that contains a joke in the folder test. Use a file name that relates to the joke. ~jokeFileCreator
</code></pre></div></div>

<p>The name of an MCP server tool in Theia is derived from the server name and function name. It uses the following syntax:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~{mcp_&lt;server-name&gt;_&lt;function-name&gt;}
</code></pre></div></div>

<p>For example, to use the <code class="language-plaintext highlighter-rouge">fetch</code> function of the <code class="language-plaintext highlighter-rouge">fetch</code> MCP server, you could write a prompt like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal show me the allowed directories ~{mcp_fetch_fetch}
</code></pre></div></div>

<p>By default, the <em>Tool Confirmation Mode</em> is <strong>Always Allow</strong>. Users can change this setting.</p>

<ul>
  <li>Open the <em>AI Configuration</em> view via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
  <li>Switch to the <em>Tools</em> tab
    <figure class="">
<img src="/blog/assets/images/theia_tools_configuration_confirmation.png" alt="" /></figure>
  </li>
  <li>Alternatively, edit <em>settings.json</em> and configure <code class="language-plaintext highlighter-rouge">ai-features.chat.toolConfirmation</code>. For example, if you want to be prompted for approval for every tool call but allow <code class="language-plaintext highlighter-rouge">jokeFileCreator</code> to execute without confirmation:
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"ai-features.chat.toolConfirmation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"*"</span><span class="p">:</span><span class="w"> </span><span class="s2">"confirm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"jokeFileCreator"</span><span class="p">:</span><span class="w"> </span><span class="s2">"always_allow"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<p>The <strong>URL approval feature</strong> is currently not supported in Eclipse Theia.</p>

<p>Further details about using tools in chat in Eclipse Theia can be found in <a href="https://theia-ide.org/docs/theia_ai/#tool-functions">Tool Functions</a>, <a href="https://theia-ide.org/docs/user_ai/#using-mcp-server-functions">Using MCP Server Functions</a>, and <a href="https://theia-ide.org/docs/user_ai/#tool-call-confirmation-ui">Tool Call Confirmation UI</a>.</p>

<h3 id="configuring-mcp-servers">Configuring MCP Servers</h3>

<p>As a user, you typically add MCP servers to your development environment via a configuration file. There are two basic types of MCP servers:</p>

<ul>
  <li>local MCP servers, which are software installed on your system</li>
  <li>remote MCP servers, which are hosted elsewhere and provide functionality without needing local system access.</li>
</ul>

<h4 id="visual-studio-code-4">Visual Studio Code</h4>

<p>In Visual Studio Code, you typically add MCP servers via an <em>mcp.json</em> file, either in the workspace <em>.vscode</em> folder or in the user profile to configure MCP servers for all workspaces.
A <em>mcp.json</em> file has two main sections:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">"servers": {}</code> - Contains the list of MCP servers and their configurations</li>
  <li><code class="language-plaintext highlighter-rouge">"inputs": []</code> - Optional placeholders for sensitive information like API keys</li>
</ul>

<p>You can use predefined variables in the <code class="language-plaintext highlighter-rouge">servers</code> section and the variables defined in the <code class="language-plaintext highlighter-rouge">inputs</code> section.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"servers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"filesystem"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stdio"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"-y"</span><span class="p">,</span><span class="w"> </span><span class="s2">"@modelcontextprotocol/server-filesystem"</span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"fetch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://remote.mcpservers.org/fetch/mcp"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sse"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"X-MCP-Toolsets"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gists"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"Authorization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Bearer ${env:GITHUB_TOKEN}"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"inputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>From the example above, you can see that you need to specify the <code class="language-plaintext highlighter-rouge">type</code> field to define whether it is a local or a remote MCP server.</p>

<p>You can manage the MCP server either via</p>

<ul>
  <li><strong>MCP SERVERS - INSTALLED</strong> section in the <em>Extensions</em> view
    <figure class="">
<img src="/blog/assets/images/vscode_mcp_servers.png" alt="" /></figure>
  </li>
  <li>Inline actions in the <em>mcp.json</em> editor (codelenses)
    <figure class="">
<img src="/blog/assets/images/mcp_codelens.png" alt="" /></figure>
  </li>
  <li><strong>MCP: List Servers</strong> command from the Command Palette
    <figure class="">
<img src="/blog/assets/images/vscode_mcp_servers_command.png" alt="" /></figure>
  </li>
</ul>

<p>After the first start of an MCP server, the server’s capabilities and tools are discovered and cached. The next time a tool is used in a prompt while the server is not running yet, Visual Studio Code can auto-start the server because of this cached information.</p>

<p>Using the <code class="language-plaintext highlighter-rouge">chat.mcp.autostart</code> setting, you can configure whether MCP servers should be restarted automatically when configuration changes are detected. This autostart setting is available only globally, not per server.</p>

<p>Further information can be found in <a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers">Use MCP servers in VS Code</a>.</p>

<h4 id="eclipse-theia-4">Eclipse Theia</h4>

<p>In Eclipse Theia, you typically add MCP servers via the general <em>settings.json</em> file. There is currently no support for an <em>mcp.json</em> file.</p>

<p>The <em>settings.json</em> file contains many settings that can be applied in Theia, not just MCP-related configuration. There is no support for predefined variables or inputs like the <code class="language-plaintext highlighter-rouge">inputs</code> section in <em>mcp.json</em>.</p>

<p>Comparing Visual Studio Code <em>mcp.json</em> and Eclipse Theia <em>settings.json</em> MCP server configuration, there are a few differences:</p>

<ul>
  <li>In Theia, you do not need to specify the <code class="language-plaintext highlighter-rouge">type</code> field.<br />
For local servers, this is straightforward because only one type is available (<code class="language-plaintext highlighter-rouge">stdio</code>). For remote servers, Theia first tries to connect via <code class="language-plaintext highlighter-rouge">sse</code>, and if that fails, it falls back to <code class="language-plaintext highlighter-rouge">http</code>.</li>
  <li>In Eclipse Theia, there is an <code class="language-plaintext highlighter-rouge">autostart</code> field that lets you configure whether an MCP server should start automatically. The default is <code class="language-plaintext highlighter-rouge">true</code> if it is not set.</li>
  <li>For remote servers, the main URL is set as <code class="language-plaintext highlighter-rouge">url</code> in Visual Studio Code, while in Eclipse Theia the field is called <code class="language-plaintext highlighter-rouge">serverUrl</code>.</li>
  <li>In Eclipse Theia, you can specify the authentication token via <code class="language-plaintext highlighter-rouge">serverAuthToken</code>, which defaults to an <em>Authorization</em> header with <em>Bearer</em>. You can change that via <code class="language-plaintext highlighter-rouge">serverAuthTokenHeader</code>.</li>
</ul>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"ai-features.mcp.mcpServers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"filesystem"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"-y"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"@modelcontextprotocol/server-filesystem"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"/home/node/examples"</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"autostart"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"fetch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://remote.mcpservers.org/fetch/mcp"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"autostart"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"serverAuthToken"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;your-token&gt;"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"X-MCP-Toolsets"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gists"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You can manage the MCP server either via</p>

<ul>
  <li><em>AI Configuration</em> view: <em>Menu</em> -&gt; <em>View</em> -&gt; <em>AI Configuration</em> -&gt; <em>MCP Servers</em> tab
    <figure class="">
<img src="/blog/assets/images/theia_mcp_filesystem.png" alt="" /></figure>
  </li>
  <li>Command palette: <em>F1</em>
    <ul>
      <li><em>MCP: Start MCP Server -&gt; filesystem</em></li>
      <li><em>MCP: Stop MCP Server -&gt; filesystem</em></li>
    </ul>
  </li>
</ul>

<p>If an MCP server is not started, its capabilities and tools cannot be used. There is no auto-start based on cached information as in Visual Studio Code. However, you can configure auto-start behavior per server via the <code class="language-plaintext highlighter-rouge">autostart</code> field, which is enabled by default.</p>

<p>Further information can be found in <a href="https://theia-ide.org/docs/user_ai/#mcp-integration">MCP Integration</a>.</p>

<h3 id="chat-variables">Chat Variables</h3>

<p>In Visual Studio Code and Eclipse Theia, it is possible, and usually recommended, to add specific context to your request. To do this explicitly, you can use the <code class="language-plaintext highlighter-rouge">#</code> syntax with some predefined options.</p>

<p>In Theia, it is also possible to implement and provide additional <em>Context Variables</em> as <em>Global Variables</em> or <em>Agent-Specific Variables</em>.</p>

<p>Further information for Visual Studio Code can be found in <a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat#_add-context-to-your-prompts">Add context to your prompts</a>.</p>

<p>Further information for Eclipse Theia can be found in <a href="https://theia-ide.org/docs/user_ai/#context-variables">Context Variables</a>.</p>

<h3 id="prompt-files-vs-prompt-fragments">Prompt Files vs Prompt Fragments</h3>

<p>Visual Studio Code and Eclipse Theia both allow users to define reusable prompts for recurring development tasks. Although the feature is similar, there are differences.</p>

<h4 id="visual-studio-code-5">Visual Studio Code</h4>

<p>In Visual Studio Code, you can create and use <a href="https://code.visualstudio.com/docs/copilot/customization/prompt-files">Prompt Files</a> to define reusable prompts for recurring development tasks.</p>

<p><em>Prompt Files</em> are Markdown files with a <em>.prompt.md</em> file extension. They are located either in the <em>.github/prompts</em> folder in the workspace or in the <em>prompts</em> folder in the user profile (for example on Windows, <em>C:\Users\&lt;username&gt;\AppData\Roaming\Code\User\prompts</em>).</p>

<p>In the optional YAML frontmatter header, the prompt behavior can be configured, for example:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">agent</code> header to define the agent that should be used for running the prompt</li>
  <li><code class="language-plaintext highlighter-rouge">tools</code> header to define the list of tools or tool sets that are available for the prompt</li>
</ul>

<p>You can use variables in the prompt file using the syntax <code class="language-plaintext highlighter-rouge">${variableName}</code>. The following variables can be used:</p>

<ul>
  <li>Workspace variables - <code class="language-plaintext highlighter-rouge">${workspaceFolder}</code>, <code class="language-plaintext highlighter-rouge">${workspaceFolderBasename}</code></li>
  <li>Selection variables - <code class="language-plaintext highlighter-rouge">${selection}</code>, <code class="language-plaintext highlighter-rouge">${selectedText}</code></li>
  <li>File context variables - <code class="language-plaintext highlighter-rouge">${file}</code>, <code class="language-plaintext highlighter-rouge">${fileBasename}</code>, <code class="language-plaintext highlighter-rouge">${fileDirname}</code>, <code class="language-plaintext highlighter-rouge">${fileBasenameNoExtension}</code></li>
  <li>Input variables - <code class="language-plaintext highlighter-rouge">${input:variableName}</code>, <code class="language-plaintext highlighter-rouge">${input:variableName:placeholder}</code></li>
</ul>

<p>Arguments passed in chat via <em>key=value</em> syntax (e.g. <code class="language-plaintext highlighter-rouge">_target=robin</code>) are available in the prompt via <code class="language-plaintext highlighter-rouge">${input:variableName}</code> or <code class="language-plaintext highlighter-rouge">${input:variableName:placeholder}</code>.</p>

<p><em>Tools</em> configured as available in the prompt via the <code class="language-plaintext highlighter-rouge">tools</code> frontmatter header can be referenced in the prompt via <code class="language-plaintext highlighter-rouge">#tool:&lt;tool-name&gt;</code>.</p>

<p>To use a <em>Prompt File</em> in the Chat view, type <em>/</em> followed by the prompt name. For example, if you created a <em>harley.prompt.md</em> file, you could execute it via the slash command <em>/harley</em>.</p>

<p>Further information can be found in <a href="https://vogella.com/blog/vscode_copilot_extension/#prompt-files">Extending Copilot in Visual Studio Code - Further Customization - Prompt Files</a>.</p>

<h4 id="eclipse-theia-5">Eclipse Theia</h4>

<p>In Eclipse Theia, you can create and use <a href="https://theia-ide.org/docs/user_ai/#prompt-fragments">Prompt Fragments</a> to define reusable prompts for recurring development tasks.</p>

<p><em>Prompt Fragments</em> are Markdown files with a <em>.prompttemplate</em> file extension. They are located either in the <em>.prompts</em> folder in the workspace or in user-wide local directories configured in the settings (<em>AI Features -&gt; Prompt Templates</em>).</p>

<p>In the optional YAML frontmatter header, prompts can be configured as a <em>Slash Command</em>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">isCommand</code> header needs to be set to <code class="language-plaintext highlighter-rouge">true</code></li>
  <li><code class="language-plaintext highlighter-rouge">commandName</code> header specifies the name of the command</li>
  <li><code class="language-plaintext highlighter-rouge">commandAgents</code> header can be used to limit the visibility of the command to the specified agents.</li>
</ul>

<p>You can use any available <a href="https://theia-ide.org/docs/user_ai/#context-variables">Context Variable</a> in the prompt.</p>

<p>Command arguments passed via chat can be used in the prompt via <em>Argument placeholders</em> <code class="language-plaintext highlighter-rouge">$ARGUMENTS</code> or <code class="language-plaintext highlighter-rouge">$1</code>, <code class="language-plaintext highlighter-rouge">$2</code>, <code class="language-plaintext highlighter-rouge">$3</code> for arguments by position.</p>

<p>The tools that should be used inside the prompt do not need to be preconfigured in the YAML frontmatter and can be referenced in the prompt via <code class="language-plaintext highlighter-rouge">~&lt;tool-name&gt;</code>.</p>

<p>To use a <em>Prompt Fragment</em> in the Chat view, you can use the special variable <code class="language-plaintext highlighter-rouge">#prompt:promptFragmentID</code>, for example <code class="language-plaintext highlighter-rouge">@Universal #prompt:harley joke about batgirl</code>. When configured as a <em>Slash Command</em>, type <em>/</em> followed by the prompt name, for example <code class="language-plaintext highlighter-rouge">@Universal /harley batgirl</code>.</p>

<p>Further information can be found in <a href="https://vogella.com/blog/theia_ai_getting_started/#prompt-fragments">Getting Started with Theia AI - Further Customization - Prompt Fragments</a> and <a href="https://vogella.com/blog/theia_ai_getting_started/#slash-commands">Getting Started with Theia AI - Further Customization - Slash Commands</a>.</p>

<h3 id="custom-agents">Custom Agents</h3>

<p><em>Custom Agents</em> are a mechanism that allows users to create specialized assistants for specific tasks in the chat. For example, users can create different personas tailored to development roles and tasks such as planning, research, or specialized workflows.</p>

<p><em>Custom Agents</em> are similar to Visual Studio Code <em>Chat Participants</em> or programmatically contributed <em>Custom Agents</em> in Eclipse Theia. However, instead of contributing them programmatically, users define and configure them through dedicated agent files.</p>

<h4 id="visual-studio-code-6">Visual Studio Code</h4>

<p>In Visual Studio Code, <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents">Custom Agents</a> are configured in custom agent files. <em>Custom Agent Files</em> are Markdown files with a <em>.agent.md</em> extension. They are located either in the <em>.github/agents</em> folder in the workspace or in the user profile folder (interestingly, also in the <em>prompts</em> folder). There is one dedicated <em>.agent.md</em> file for each <em>Custom Agent</em>.</p>

<p>Similar to <em>Prompt Files</em>, <em>Custom Agents</em> can be configured via an optional YAML frontmatter header. You can, for example:</p>

<ul>
  <li>select the model that should be used when running the agent prompt via the <code class="language-plaintext highlighter-rouge">model</code> header</li>
  <li>select the tools or tool sets that can be used by the custom agent via the <code class="language-plaintext highlighter-rouge">tools</code> header</li>
  <li>select the agents that are available as subagents in the custom agent via the <code class="language-plaintext highlighter-rouge">agents</code> header</li>
  <li>configure handoffs for orchestrating multi-step workflows via the <code class="language-plaintext highlighter-rouge">handoffs</code> header</li>
</ul>

<p>To use a <em>Custom Agent</em>, select it from the agents dropdown in chat and enter a prompt, for example <code class="language-plaintext highlighter-rouge">joke about alfred</code>.</p>

<figure class="">
  <img src="/blog/assets/images/copilot_select_custom_agent.png" alt="" /></figure>

<p>Further information can be found in <a href="https://vogella.com/blog/vscode_copilot_extension/#custom-agents">Extending Copilot in Visual Studio Code - Further Customization - Custom Agents</a>.</p>

<p>While the usage of agents in Visual Studio Code and Eclipse Theia is quite similar, Visual Studio Code has some features that are currently not available in Eclipse Theia:</p>

<ul>
  <li>You can select an <a href="https://code.visualstudio.com/docs/copilot/agents/overview#_types-of-agents">Agent type</a> to control <em>where</em> and <em>how</em> an agent runs: <a href="https://code.visualstudio.com/docs/copilot/agents/local-agents">local</a>, <a href="https://code.visualstudio.com/docs/copilot/agents/background-agents">background</a>, <a href="https://code.visualstudio.com/docs/copilot/agents/cloud-agents">cloud</a>, or <a href="https://code.visualstudio.com/docs/copilot/agents/third-party-agents">third-party</a>.</li>
  <li>There is a distinction between calling a <a href="https://code.visualstudio.com/docs/copilot/agents/subagents">Subagent</a>, which spawns a child agent within a session to handle a subtask in its own isolated context window, and performing a <a href="https://code.visualstudio.com/docs/copilot/agents/overview#_hand-off-a-session-to-another-agent">Hand off</a>, which transfers a session from one agent type to another while carrying over the conversation history.</li>
</ul>

<p>Further information can be found in <a href="https://code.visualstudio.com/docs/copilot/agents/overview">Using agents in Visual Studio Code</a>.</p>

<h4 id="eclipse-theia-6">Eclipse Theia</h4>

<p>In Eclipse Theia, multiple <a href="https://theia-ide.org/docs/user_ai/#custom-agents">Custom Agents</a> are configured in a single custom agent configuration file. The <em>Custom Agent File</em> is a YAML file named <em>customAgents.yml</em>. It is located either in the <em>.prompts</em> folder in the workspace or in user-wide local directories configured in the settings (<em>AI Features -&gt; Prompt Templates</em>).</p>

<p>Because there is only one file for multiple <em>Custom Agents</em>, there is no frontmatter configuration header. Instead, each <em>Custom Agent</em> configuration has its own attributes. In addition to the obvious fields such as <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">name</code>, <code class="language-plaintext highlighter-rouge">description</code>, and <code class="language-plaintext highlighter-rouge">prompt</code>, you need to specify the model via the <code class="language-plaintext highlighter-rouge">defaultLLM</code> field.</p>

<p>You can use any available <a href="https://theia-ide.org/docs/user_ai/#context-variables">Context Variable</a> in the prompt.</p>

<p>The tools that should be used inside the prompt do not need to be preconfigured in any way. They can be referenced in the prompt via <code class="language-plaintext highlighter-rouge">~&lt;tool-name&gt;</code>.</p>

<p>You can delegate to another agent by using the <code class="language-plaintext highlighter-rouge">delegateToAgent</code> <em>Tool Function</em>.</p>

<p>To use a <em>Custom Agent</em>, you need to use the <code class="language-plaintext highlighter-rouge">@</code> syntax in the chat view, just like for any other agent in Theia, e.g. <code class="language-plaintext highlighter-rouge">@Joker joke about alfred</code>.</p>

<p>If you use a specific agent most of the time and do not want to type the <code class="language-plaintext highlighter-rouge">@</code> syntax every time, you can configure a default agent via the preference <code class="language-plaintext highlighter-rouge">ai-features.chat.defaultChatAgent</code>, for example to use the <em>Universal</em> agent whenever no agent is explicitly mentioned in the chat:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"ai-features.chat.defaultChatAgent": "Universal"
</code></pre></div></div>

<p>Further information can be found in <a href="https://vogella.com/blog/theia_ai_getting_started/#custom-agents">Getting Started with Theia AI - Further Customization - Custom Agents</a> and <a href="https://vogella.com/blog/theia_ai_getting_started/#agent-to-agent-delegation-function">Getting Started with Theia AI - Further Customization - Agent-to-Agent Delegation (function)</a>.</p>

<h3 id="agent-orchestration">Agent orchestration</h3>

<p>When talking about AI agents, we now often refer to <em>Agentic AI workflows</em>, where multiple agents collaborate to achieve complex goals. When creating <em>Custom Agents</em>, you need to consider collaboration options, especially if you design your agents for dedicated tasks rather than having one agent do everything. This is important because you should keep the context of your agent or prompt as small as possible while still providing enough information to achieve the desired results. Creating agents for dedicated tasks with a limited scope or context is similar to the encapsulation principle in object-oriented programming, and likewise, you need to consider processing or orchestration patterns.</p>

<p>Apart from a <em>Single Agent</em> that does all the work alone, you currently have two options:</p>

<ul>
  <li><em>Delegate Pattern</em><br />
Create guided sequential workflows that transition between agents. The agents are called one after the other.</li>
  <li><em>Coordinator and Worker Pattern</em><br />
The main/coordinator agent receives the task, delegates subtasks to subagents, and combines the subagent results into the final result.</li>
</ul>

<p>You can also combine these patterns to have multiple “main” agents that use worker agents for specific tasks. Each “main” agent can delegate to the next “main” agent once it is done, creating a sequential workflow of main tasks.</p>

<p>While the patterns are generally the same, technically there are differences between Visual Studio Code and Eclipse Theia.</p>

<h4 id="visual-studio-code-7">Visual Studio Code</h4>

<p>The <em>Delegate Pattern</em> is supported via <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents#_handoffs">Handoffs</a> when creating <em>Custom Agents</em> in Visual Studio Code.
A handoff is configured in the YAML frontmatter header. The user needs to actively proceed with the handoff by clicking a button in the chat UI.
The token usage with a handoff is similar to having a single agent that processes all the tasks itself, as the whole context is passed from one agent to the next.</p>

<p>The <a href="https://code.visualstudio.com/docs/copilot/agents/subagents#_coordinator-and-worker-pattern">Coordinator and Worker Pattern</a> is supported through <a href="https://code.visualstudio.com/docs/copilot/agents/subagents">Subagents</a>.
To call a subagent, the built-in <code class="language-plaintext highlighter-rouge">agent/runSubagent</code> tool needs to be enabled for the coordinator agent.
Using a subagent means spawning a child agent within a session to handle a subtask in its own isolated context window. This reduces the token usage, as subagents are not called with the whole context compared to a <em>Handoff</em>.</p>

<p>Each subagent call is sequential (the coordinator waits for that call to return), but the coordinator can spawn multiple subagent calls in parallel.</p>

<p>Further information can be found in <a href="https://vogella.com/blog/ai-agent-orchestration/#visual-studio-code">AI Agent Orchestration - Visual Studio Code</a>.</p>

<h4 id="eclipse-theia-7">Eclipse Theia</h4>

<p>Theia does not provide a feature like <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents#_handoffs">Handoffs</a> in Visual Studio Code. Instead, Theia provides the built-in <em>Tool Function</em> <code class="language-plaintext highlighter-rouge">delegateToAgent</code> to support <a href="https://theia-ide.org/docs/user_ai/#agent-to-agent-delegation">Agent-to-Agent Delegation</a>, which is comparable to <a href="https://code.visualstudio.com/docs/copilot/agents/subagents">Subagents</a>. Whether you implement the <em>Delegate Pattern</em> or the <em>Coordinator and Worker Pattern</em> depends on how you use the <code class="language-plaintext highlighter-rouge">delegateToAgent</code> tool in the prompt.</p>

<p>Using a subagent means spawning a child agent within a session to handle a subtask in its own isolated context window. Because Theia has no <em>Handoff</em> concept that forwards processing to another agent, but only the concept of subagents, you do not see the same token-usage difference there.</p>

<p>Each subagent call via <code class="language-plaintext highlighter-rouge">delegateToAgent</code> is sequential (the coordinator waits for that call to return), but the coordinator can spawn multiple subagent calls in parallel.</p>

<p>Further information can be found in <a href="https://vogella.com/blog/ai-agent-orchestration/#eclipse-theia">AI Agent Orchestration - Eclipse Theia</a>.</p>

<h3 id="context-monitoring--token-usage">Context Monitoring / Token Usage</h3>

<p>In Visual Studio Code, it is possible to <a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat-context#_monitor-context-window-usage">monitor context window usage</a>. This helps indicate how much context a conversation uses. Theia does not yet support such context-window monitoring, but this feature has been requested via <a href="https://github.com/eclipse-theia/theia/issues/16779">Context window inspection / analysis command</a>.</p>

<figure class="">
  <img src="/blog/assets/images/copilot_context_window_subagents_codex.png" alt="" /></figure>

<p>In Theia, token usage can be inspected via <em>AI Configuration</em> by switching to the <em>Token Usage</em> tab, at least for LLMs that provide usage metadata for a request. Token usage is accumulated within a session, so you do not get the information per request. Token usage is not persisted and is reset when restarting the application. Visual Studio Code does not support tracking token usage at that level.</p>

<figure class="">
  <img src="/blog/assets/images/theia_token_coordinate.png" alt="" /></figure>

<h3 id="agent-skills">Agent Skills</h3>

<p><a href="https://agentskills.io/home">Agent Skills</a> is a simple, open format for giving agents new capabilities and expertise. Basically, a skill is a folder containing a <em>SKILL.md</em> file that includes metadata in the form of a YAML frontmatter header and instructions that tell an agent how to perform a specific task. It can also contain subfolders with scripts, templates, and referenced materials. Because it is an open format, it is supported in various AI tools.</p>

<p><em>Agent Skills</em> are a fairly new format, and both Visual Studio Code and Eclipse Theia support it, although in Eclipse Theia the feature is still in alpha. There are some differences in where skills are stored and how they are used, which I list in the following sections. If you are interested in examples, have a look at <a href="https://github.com/heilcheng/awesome-agent-skills">awesome-agent-skills</a>.</p>

<h4 id="visual-studio-code-8">Visual Studio Code</h4>

<ul>
  <li>The location of the skills folder for a project in your repository is <em>.github/skills/</em>, <em>.claude/skills/</em> or <em>.agents/skills/</em>.</li>
  <li>The location for personal skills in the user home is <em>~/.copilot/skills/</em>, <em>~/.claude/skills/</em> or <em>~/.agents/skills/</em>.</li>
  <li>You can configure additional locations for skills via the <code class="language-plaintext highlighter-rouge">chat.agentSkillsLocations</code> setting to share skills across projects or keep them in a central location.</li>
  <li>You can use skills directly in the chat via a slash command. For example, if you created a skill with the name <em>webapp-testing</em>, you can use it directly in the chat via <code class="language-plaintext highlighter-rouge">/webapp-testing for the login page</code>.</li>
  <li>Copilot discovers the skills to use automatically by matching the user prompt with the skill description. Only the body of a matching skill will be loaded into the context. Additional resources will only be added if they are referenced in the instructions.</li>
  <li>You can create a new skill manually by creating the required folder and a <em>SKILL.md</em> file in that folder</li>
  <li>You can create a new skill with the help of AI by using the <code class="language-plaintext highlighter-rouge">/create-skill</code> slash command in the chat</li>
  <li>You can use the <em>Chat Customizations</em> dialog by clicking the gear icon in the upper-right corner of the Copilot chat window, select the <em>Skills</em> menu item on the left side and select an option to generate a skill via the dropdown on the upper right corner to guide you through the initial creation.
    <figure class="">
<img src="/blog/assets/images/copilot_chat_menu.png" alt="" /></figure>

    <figure class="">
<img src="/blog/assets/images/copilot_customizations_skills.png" alt="" /></figure>
  </li>
</ul>

<p>You can learn more about the use of <em>Agent Skills</em> in Visual Studio Code in <a href="https://code.visualstudio.com/docs/copilot/customization/agent-skills">Use Agent Skills in VS Code</a> and <a href="https://vogella.com/blog/vscode_copilot_extension/#agent-skills">Extending Copilot in Visual Studio Code - Further Customization - Agent Skills</a>.</p>

<h4 id="eclipse-theia-8">Eclipse Theia</h4>

<p>Support for <em>Agent Skills</em> in Eclipse Theia is still in alpha and currently has known limitations. For example, in Theia 1.70.0, automatic skill discovery is not working reliably. Automatic execution and tool calls can also fail. These issues are likely to improve in upcoming releases.</p>

<ul>
  <li>The location of the skills folder for a project in your repository is <em>.prompts/skills/</em>.</li>
  <li>The location for personal skills in the user home is <em>~/.theia/skills/</em>.</li>
  <li>You can configure additional locations for skills via the <code class="language-plaintext highlighter-rouge">ai-features.skills.skillDirectories</code> setting to share skills across projects or keep them in a central location.</li>
  <li>You can use skills directly in the chat as a slash command. For example, if you created a skill with the name <em>webapp-testing</em>, you can use it directly in the chat via <code class="language-plaintext highlighter-rouge">/webapp-testing for the login page</code>.</li>
  <li>You can enable agents to load skills on demand by using the `` variable in an agent’s prompt to list all available skills, and use the <code class="language-plaintext highlighter-rouge">~getSkillFileContent</code> function to load a selected skill on demand.</li>
  <li>You can create a new skill manually by creating the required folder and a <em>SKILL.md</em> file in that folder</li>
  <li>You can create a new skill with the help of AI by using the built-in <code class="language-plaintext highlighter-rouge">@CreateSkill</code> agent in the chat</li>
  <li>There is a <a href="https://theia-ide.org/docs/user_ai/#skills-and-slash-commands-view">Skills and Slash Commands View</a> available via the <em>AI Configuration</em> via the <em>Skills</em> tab, which provides a convenient overview of the discovered skills and a button to directly open the corresponding <em>SKILL.md</em> file in an editor.
    <figure class="">
<img src="/blog/assets/images/theia_configuration_skills.png" alt="" /></figure>
  </li>
</ul>

<p>You can learn more about the use of <em>Agent Skills</em> in Eclipse Theia via <a href="https://theia-ide.org/docs/user_ai/#agent-skills-alpha">Agent Skills (Alpha)</a> and <a href="https://vogella.com/blog/theia_ai_getting_started/#agent-skills">Getting Started with Theia AI - Further Customization - Agent Skills</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>As you can see, the AI capabilities of Visual Studio Code GitHub Copilot and Eclipse Theia AI are very similar. Both ecosystems evolve quickly, and while writing this post, I had to update findings and screenshots several times to keep it current.</p>

<p>At a high level, both platforms support the same core building blocks: tools, MCP integration, custom agents, reusable prompts, and skills. The main differences are in product strategy and developer control:</p>

<ul>
  <li>Visual Studio Code with GitHub Copilot gives you a polished product with strong built-in workflows and broad adoption.</li>
  <li>Eclipse Theia gives you an open platform you can own and shape deeply for your own product and domain.</li>
</ul>

<p>From a practical perspective, your choice usually depends less on feature checklists and more on your goals:</p>

<ul>
  <li>Choose Visual Studio Code GitHub Copilot if you want fast adoption, familiar UX, and minimal platform-level ownership.</li>
  <li>Choose Eclipse Theia if you need full control, deeper integration into your own application, and an open customization model.</li>
</ul>

<p>It is also worth noting that AI customization now offers multiple overlapping mechanisms for similar outcomes. Deciding whether to use a <em>Custom Agent</em>, a <em>Prompt File/Fragment</em>, or an <em>Agent Skill</em> is not always straightforward and often depends on scope, reuse, and ownership.</p>

<p>If you want to dive deeper into that topic, there is a valuable discussion in the Theia repository: <a href="https://github.com/eclipse-theia/theia/discussions/17087#discussioncomment-15970074">Agent vs PromptTemplate vs Skill</a>. The response by <a href="https://www.linkedin.com/in/jonas-helming-76303b28/">Jonas Helming</a> provides useful guidance on how these mechanisms differ and when each is a good fit.</p>

<p>In short: both platforms are strong, modern foundations for AI-assisted development. If you optimize for ready-to-use productivity, Visual Studio Code is a strong choice. If you optimize for extensibility and product ownership, Eclipse Theia is compelling.</p>]]></content><author><name>Dirk Fauth</name></author><category term="fipro" /><category term="vscode" /><category term="theia" /><category term="ai" /><category term="agents" /><summary type="html"><![CDATA[If you plan to build applications with AI support, especially for software development, you first need to decide which platform to use. You can build a plain web application from scratch and implement everything yourself, or build on top of an existing platform. In my previous blog posts, I explained how to customize the AI experience in Visual Studio Code and how to create a custom AI extension in Eclipse Theia. While writing those posts, I noticed many similarities, both from a user perspective and from an extension-development perspective. In this post, I compare both approaches to give you a clearer view of where they align and where they differ. If you have not read my previous blog posts and want more details, have a look at:]]></summary></entry><entry><title type="html">AI Agent Orchestration</title><link href="https://vogella.com/blog/ai-agent-orchestration/" rel="alternate" type="text/html" title="AI Agent Orchestration" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://vogella.com/blog/ai-agent-orchestration</id><content type="html" xml:base="https://vogella.com/blog/ai-agent-orchestration/"><![CDATA[<p>When talking about AI agents, we now often refer to <em>Agentic AI workflows</em>, where multiple agents collaborate to achieve complex goals. When creating <em>Custom Agents</em>, you need to consider collaboration options, especially if you design your agents for dedicated tasks rather than having one agent do everything. This is important because you should keep the context of your agent or prompt as minimal as possible while still providing enough information to achieve the desired results. Creating agents for dedicated tasks with a limited scope/context is similar to the encapsulation principle in object-oriented programming, and similarly, you need to consider processing or orchestration patterns.</p>

<p>Apart from a <em>Single Agent</em> that does all the work alone, you currently have two options:</p>

<ul>
  <li><em>Delegate Pattern</em><br />
Create guided sequential workflows that transition between agents. The agents are called one after the other.</li>
  <li><em>Coordinator and Worker Pattern</em><br />
The main/coordinator agent receives the task, delegates subtasks to subagents, and combines the subagent results into the final result.</li>
</ul>

<p>You can also combine these patterns to have multiple “main” agents that use worker agents for specific tasks. Each “main” agent can delegate to the next “main” agent once it is done, creating a sequential workflow of main tasks.</p>

<p>I will explain these patterns and show the differences between Visual Studio Code and Eclipse Theia using the following example: get a list of links for a specific topic. To get those links, first check a GitHub user’s gists for a publication collection. Then inspect the list of publications for links contained in the posts.</p>

<h2 id="visual-studio-code">Visual Studio Code</h2>

<p>In the following sections, I describe how to create <em>Custom Agents</em> in Visual Studio Code and compare the different orchestration patterns.</p>

<h3 id="github-mcp-server">GitHub MCP Server</h3>

<p>As explained earlier, I want to set up an example process where the first step is to retrieve a list of publications from a GitHub Gist. For this, we need to configure the <em>GitHub MCP Server</em> with the <em>gists</em> toolset enabled.</p>

<ul>
  <li>Open the file <em>.vscode/mcp.json</em> (create one if you do not already have that file in your workspace)
    <ul>
      <li>Add the following full configuration (or merge the <code class="language-plaintext highlighter-rouge">github</code> server entry into your existing <code class="language-plaintext highlighter-rouge">servers</code> object):</li>
    </ul>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"servers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sse"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"X-MCP-Toolsets"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gists"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"inputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>

    <p>If you already have other servers configured in <em>.vscode/mcp.json</em>, keep them and only add the <code class="language-plaintext highlighter-rouge">github</code> entry under <code class="language-plaintext highlighter-rouge">servers</code>.</p>

    <p>This adds the <a href="https://github.com/github/github-mcp-server/blob/main/docs/remote-server.md">Remote GitHub MCP Server</a> with the <em>gists</em> toolset and OAuth authentication.</p>

    <p><em><strong>Note:</strong></em><br />
If you want to use a Personal Access Token (PAT) for the authorization instead of OAuth, have a look at <a href="https://vogella.com/blog/vscode_copilot_extension/#remote-mcp-server-with-authorization">Extending Copilot in Visual Studio Code - Remote MCP Server with authorization</a>.</p>
  </li>
  <li>In the editor, you will see actions provided as <em>CodeLens</em> that let you interact with the server. Click <em>Start</em> to start the GitHub MCP server.
    <figure class="">
<img src="/blog/assets/images/copilot_mcp_github_gists.png" alt="" /></figure>

    <p>The first time, you will be prompted via dialog to authenticate with GitHub. After clicking <em>Allow</em>, a website opens where you can log in to the account you want to connect the MCP server to.</p>
    <figure class="">
<img src="/blog/assets/images/copilot_github_authenticate.png" alt="" /></figure>

    <p>After the authentication succeeds, the server starts and the capabilities and tools provided by the server are discovered.</p>
  </li>
</ul>

<h3 id="single-agent">Single Agent</h3>

<p>We start by creating a single <em>Custom Agent</em> that performs all steps itself. This agent will then be split to explain the orchestration patterns.</p>

<ul>
  <li>Create a new <em>Custom Agent</em> that executes the previously described process to provide the user with a collection of links for a specific topic.
    <ul>
      <li>
        <p>In the Copilot chat window, click the gear icon in the upper right corner (<em>Configure Chat…</em>) and select<br />
<em>Custom Agents</em> -&gt; <em>Create new custom agent…</em> -&gt; <em>.github/agents</em> -&gt; name: research</p>

        <p>This creates the file <em>.github/agents/research.agent.md</em></p>
      </li>
    </ul>
  </li>
  <li>Add the tools <code class="language-plaintext highlighter-rouge">web/fetch</code> and <code class="language-plaintext highlighter-rouge">github/list_gists</code></li>
  <li>Add a prompt that defines the steps to process</li>
  <li>
    <p>The following snippet shows how such an agent could look like</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">collection</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">links</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">specific</span><span class="nv"> </span><span class="s">topic."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">web/fetch</span><span class="pi">,</span> <span class="nv">github/list_gists</span><span class="pi">]</span>
<span class="nn">---</span>

You are an agent that helps the developer by extracting and providing links mentioned in blog posts.

To provide the necessary links execute the following steps:
<span class="p">
1.</span> Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use #tool:github/list_gists to find the correct gist.
<span class="p">2.</span> Use #tool:web/fetch to fetch the content of the gist with a max-length parameter of 15000.
<span class="p">3.</span> Filter the fetched content for links about the requested information.
<span class="p">4.</span> For every found blog post, use #tool:web/fetch to fetch the content of the given blog post with a max-length parameter of 15000.
<span class="p">5.</span> Collect all links that are mentioned in the blog post and relevant for the topic.
<span class="p">6.</span> Filter out duplicate links and links that are not relevant for the topic. Relevance can be determined by the presence of keywords related to the topic in the context of the link.
<span class="p">7.</span> Provide a collection of the extracted filtered links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.
</code></pre></div>    </div>
  </li>
</ul>

<p><em><strong>Hint:</strong></em><br />
The built-in <code class="language-plaintext highlighter-rouge">web/fetch</code> tool asks for approval to execute <code class="language-plaintext highlighter-rouge">fetch</code> and to access the URLs it wants to retrieve, ensuring no malicious content is fetched.
If you use the custom agent prompts that I prepared, it will fetch my gist with my publications and blog posts published at <a href="https://vogella.com/blog/">https://vogella.com/blog/</a>. If you trust these sources (at least I do :smile:), you can configure trust in <em>settings.json</em> by adding the following configuration to reduce the number of prompts during processing:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"chat.tools.urls.autoApprove"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"https://vogella.com/blog/"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">research</code> by selecting it in the agents dropdown in the chat view, then enter a prompt, for example <code class="language-plaintext highlighter-rouge">show links about visual studio code</code>.
    <figure class="">
<img src="/blog/assets/images/copilot_select_custom_agent_research.png" alt="" /></figure>

    <ul>
      <li>When asked to allow fetching the gist and the content of the gist, select <em>Allow and Review Once</em> for the first request, and <em>Allow Once</em> afterwards.<br />
Further information can be found in the official documentation: <a href="https://code.visualstudio.com/docs/copilot/agents/agent-tools#_url-approval">Use tools with agents - URL approval</a></li>
    </ul>
  </li>
</ul>

<p>After the agent finishes its task, you can <a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat-context#_monitor-context-window-usage">monitor the context window usage</a>.
The following screenshots show the context window usage when I ran the <em>Custom Agent</em> with Gemini 2.5 Pro and GPT-5.3-Codex. The values might be different when executing it again.</p>

<p>This is the context window usage with <em>Gemini 2.5 Pro</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_single_gemini.png" alt="" /></figure>

<p>This is the context window usage with <em>GPT-5.3-Codex</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_single_codex.png" alt="" /></figure>

<h3 id="delegate-pattern">Delegate Pattern</h3>

<p>The <em>Delegate Pattern</em> is supported via <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents#_handoffs">Handoffs</a> when creating <em>Custom Agents</em> in Visual Studio Code. For this scenario, we split the previous <em>research</em> agent into two <em>Custom Agents</em>, one per task:</p>

<ul>
  <li>One agent to get the information from a gist to find the blog posts</li>
  <li>
    <p>One agent to extract the links from the found blog posts</p>
  </li>
  <li>Create a new <em>Custom Agent</em> that extracts links from the text of a given blog post.
    <ul>
      <li>
        <p>In the Copilot chat window, click the gear icon in the upper right corner (<em>Configure Chat…</em>) and select<br />
<em>Custom Agents</em> -&gt; <em>Create new custom agent…</em> -&gt; <em>.github/agents</em> -&gt; name: link_extractor</p>

        <p>This creates the file <em>.github/agents/link_extractor.agent.md</em></p>
      </li>
    </ul>
  </li>
  <li>Add the built-in <code class="language-plaintext highlighter-rouge">web/fetch</code> tool to fetch the content</li>
  <li>Add a prompt that defines the steps to process</li>
  <li>
    <p>The following snippet shows how such an agent could look like</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">list</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">links</span><span class="nv"> </span><span class="s">extracted</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">blog</span><span class="nv"> </span><span class="s">posts."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">web/fetch</span><span class="pi">]</span>
<span class="nn">---</span>

You are an agent that helps the developer by extracting links mentioned in a blog post and providing them in a structured format.

To provide the necessary links execute the following steps:
<span class="p">
1.</span> Use #tool:web/fetch to fetch the content of the given blog post with a max-length parameter of 15000.
<span class="p">2.</span> Collect all links that are mentioned in the blog post and relevant for the topic.
<span class="p">3.</span> Filter out duplicate links and links that are not relevant for the topic. Relevance can be determined by the presence of keywords related to the topic in the context of the link.
<span class="p">4.</span> Provide a collection of the extracted filtered links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.
</code></pre></div>    </div>
  </li>
  <li>Create a new <em>Custom Agent</em> that is able to retrieve information from a <em>gist</em>.
    <ul>
      <li>
        <p>In the Copilot chat window, click the gear icon in the upper right corner (<em>Configure Chat…</em>) and select<br />
<em>Custom Agents</em> -&gt; <em>Create new custom agent…</em> -&gt; <em>.github/agents</em> -&gt; name: gists</p>

        <p>This creates the file <em>.github/agents/gists.agent.md</em></p>
      </li>
    </ul>
  </li>
  <li>Add the GitHub MCP tool <code class="language-plaintext highlighter-rouge">github/list_gists</code> to list the gists</li>
  <li>Add the built-in <code class="language-plaintext highlighter-rouge">web/fetch</code> tool to fetch the content</li>
  <li>Configure a <code class="language-plaintext highlighter-rouge">handoff</code> to delegate processing to the <code class="language-plaintext highlighter-rouge">link_extractor</code> agent</li>
  <li>
    <p>The following snippet shows how such an agent could look like</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">list</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">links</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">blog</span><span class="nv"> </span><span class="s">posts</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">GitHub</span><span class="nv"> </span><span class="s">Gist."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">github/list_gists</span><span class="pi">,</span> <span class="nv">web/fetch</span><span class="pi">]</span>
<span class="na">handoffs</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">label</span><span class="pi">:</span> <span class="s">Extract Links from posts</span>
    <span class="na">agent</span><span class="pi">:</span> <span class="s">link_extractor</span>
    <span class="na">prompt</span><span class="pi">:</span> <span class="s">Extract links from the given list of blog posts</span>
    <span class="na">send</span><span class="pi">:</span> <span class="kc">true</span>
<span class="nn">---</span>

You are an agent that helps the developer by providing links to blog posts.

To provide the necessary links execute the following steps:
<span class="p">
1.</span> Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use #tool:github/list_gists to find the correct gist.
<span class="p">2.</span> Use #tool:web/fetch to fetch the content of the gist with a max-length parameter of 15000.
<span class="p">3.</span> Filter the fetched content for links about the requested information.
<span class="p">4.</span> Provide a list of links to the relevant blog posts.
</code></pre></div>    </div>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">gists</code> by selecting it in the agents dropdown in the chat view, then enter a prompt, for example <code class="language-plaintext highlighter-rouge">show links about visual studio code</code>.
    <figure class="">
<img src="/blog/assets/images/copilot_select_custom_agent_gists.png" alt="" /></figure>

    <ul>
      <li>When asked to allow fetching the gist and the content of the gist, select <em>Allow and Review Once</em> for the first request, and <em>Allow Once</em> afterwards.<br />
Further information can be found in the official documentation: <a href="https://code.visualstudio.com/docs/copilot/agents/agent-tools#_url-approval">Use tools with agents - URL approval</a></li>
      <li>After the <code class="language-plaintext highlighter-rouge">gists</code> agent finishes its task, the user must actively proceed with the handoff by clicking the <em>Proceed</em> button in the chat.
        <figure class="">
<img src="/blog/assets/images/copilot_handoff_proceed_gists.png" alt="" /></figure>
      </li>
      <li>You can see that the agent switches to the <code class="language-plaintext highlighter-rouge">link_extractor</code> agent in the chat
        <figure class="">
<img src="/blog/assets/images/copilot_select_custom_agent_link_extractor.png" alt="" /></figure>
      </li>
    </ul>
  </li>
</ul>

<p>After the agent finishes its task, you can <a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat-context#_monitor-context-window-usage">monitor the context window usage</a>.
The following screenshots show the context window usage when I ran the <em>Custom Agent</em> with Gemini 2.5 Pro and GPT-5.3-Codex. The values might be different when executing it again.</p>

<p>This is the context window usage with <em>Gemini 2.5 Pro</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_delegate_gemini.png" alt="" /></figure>

<p>This is the context window usage with <em>GPT-5.3-Codex</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_delegate_codex.png" alt="" /></figure>

<p>You can see that the context window looks quite similar to executing the process with a single agent. The reason is that we stay in the same conversation, so the relevant context is largely the same. This means the context contains information from the first agent that is also available to the second agent. The <em>Delegate Pattern</em> therefore helps create a better structure for a multi-agent system and reusable agents for various scenarios via “encapsulation”, but it has little to no effect on token usage compared to the single-agent solution.</p>

<h3 id="coordinator-and-worker-pattern">Coordinator and Worker Pattern</h3>

<p>In Visual Studio Code, you implement the <a href="https://code.visualstudio.com/docs/copilot/agents/subagents#_coordinator-and-worker-pattern">Coordinator and Worker Pattern</a> by using <a href="https://code.visualstudio.com/docs/copilot/agents/subagents">Subagents</a>. Using a subagent means spawning a child agent within a session to handle a subtask in its own isolated context window. From a pattern perspective, this means there is a main coordinator agent that manages the overall task and delegates subtasks to specialized subagents. To call a subagent, the built-in <code class="language-plaintext highlighter-rouge">agent/runSubagent</code> tool needs to be enabled for the coordinator agent. Each subagent call is sequential (the coordinator waits for that call to return), but the coordinator can spawn multiple subagent calls in parallel.</p>

<p>In this section, the previously created agents are converted into coordinator and worker agents.</p>

<ul>
  <li>Open the file <em>.github/agents/gists.agent.md</em></li>
  <li>Remove the <code class="language-plaintext highlighter-rouge">handoffs</code> header</li>
  <li>Ensure that the agent returns something at the end</li>
  <li>
    <p>The following snippet shows how such an agent could look like</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">list</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">links</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">blog</span><span class="nv"> </span><span class="s">posts</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">GitHub</span><span class="nv"> </span><span class="s">Gist."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">github/list_gists</span><span class="pi">,</span> <span class="nv">web/fetch</span><span class="pi">]</span>
<span class="nn">---</span>

You are an agent that helps the developer by providing links to blog posts.

To provide the necessary links execute the following steps:
<span class="p">
1.</span> Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use #tool:github/list_gists to find the correct gist.
<span class="p">2.</span> Use #tool:web/fetch to fetch the content of the gist with a max-length parameter of 15000.
<span class="p">3.</span> Filter the fetched content for links about the requested information.
<span class="p">4.</span> Provide a list of links to the relevant blog posts.
</code></pre></div>    </div>
  </li>
  <li>Open the file <em>.github/agents/research.agent.md</em></li>
  <li>Add the built-in <code class="language-plaintext highlighter-rouge">agent</code> tool to call subagents and remove the other tools</li>
  <li>Configure the agents <code class="language-plaintext highlighter-rouge">gists</code> and <code class="language-plaintext highlighter-rouge">link_extractor</code> as agents that can be used as subagents</li>
  <li>
    <p>The following snippet shows how such an agent could look like</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">collection</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">links</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">specific</span><span class="nv"> </span><span class="s">topic."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">agent</span><span class="pi">]</span>
<span class="na">agents</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">gists"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">link_extractor"</span><span class="pi">]</span>
<span class="nn">---</span>

You are an agent that helps the developer by providing links to blog posts about a specific topic.
To provide the necessary links use subagents to execute the following steps:
<span class="p">
1.</span> Use the gists subagent to fetch a collection of blog posts about the specific topic.
<span class="p">2.</span> For each of the found blog posts use the link_extractor subagent to fetch the content of the blog post and extract all links that are mentioned in the blog post.
<span class="p">3.</span> Provide a collection of the extracted links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.
</code></pre></div>    </div>
  </li>
  <li>
    <p>The <code class="language-plaintext highlighter-rouge">link_extractor</code> agent can stay as it is, since it does not specify a <code class="language-plaintext highlighter-rouge">handoff</code> and already returns the result in its prompt.</p>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">research</code> by selecting it in the agents dropdown in the chat view, then enter a prompt, for example <code class="language-plaintext highlighter-rouge">show links about visual studio code</code>.
    <figure class="">
<img src="/blog/assets/images/copilot_select_custom_agent_research.png" alt="" /></figure>
  </li>
</ul>

<p>When watching the execution, you should notice that:</p>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">research</code> agent stays active as the coordinator agent</li>
  <li>While the subagents are called, the coordinator waits until they are done</li>
  <li>The <code class="language-plaintext highlighter-rouge">link_extractor</code> agent is called multiple times, once per found blog post. These multiple agent calls are executed in parallel, while the coordinator agent waits until all spawned child agents are finished.</li>
</ul>

<p>After the agent finishes its task, you can <a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat-context#_monitor-context-window-usage">monitor the context window usage</a>.
The following screenshots show the context window usage when I ran the <em>Custom Agent</em> with Gemini 2.5 Pro and GPT-5.3-Codex. The values might be different when executing it again.</p>

<p>This is the context window usage with <em>Gemini 2.5 Pro</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_subagents_gemini.png" alt="" /></figure>

<p>This is the context window usage with <em>GPT-5.3-Codex</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_subagents_codex.png" alt="" /></figure>

<p>You can see that the context window is smaller compared to the other patterns, which can be explained by the fact that each subagent is executed in its own isolated context window.</p>

<h2 id="eclipse-theia">Eclipse Theia</h2>

<p>If you are new to Eclipse Theia and want to try out the examples in the following sections, you have three options:</p>

<ul>
  <li><a href="https://try.theia-cloud.io/">Try Theia IDE online</a></li>
  <li><a href="https://theia-ide.org/#theiaidedownload">Get Theia IDE for desktop</a></li>
  <li>Build your own Theia application<br />
Have a look at <a href="https://vogella.com/blog/theia_getting_started/">Getting Started with Eclipse Theia</a> and <a href="https://vogella.com/blog/theia_ai_getting_started/">Getting Started with Theia AI</a> if you are interested in that topic.</li>
</ul>

<p>For the following sections, I assume that you have a Theia application with AI support. To enable AI features inside a Theia application, such as Theia IDE, you need access to an LLM and must configure it accordingly. I will give an example using the Google Gemini free tier. This only works if you have a Google account. If you want to use another LLM, have a look at the <a href="https://theia-ide.org/docs/user_ai/#llm-providers-overview">LLM Providers Overview</a> to see which LLMs are currently supported by Theia. If you have a subscription for an LLM that is not listed here, check whether it is compatible with the <a href="https://github.com/openai/openai-node">OpenAI API</a> and try to configure it as an <a href="https://theia-ide.org/docs/user_ai/#openai-compatible-models-eg-via-vllm">OpenAI Compatible Model</a>.</p>

<ul>
  <li>If you have a Google account, you can try out the <a href="https://aistudio.google.com/">Google AI Studio</a>
    <ul>
      <li>Create a new project via <a href="https://aistudio.google.com/projects">Google AI Studio - Projects</a>
        <ul>
          <li>Click on <strong>Create a new project</strong></li>
          <li>Provide a name like <em>theia-evaluation</em></li>
          <li>Click on <strong>Create project</strong></li>
        </ul>
      </li>
      <li>Create a new API key via <a href="https://aistudio.google.com/api-keys">Google AI Studio - API Keys</a>
        <ul>
          <li>Click on <strong>Create API Key</strong></li>
          <li>Give the key a name like <em>theia</em></li>
          <li>Select the previously created <em>theia-evaluation</em> project</li>
          <li>Click on <strong>Create key</strong></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Switch to the Theia application (e.g. the Theia IDE)
    <ul>
      <li>Open the settings page by pressing <strong>CTRL</strong> + <strong>,</strong>
        <ul>
          <li>Select the <strong>AI Features</strong></li>
          <li>Check <strong>Enable AI</strong></li>
          <li>Configure the LLM you want to use
            <ul>
              <li>Google
                <ul>
                  <li><strong>Api Key</strong>: Copy and paste your Google AI API key (see above)<br />
For this tutorial we simply configure the API key via preferences as it is easier than setting up the environment.
In a productive environment you should use the environment variable <code class="language-plaintext highlighter-rouge">GOOGLE_API_KEY</code> to set the key securely.</li>
                  <li><strong>Models</strong>: Ensure to have models in the list that are currently available according to <a href="https://ai.google.dev/gemini-api/docs/models">Gemini Models</a></li>
                </ul>
              </li>
            </ul>
          </li>
          <li>Configure the <em>Model Aliases</em>
            <ul>
              <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
              <li>Switch to the <em>Model Aliases</em> tab</li>
              <li>For every model alias, select the model that you configured, e.g. <code class="language-plaintext highlighter-rouge">google/gemini-3.1-flash-lite-preview</code></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>Since version 1.68.0 Theia also supports a <a href="https://github.com/eclipse-theia/theia/pull/16841">GitHub Copilot language model integration</a>.
To try it out, you need to authenticate with GitHub</p>

<ul>
  <li>Click on <em>Sign in to GitHub Copilot</em> in the footer of the Theia application
    <figure class="">
<img src="/blog/assets/images/theia_copilot_footer.png" alt="" /></figure>
  </li>
  <li>This will open the following window
    <figure class="">
<img src="/blog/assets/images/theia_copilot_signin.png" alt="" /></figure>
  </li>
  <li><strong>Copy</strong> the key</li>
  <li>Click on <em>Open GitHub</em></li>
  <li>Follow the instructions on the GitHub website to grant the device permission</li>
  <li>After the device activation is successful, switch back to the Theia browser window and click on <em>I have authorized</em> to finish the authentication process in Theia</li>
  <li>After successful authentication, you can select a Copilot model as <em>Model Alias</em>, e.g. <code class="language-plaintext highlighter-rouge">copilot/gpt-4o</code></li>
</ul>

<p><em><strong>Note:</strong></em><br />
At the time of writing this article, the only Copilot models that are working in Theia are <code class="language-plaintext highlighter-rouge">gpt-4o</code> and <code class="language-plaintext highlighter-rouge">gpt-4o-mini</code>. Using other models results in the following issue: <a href="https://github.com/eclipse-theia/theia-ide/issues/675">AI Chat with GitHub Copilot fails with: 400 The requested model is not supported</a>.</p>

<h3 id="mcp-server-configuration">MCP Server Configuration</h3>

<p>To set up the example process like in Visual Studio Code to retrieve a list of publications from a GitHub Gist and then fetch the data for further processing, we need to configure the necessary MCP server. This is again the <em>GitHub MCP Server</em> with the <em>gists</em> toolset enabled, and the <em>fetch MCP Server</em> as Theia does not provide a built-in fetch tool.</p>

<ul>
  <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em>
    <ul>
      <li>Switch to the <em>MCP Servers</em> tab</li>
      <li>Add the <a href="https://github.com/modelcontextprotocol/servers/tree/main/src/fetch">Fetch MCP Server</a> as a remote MCP Server
        <ul>
          <li>Click on <em>Add MCP Server</em></li>
          <li>Set the following values in the dialog
            <ul>
              <li><strong>Server Name:</strong> <em>fetch</em></li>
              <li><strong>Server Type:</strong> <em>Remote (URL)</em></li>
              <li><strong>Server URL:</strong> <em>https://remote.mcpservers.org/fetch/mcp</em></li>
              <li>Keep the <strong>Autostart</strong> flag checked</li>
            </ul>
          </li>
          <li>Click <em>Add Server</em></li>
        </ul>

        <figure class="">
<img src="/blog/assets/images/theia_mcp_add_fetch.png" alt="" /></figure>
      </li>
      <li>Add the <a href="https://github.com/github/github-mcp-server/blob/main/docs/remote-server.md">Remote GitHub MCP Server</a> with the <em>gists</em> toolset
        <ul>
          <li>Click on <em>Add MCP Server</em></li>
          <li>Set the following values in the dialog
            <ul>
              <li><strong>Server Name:</strong> <em>github</em></li>
              <li><strong>Server Type:</strong> <em>Remote (URL)</em></li>
              <li><strong>Server URL:</strong> <em>https://api.githubcopilot.com/mcp/x/gists</em></li>
              <li><strong>Auth Token:</strong> Your Personal Access Token (PAT) with <strong>repo</strong> scope. See <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic">Creating a personal access token (classic)</a> if you do not already have a PAT.</li>
              <li>Keep the <strong>Autostart</strong> flag checked</li>
            </ul>
          </li>
          <li>Click <em>Add Server</em></li>
        </ul>

        <figure class="">
<img src="/blog/assets/images/theia_mcp_add_github.png" alt="" /></figure>
      </li>
    </ul>
  </li>
  <li>Instead of configuring everything via user interface, you can also directly paste one of the following configurations directly in the settings JSON
    <ul>
      <li>Switch to the JSON view of the settings by clicking the curly braces on the upper right corner of the editor (<em>Open Settings (JSON)</em>)</li>
      <li>Alternatively use the <em>Command Palette</em> (F1) and search for <em>Preferences: Open Settings (JSON)</em></li>
      <li>Copy the following snippet and paste it in the editor
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"window.titleBarStyle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"custom"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.AiEnable.enableAI"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.google.apiKey"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;your-api-key&gt;"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.google.models"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"gemini-3.1-flash-lite-preview"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-3-flash-preview"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-2.5-flash"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-2.5-flash-lite"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-2.5-pro"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"ai-features.languageModelAliases"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"default/code"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-3.1-flash-lite-preview"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/universal"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-3.1-flash-lite-preview"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/code-completion"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-3.1-flash-lite-preview"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/summarize"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-3.1-flash-lite-preview"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"ai-features.chat.defaultChatAgent"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Universal"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.mcp.mcpServers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"fetch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://remote.mcpservers.org/fetch/mcp"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"autostart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/x/gists"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"autostart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"serverAuthToken"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;your-github-pat&gt;"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>Replace <code class="language-plaintext highlighter-rouge">&lt;your-api-key&gt;</code> with your Google AI Studio AI key and <code class="language-plaintext highlighter-rouge">&lt;your-github-pat&gt;</code> with your PAT.</li>
    </ul>
  </li>
</ul>

<p>After performing the above steps, you should see the two MCP servers in the overview and they should directly be <em>Connected</em> as the servers are configured to autostart.</p>

<h3 id="single-agent-1">Single Agent</h3>

<p>We again first create a single <em>Custom Agent</em> that performs all steps itself. This agent will then be split to explain the orchestration patterns.</p>

<ul>
  <li>Create a new <em>Custom Agent</em> that executes the previously described process to provide the user with a collection of links for a specific topic.
    <ul>
      <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
      <li>Switch to the <em>Agents</em> tab</li>
      <li>Click on <strong>Add Custom Agent</strong>
        <figure class="">
<img src="/blog/assets/images/theia_add_custom_agent.png" alt="" /></figure>
      </li>
      <li>Select the <em>.prompts</em> folder of the current workspace</li>
      <li>Verify that a <em>.prompts</em> folder is generated in your workspace that contains a <em>customAgents.yml</em> file</li>
      <li>Define a custom agent with the name <em>Research</em> by defining the following information
        <ul>
          <li><em>id</em>: A unique identifier for the agent.</li>
          <li><em>name</em>: The display name of the agent.</li>
          <li><em>description</em>: A brief explanation of what the agent does.</li>
          <li><em>prompt</em>: The default prompt that the agent will use for processing requests.</li>
          <li><em>defaultLLM</em>: The language model used by default.</li>
          <li><em>showInChat</em>: Whether the agent should be shown in the chat UI. This one is optional and defaults to <code class="language-plaintext highlighter-rouge">true</code>.</li>
        </ul>
      </li>
      <li>Replace the content of the <em>customAgents.yml</em> with the following snippet</li>
    </ul>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Research</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Research</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a collection of links for a specific topic.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by extracting and providing links mentioned in blog posts.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use ~{mcp_github_list_gists} to find the correct gist.</span>
    <span class="s">2. Use ~{mcp_fetch_fetch} to fetch the content of the gist with a max-length parameter of 15000.</span>
    <span class="s">3. Filter the fetched content for links about the requested information.</span>
    <span class="s">4. For every found blog post, use ~{mcp_fetch_fetch} to fetch the content of the given blog post with a max-length parameter of 15000.</span>
    <span class="s">5. Collect all links that are mentioned in the blog post and relevant for the topic.</span>
    <span class="s">6. Filter out duplicate links and links that are not relevant for the topic. Relevance can be determined by the presence of keywords related to the topic in the context of the link.</span>
    <span class="s">7. Provide a collection of the extracted filtered links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>

    <p>Compared to a custom agent in Visual Studio Code, we do not need to configure which tools we want to use in the prompt. They can simply be referenced in the prompt via <code class="language-plaintext highlighter-rouge">~&lt;tool-name&gt;</code>.</p>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">Research</code> by selecting it in the chat prompt via <code class="language-plaintext highlighter-rouge">@</code> syntax and add the prompt to execute, for example <code class="language-plaintext highlighter-rouge">@Research show links about theia</code>.
    <figure class="">
<img src="/blog/assets/images/theia_select_custom_agent_research.png" alt="" /></figure>
  </li>
</ul>

<p>Theia does not yet support context window monitoring like Visual Studio Code, so we cannot inspect detailed context usage. This feature has been requested via <a href="https://github.com/eclipse-theia/theia/issues/16779">Context window inspection / analysis command</a>. For several LLMs, token usage can still be inspected via <em>AI Configuration</em> by switching to the <em>Token Usage</em> tab. I tested the example using <em>GPT-5.3-Codex</em> hosted on Azure, <em>GPT-4o</em> via Copilot, and <em>Gemini 3.1 Flash Lite Preview</em> via Google AI Studio in the Free Tier.</p>

<figure class="">
  <img src="/blog/assets/images/theia_token_single.png" alt="" /></figure>

<p><em><strong>Note:</strong></em><br />
The token counts reported for Gemini models are incorrect in Theia 1.69.0. I created the ticket <a href="https://github.com/eclipse-theia/theia/issues/17165">Token usage shows incorrect values for Gemini models</a> and submitted a pull request that fixes this issue. The screenshot above shows token usage with the fix applied for a fair comparison.</p>

<p>Token usage is not persisted and is reset when restarting the application. To get a better comparison, I restart after each example.</p>

<h3 id="delegate-pattern-1">Delegate Pattern</h3>

<p>Theia does not provide a feature like the <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents#_handoffs">Handoffs</a> in Visual Studio Code. Instead Theia provides the built-in <em>Tool Function</em> <code class="language-plaintext highlighter-rouge">delegateToAgent</code> to support <a href="https://theia-ide.org/docs/user_ai/#agent-to-agent-delegation">Agent-to-Agent Delegation</a>. To show this scenario, we split the previous <em>Research</em> agent into two <em>Custom Agents</em>, one per task:</p>

<ul>
  <li>One agent to get the information from a gist to find the blog posts</li>
  <li>One agent to extract the links from the found blog posts</li>
</ul>

<p>In Eclipse Theia, multiple <a href="https://theia-ide.org/docs/user_ai/#custom-agents">Custom Agents</a> are configured in a single custom agent configuration file. We therefore add the new agents to the previously created <em>customAgents.yml</em>.</p>

<ul>
  <li>Open the <em>.prompts/customAgents.yml</em> file</li>
  <li>Add a new <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent
    <ul>
      <li>Use the <em>MCP Tool</em> <code class="language-plaintext highlighter-rouge">mcp_fetch_fetch</code> to fetch the content of the blog posts</li>
      <li>Add a prompt that defines the steps to process</li>
      <li>The following snippet shows how such an agent could look like</li>
    </ul>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Link_Extractor</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Link_Extractor</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of links extracted from blog posts.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by extracting links mentioned in blog posts and providing them in a structured format.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Iterate over the list of provided blog posts</span>
    <span class="s">2. For every blog post use ~{mcp_fetch_fetch} to fetch the content of the blog post with a max-length parameter of 15000.</span>
    <span class="s">3. Collect all links that are mentioned in the blog post and relevant for the topic.</span>
    <span class="s">4. Filter out duplicate links and links that are not relevant for the topic. Relevance can be determined by the presence of keywords related to the topic in the context of the link.</span>
    <span class="s">5. Provide a collection of the extracted filtered links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>
  </li>
  <li>Add a new <code class="language-plaintext highlighter-rouge">Gists</code> agent
    <ul>
      <li>Use the <em>MCP Tool</em> <code class="language-plaintext highlighter-rouge">mcp_github_list_gists</code> to list the gists</li>
      <li>Use the <em>MCP Tool</em> <code class="language-plaintext highlighter-rouge">mcp_fetch_fetch</code> to fetch the content</li>
      <li>Use the Theia built-in <em>Tool Function</em> <code class="language-plaintext highlighter-rouge">delegateToAgent</code> to delegate processing to the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent</li>
      <li>The following snippet shows how such an agent could look like</li>
    </ul>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of links to blog posts from a GitHub Gist.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by providing links to blog posts.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use ~{mcp_github_list_gists} to find the correct gist.</span>
    <span class="s">2. Use ~{mcp_fetch_fetch} to fetch the content of the gist with a max-length parameter of 15000.</span>
    <span class="s">3. Filter the fetched content for links about the requested information.</span>
    <span class="s">4. Provide a list of links to the relevant blog posts.</span>
    <span class="s">5. Pass the provided list of links to the Link_Extractor agent via ~{delegateToAgent} to extract links from the given list of blog posts</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">Gists</code> by selecting it in the chat prompt via <code class="language-plaintext highlighter-rouge">@</code> syntax and add the prompt to execute, for example <code class="language-plaintext highlighter-rouge">@Gists show links about theia</code>.
    <figure class="">
<img src="/blog/assets/images/theia_select_custom_agent_gists.png" alt="" /></figure>
  </li>
</ul>

<p>You can see in the chat response that the <code class="language-plaintext highlighter-rouge">Gists</code> agent stays the active agent, and <code class="language-plaintext highlighter-rouge">Link_Extractor</code> is called as part of it. So it is not actually a <em>Handoff</em> like in Visual Studio Code, where the active agent really switches.</p>

<figure class="">
  <img src="/blog/assets/images/theia_delegate_response.png" alt="" /></figure>

<p>Theia does not yet support context window monitoring like Visual Studio Code, so we cannot inspect detailed context usage. This feature has been requested via <a href="https://github.com/eclipse-theia/theia/issues/16779">Context window inspection / analysis command</a>. For several LLMs, token usage can still be inspected via <em>AI Configuration</em> by switching to the <em>Token Usage</em> tab. I tested the example using <em>GPT-5.3-Codex</em> hosted on Azure, <em>GPT-4o</em> via Copilot, and <em>Gemini 3.1 Flash Lite Preview</em> via Google AI Studio in the Free Tier.</p>

<figure class="">
  <img src="/blog/assets/images/theia_token_delegate.png" alt="" /></figure>

<p><em><strong>Note:</strong></em><br />
The token counts reported for Gemini models are incorrect in Theia 1.69.0. I created the ticket <a href="https://github.com/eclipse-theia/theia/issues/17165">Token usage shows incorrect values for Gemini models</a> and submitted a pull request that fixes this issue. The screenshot above shows token usage with the fix applied for a fair comparison.</p>

<p>Interestingly, token usage for the <em>Delegate Pattern</em> in Theia is slightly higher compared to the single-agent solution.
It is also interesting that the <em>Delegate Pattern</em> is not exactly the same as in Visual Studio Code via <em>Handoffs</em>. It seems that <em>Agent-to-Agent Delegation</em> is similar to <em>Subagents</em> in Visual Studio Code, at least based on the chat output.</p>

<p>At the time of writing this blog post, Theia does not support an automatic <em>Handoff</em> similar to Visual Studio Code. But you can try to simulate this manually.</p>

<ul>
  <li>Update the <code class="language-plaintext highlighter-rouge">Gists</code> agent
    <ul>
      <li>Remove the last step that passes the processing to the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent</li>
      <li>The following snippet shows how such an agent could look like</li>
    </ul>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of links to blog posts from a GitHub Gist.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by providing links to blog posts.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use ~{mcp_github_list_gists} to find the correct gist.</span>
    <span class="s">2. Use ~{mcp_fetch_fetch} to fetch the content of the gist with a max-length parameter of 15000.</span>
    <span class="s">3. Filter the fetched content for links about the requested information.</span>
    <span class="s">4. Provide a list of links to the relevant blog posts.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">Gists</code> by selecting it in the chat prompt via <code class="language-plaintext highlighter-rouge">@</code> syntax and add the prompt to execute, for example <code class="language-plaintext highlighter-rouge">@Gists show links about theia</code>.
    <figure class="">
<img src="/blog/assets/images/theia_select_custom_agent_gists.png" alt="" /></figure>
  </li>
  <li>Once the <code class="language-plaintext highlighter-rouge">Gists</code> agent is done, use the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent by selecting it in the chat prompt via <code class="language-plaintext highlighter-rouge">@</code> syntax and add the prompt to execute, for example <code class="language-plaintext highlighter-rouge">@Link_Extractor fetch the previously found blog posts and extract further links</code>.
    <figure class="">
<img src="/blog/assets/images/theia_select_custom_agent_link_extractor.png" alt="" /></figure>
  </li>
</ul>

<p>As a result of manually changing the active agent, <code class="language-plaintext highlighter-rouge">Link_Extractor</code> is now the active agent.</p>

<figure class="">
  <img src="/blog/assets/images/theia_token_delegate_manually.png" alt="" /></figure>

<p>The token usage increased as the whole conversation is passed to the next agent as context, and the token usage is accumulated over the whole session. Therefore the <em>Input Tokens</em> and the <em>Output Tokens</em> are higher than compared to using the <em>Agent-to-Agent Delegation</em> with an isolated context or the <em>Single Agent</em>, where only a single request is processed.</p>

<h3 id="coordinator-and-worker-pattern-1">Coordinator and Worker Pattern</h3>

<p>In Eclipse Theia, you implement the <em>Coordinator and Worker Pattern</em> by using <em>Subagents</em> via the built-in <em>Tool Function</em> <code class="language-plaintext highlighter-rouge">delegateToAgent</code>. Using a subagent means spawning a child agent within a session to handle a subtask in its own isolated context window. This can be seen in the <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-chat/src/browser/agent-delegation-tool.ts"><code class="language-plaintext highlighter-rouge">delegateToAgent</code> implementation</a>. From a pattern perspective, this means there is a main coordinator agent that manages the overall task and delegates subtasks to specialized subagents. Each subagent call is sequential (the coordinator waits for that call to return), but the coordinator can spawn multiple subagent calls in parallel.</p>

<p>In this section, the previously created agents are converted into coordinator and worker agents.</p>

<ul>
  <li>Open the <em>.prompts/customAgents.yml</em> file</li>
  <li>Change the prompt of the <code class="language-plaintext highlighter-rouge">Research</code> agent
    <ul>
      <li>Use <code class="language-plaintext highlighter-rouge">~{delegateToAgent}</code> to delegate tasks to the <code class="language-plaintext highlighter-rouge">Gists</code> and the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent</li>
      <li>
        <p>The following snippet shows how such an agent could look like</p>

        <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Research</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Research</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a collection of links for a specific topic.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by providing links to blog posts about a specific topic.</span>
    <span class="s">To provide the necessary links use subagents to execute the following steps:</span>

    <span class="s">1. Use the Gists subagent via ~{delegateToAgent} to fetch a collection of blog posts about the specific topic.</span>
    <span class="s">2. For each of the found blog post link use the Link_Extractor subagent via ~{delegateToAgent} to fetch the content of the blog post and extract all links that are mentioned in the blog post.</span>
    <span class="s">3. Provide a collection of the extracted links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Change the prompt of the <code class="language-plaintext highlighter-rouge">Gists</code> agent
    <ul>
      <li>Remove the last step that delegates to the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent</li>
      <li>Ensure that the agent returns something at the end</li>
      <li>
        <p>The following snippet shows how such an agent could look like</p>

        <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of links to blog posts from a GitHub Gist.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by providing links to blog posts.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use ~{mcp_github_list_gists} to find the correct gist.</span>
    <span class="s">2. Use ~{mcp_fetch_fetch} to fetch the content of the gist with a max-length parameter of 15000.</span>
    <span class="s">3. Filter the fetched content for links about the requested information.</span>
    <span class="s">4. Provide a list of links to the relevant blog posts.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Change the prompt of the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent
    <ul>
      <li>Remove the iteration, as now the agent is called once per blog post</li>
      <li>Ensure that the agent returns something at the end</li>
      <li>
        <p>The following snippet shows how such an agent could look like</p>

        <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Link_Extractor</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Link_Extractor</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of links extracted from blog posts.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by extracting links mentioned in a blog post and providing them in a structured format.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Use ~{mcp_fetch_fetch} to fetch the content of the blog post with a max-length parameter of 15000.</span>
    <span class="s">2. Collect all links that are mentioned in the blog post and relevant for the topic.</span>
    <span class="s">3. Filter out duplicate links and links that are not relevant for the topic. Relevance can be determined by the presence of keywords related to the topic in the context of the link.</span>
    <span class="s">4. Provide a collection of the extracted filtered links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">Research</code> by selecting it in the chat prompt via <code class="language-plaintext highlighter-rouge">@</code> syntax and add the prompt to execute, for example <code class="language-plaintext highlighter-rouge">@Research show links about theia</code>.
    <figure class="">
<img src="/blog/assets/images/theia_select_custom_agent_research.png" alt="" /></figure>
  </li>
</ul>

<p>I noticed that it depends on the model used whether subagent calls are executed sequentially or in parallel. For example, with <em>gemini-3.1-flash-lite-preview</em> in the free tier, the <code class="language-plaintext highlighter-rouge">delegateToAgent</code> tool calls were executed sequentially. When executing the same workflow with <em>gpt-5.4</em>, the calls were executed in parallel, as you can see in the following screenshot:</p>

<figure class="">
  <img src="/blog/assets/images/theia_coordinate_response.png" alt="" /></figure>

<p>Theia does not yet support context window monitoring like Visual Studio Code, so we cannot inspect detailed context usage. This feature has been requested via <a href="https://github.com/eclipse-theia/theia/issues/16779">Context window inspection / analysis command</a>. For several LLMs, token usage can still be inspected via <em>AI Configuration</em> by switching to the <em>Token Usage</em> tab. I tested the example using <em>GPT-5.3-Codex</em> hosted on Azure, <em>GPT-4o</em> via Copilot, and <em>Gemini 3.1 Flash Lite Preview</em> via Google AI Studio in the Free Tier.</p>

<figure class="">
  <img src="/blog/assets/images/theia_token_coordinate.png" alt="" /></figure>

<p><em><strong>Note:</strong></em><br />
The token counts reported for Gemini models are incorrect in Theia 1.69.0. I created the ticket <a href="https://github.com/eclipse-theia/theia/issues/17165">Token usage shows incorrect values for Gemini models</a> and submitted a pull request that fixes this issue. The screenshot above shows token usage with the fix applied for a fair comparison.</p>

<p>Interestingly, token usage for the <em>Coordinator and Worker Pattern</em> in Theia uses fewer tokens than the <em>Delegate Pattern</em> but still slightly more than the single-agent solution.</p>

<h2 id="conclusion">Conclusion</h2>

<p>There is no single “best” orchestration pattern for every scenario. The right choice depends on whether your priority is simplicity, reuse, user guidance, or token efficiency.</p>

<p>For quick implementations and straightforward tasks, a single agent is often the easiest and most reliable option. If you want to split responsibilities into reusable building blocks and keep a guided user flow, delegation is a good fit. If you need better context isolation and potentially lower token usage for larger workflows, a coordinator with specialized worker subagents is usually the strongest approach.</p>

<p>The key takeaway is to treat orchestration as an architectural decision, not just a prompt-writing detail. In Visual Studio Code, the selected orchestration pattern can have a clear impact on token usage, while in Eclipse Theia the impact is almost negligible in this scenario. Start with the simplest setup that works, measure behavior and token usage with your target model, and then evolve toward delegation or coordinator-worker designs when your workflow grows in complexity.</p>

<p>As agent tooling in Visual Studio Code and Eclipse Theia continues to evolve, these patterns will likely become even more powerful. Revisit your agent design regularly to benefit from new capabilities and improved model behavior.</p>]]></content><author><name>Dirk Fauth</name></author><category term="fipro" /><category term="vscode" /><category term="typescript" /><category term="theia" /><category term="ai" /><category term="agents" /><summary type="html"><![CDATA[When talking about AI agents, we now often refer to Agentic AI workflows, where multiple agents collaborate to achieve complex goals. When creating Custom Agents, you need to consider collaboration options, especially if you design your agents for dedicated tasks rather than having one agent do everything. This is important because you should keep the context of your agent or prompt as minimal as possible while still providing enough information to achieve the desired results. Creating agents for dedicated tasks with a limited scope/context is similar to the encapsulation principle in object-oriented programming, and similarly, you need to consider processing or orchestration patterns.]]></summary></entry><entry><title type="html">Eclipse UI Best Practices and Icon Testing</title><link href="https://vogella.com/blog/eclipse-ui-best-practices-and-icon-replacement/" rel="alternate" type="text/html" title="Eclipse UI Best Practices and Icon Testing" /><published>2026-03-18T00:00:00+00:00</published><updated>2026-03-18T00:00:00+00:00</updated><id>https://vogella.com/blog/eclipse-ui-best-practices-and-icon-replacement</id><content type="html" xml:base="https://vogella.com/blog/eclipse-ui-best-practices-and-icon-replacement/"><![CDATA[<p>The <a href="https://github.com/eclipse-platform/ui-best-practices/">Eclipse UI Best Practices</a> project works on icon packs with modernized icons for the Eclipse IDE.</p>

<p><img src="/blog/assets/images/eclipse-dual-tone-icons.png" alt="Eclipse IDE with dual-tone icons" /></p>

<h2 id="testing-new-icons-in-your-ide">Testing New Icons in Your IDE</h2>

<p>To try them out in your existing Eclipse installation, you can use the provided <code class="language-plaintext highlighter-rouge">replace_icons.sh</code> script.</p>

<h3 id="prerequisites">Prerequisites</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">jq</code> (for JSON parsing)</li>
  <li>A JDK with the <code class="language-plaintext highlighter-rouge">jar</code> command</li>
</ul>

<h3 id="steps">Steps</h3>

<ol>
  <li>Clone the repository:</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/eclipse-platform/ui-best-practices.git
<span class="nb">cd </span>ui-best-practices
</code></pre></div></div>

<ol>
  <li>Run the replacement script, pointing it to your Eclipse <code class="language-plaintext highlighter-rouge">plugins</code> directory and the desired icon mapping file:</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./scripts/replace_icons.sh /path/to/eclipse/plugins iconpacks/eclipse-dual-tone/icon-mapping.json
</code></pre></div></div>

<p>The script reads the JSON mapping file, locates each icon in the icon pack, and replaces the corresponding icons inside the Eclipse plugins — whether they are folder-based or packaged as JARs.</p>

<ol>
  <li>Restart Eclipse with the <code class="language-plaintext highlighter-rouge">-clean -clearPersistedState</code> flags to clear the icon cache:</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./eclipse <span class="nt">-clean</span> <span class="nt">-clearPersistedState</span>
</code></pre></div></div>

<p>You should now see the updated icons in your IDE.</p>

<p><strong>Note:</strong> To see the new icons in EGit, you need a nightly build of EGit, as it only recently moved to SVG images.</p>

<h2 id="kudos-to-the-contributors">Kudos to the Contributors</h2>

<p>This project would not exist without the dedicated work of its contributors. Special thanks to <strong>Jasmin Hundt</strong>, <strong>Matthias Becker</strong>, <strong>Daniela Stegaru</strong>, and <strong>Michael Bangas</strong>. Their efforts are making the Eclipse IDE more visually modern and consistent.</p>]]></content><author><name>Lars Vogel</name></author><category term="eclipse" /><category term="rcp" /><summary type="html"><![CDATA[The Eclipse UI Best Practices project works on icon packs with modernized icons for the Eclipse IDE.]]></summary></entry><entry><title type="html">Eclipse RCP Book - Fourth Edition</title><link href="https://vogella.com/blog/eclipse-rcp-book-fourth-edition/" rel="alternate" type="text/html" title="Eclipse RCP Book - Fourth Edition" /><published>2026-02-18T00:00:00+00:00</published><updated>2026-02-18T00:00:00+00:00</updated><id>https://vogella.com/blog/eclipse-rcp-book-fourth-edition</id><content type="html" xml:base="https://vogella.com/blog/eclipse-rcp-book-fourth-edition/"><![CDATA[<p>I’m happy to announce the release of the fourth edition of the <strong>Eclipse RCP</strong> book, updated for Eclipse 2025-12.</p>

<p><img src="/blog/assets/images/eclipse-rcp-cover.jpg" alt="Eclipse RCP Book Cover" /></p>

<p>Eclipse Rich Client Platform (RCP) powers some of the world’s most sophisticated desktop applications. This comprehensive guide takes you from first principles to professional application development, combining crystal-clear explanations with hands-on exercises that build a complete working application.</p>

<p>You’ll learn:</p>

<ul>
  <li>Building modern UIs with SWT, JFace, and CSS styling</li>
  <li>OSGi modularity, dependency injection, and platform services</li>
  <li>Command-line builds with Maven and Tycho</li>
  <li>Migrating legacy Eclipse 3.x applications</li>
</ul>

<p>Whether you’re building your first Eclipse application or modernizing existing systems, this book provides the complete roadmap. Each chapter pairs thorough explanations with detailed exercises.</p>

<p>This fourth edition is fully updated for Eclipse 2025-12 and reflects real-world best practices from hundreds of training sessions and production projects.</p>

<p>Get the book on Amazon:</p>
<ul>
  <li><a href="https://amzn.to/4rUzZSW">Amazon Germany</a></li>
  <li><a href="https://amzn.to/4rV3N1V">Amazon USA</a></li>
  <li><a href="https://amzn.to/4tPyBmH">Amazon France</a></li>
</ul>]]></content><author><name>Lars Vogel</name></author><category term="eclipse" /><category term="rcp" /><summary type="html"><![CDATA[I’m happy to announce the release of the fourth edition of the Eclipse RCP book, updated for Eclipse 2025-12.]]></summary></entry><entry><title type="html">Getting Started with Theia AI</title><link href="https://vogella.com/blog/theia_ai_getting_started/" rel="alternate" type="text/html" title="Getting Started with Theia AI" /><published>2026-01-28T00:00:00+00:00</published><updated>2026-01-28T00:00:00+00:00</updated><id>https://vogella.com/blog/theia_ai_getting_started</id><content type="html" xml:base="https://vogella.com/blog/theia_ai_getting_started/"><![CDATA[<p>Eclipse Theia comes with quite extensive AI capabilities. This includes the usage of AI from a users perspective as well as from a developers perspective to implement AI features in Theia.
This tutorial focuses on implementing AI features in Theia. But as you can not describe how to implement something without knowing how to use it in the end, there are also some Theia AI usage guides in here. For a more detailed description on how to use Theia AI as an end-user, have a look at <a href="https://theia-ide.org/docs/user_ai/">Using the AI Features in the Theia IDE as an End User</a>.</p>

<p>To get a better understanding of the Theia AI capabilities compared to Visual Studio Code GitHub Copilot, I will try to keep the same structure as in my <a href="https://vogella.com/blog/vscode_copilot_extension/">Extending Copilot in Visual Studio Code</a> tutorial. For Theia the structure will be a slightly different, as Theia AI is doing things differently and even provides some more features that help in providing AI features for end users.</p>

<h2 id="prerequisites">Prerequisites</h2>

<p>Additionally to using Visual Studio Code with Dev Containers for the development, you need access to a LLM (Large Language Model). If you have an AI subscription, e.g. from OpenAI, you can use that one. Have a look at the <a href="https://theia-ide.org/docs/user_ai/#llm-providers-overview">LLM Providers Overview</a> to see which LLMs are currently supported by Theia. If you have a subscription for a LLM that is not listed here, check if it is compatible with the <a href="https://github.com/openai/openai-node">OpenAI API</a> and try to configure it as a <a href="https://theia-ide.org/docs/user_ai/#openai-compatible-models-eg-via-vllm">OpenAI Compatible Model</a>.</p>

<p>If you do not have a subscription yet, but want to follow this tutorial, you can try a free alternative. So far I only found these two variants:</p>

<ul>
  <li>Google AI (in case you have a Google account)</li>
  <li>Local LLM via Ollama</li>
</ul>

<p><a href="https://chatgpt.com/de-DE/pricing/">OpenAI offers a free tier</a>, but this is only for the usage of <a href="https://chatgpt.com/">ChatGPT</a>. There is no free tier for the usage of the OpenAI API. You actually need to configure the billing setup in order to use it.</p>

<ul>
  <li>If you have a Google account, you can try out the <a href="https://aistudio.google.com/">Google AI Studio</a>
    <ul>
      <li>Create a new project via <a href="https://aistudio.google.com/projects">Google AI Studio - Projects</a>
        <ul>
          <li>Click on <strong>Create a new project</strong></li>
          <li>Provide a name like <em>theia-evaluation</em></li>
          <li>Click on <strong>Create project</strong></li>
        </ul>
      </li>
      <li>Create a new API key via <a href="https://aistudio.google.com/api-keys">Google AI Studio - API Keys</a>
        <ul>
          <li>Click on <strong>Create API Key</strong></li>
          <li>Give the key a name like <em>theia</em></li>
          <li>Select the previously created <em>theia-evaluation</em> project</li>
          <li>Click on <strong>Create key</strong></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>If you have a capable notebook or workstation, you can also try out a local LLM via <a href="https://ollama.com/">Ollama</a>
    <ul>
      <li>Download and install Ollama for your operating system</li>
      <li>Pull a model that supports tool calls, e.g. <code class="language-plaintext highlighter-rouge">llama3.2</code>
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ollama pull llama3.2
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>Since version 1.68.0 Theia also supports a <a href="https://github.com/eclipse-theia/theia/pull/16841">GitHub Copilot language model integration</a>.</p>

<p>Dependent on which LLM you want to use, I will show how it can be configured later.</p>

<h2 id="project-setup">Project Setup</h2>

<p>As this tutorial is part of my <a href="https://github.com/fipro78/vscode_theia_cookbook">Visual Studio Code Extension - Theia - Cookbook</a>, I will use the Dev Container created with the <a href="https://vogella.com/blog/vscode_copilot_extension/">Getting Started with Visual Studio Code Extension Development</a> tutorial and extended in the <a href="https://vogella.com/blog/theia_getting_started/">Getting Started with Eclipse Theia</a> tutorial.</p>

<ul>
  <li>Clone the <a href="https://github.com/fipro78/vscode_theia_cookbook">Visual Studio Code Extension - Theia - Cookbook GitHub Repository</a> from the <em>vscode_copilot</em> branch. You can also use the <em>theia_getting_started</em> branch if you don’t want the Visual Studio Code Copilot Extension in your setup.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone -b vscode_copilot https://github.com/fipro78/vscode_theia_cookbook.git
</code></pre></div>    </div>
  </li>
  <li>Switch to the new folder and open Visual Studio Code
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd vscode_theia_cookbook
code .
</code></pre></div>    </div>
  </li>
  <li>When asked, select <em>Reopen in Container</em> to build and open the project in the Dev Container</li>
</ul>

<h3 id="update-dependencies">Update Dependencies</h3>

<p>Developing applications that are based on web frameworks typically means that you have to handle dependency updates quite often. The reason is the high frequency in which libraries are updated. Sometimes it feels like the tutorials and blog posts that rely on a specific version of a library is outdated at the time it is published. The Eclipse Theia project also publishes new releases quite often, so it is a common task to update your dependencies. As several things happened since the <a href="https://vogella.com/blog/theia_getting_started/">Getting Started with Eclipse Theia</a> tutorial, I will update the setup in the following section. It should be at least up-to-date at the time the tutorial is published, and describe in general the steps to follow for updating in the future.</p>

<p><em><strong>Note:</strong></em><br />
You need at least to use Theia 1.70.0 to make all features work that are described and used in this tutorial.</p>

<p>Theia and Visual Studio Code can use Node 22 in the meanwhile. The <a href="https://github.com/eclipse-theia/theia/blob/master/doc/Developing.md#prerequisites">Theia Prerequisites</a> talk about the requirement <em>Node.js &gt;= 22 and &lt;= 24</em> and the <a href="https://github.com/microsoft/vscode/blob/main/.devcontainer/Dockerfile">VS Code Dev Container</a> is based on <code class="language-plaintext highlighter-rouge">typescript-node:22-bookworm</code>. Therefore we first update the project setup to use Node 22.</p>

<ul>
  <li>Open the file <em>theia/package.json</em>
    <ul>
      <li>Update the <code class="language-plaintext highlighter-rouge">enginges</code> section
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"engines"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"node"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&gt;=22 &lt;=24"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"npm"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&gt;=10"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Open the file <em>.devcontainer/devcontainer.json</em>
    <ul>
      <li>Update the <code class="language-plaintext highlighter-rouge">image</code> to <code class="language-plaintext highlighter-rouge">typescript-node:22-bookworm</code>
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"image"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm"</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Open the <em>Dockerfile</em> in the project root (used to containerize the application)
    <ul>
      <li>Update the <code class="language-plaintext highlighter-rouge">NODE_VERSION</code>
        <div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ARG</span><span class="s"> NODE_VERSION=22</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Open the file <em>.devcontainer/postCreateCommand.sh</em>
    <ul>
      <li>Install <a href="https://www.npmjs.com/package/npm-check-updates"><code class="language-plaintext highlighter-rouge">npm-check-updates</code></a> to make dependency updates more comfortable.
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install -g npm yo generator-code @vscode/vsce @angular/cli generator-theia-extension npm-check-updates
</code></pre></div>        </div>
        <p><em><strong>Note:</strong></em><br />
This step is of course opinionated. There are also other ways to update dependencies, but I personally found this one quite comfortable.</p>
      </li>
    </ul>
  </li>
  <li>Rebuild the Dev Container (e.g. <em>F1 -&gt; Dev Containers: Rebuild Container</em>)</li>
</ul>

<p>After the Dev Container is rebuilt, lets update the dependencies of the Theia project(s) by using <a href="https://www.npmjs.com/package/npm-check-updates"><code class="language-plaintext highlighter-rouge">npm-check-updates</code></a>.
You can also use different ways to update the dependencies, e.g. by manually search and replace the Theia package versions with the most current one,
or by executing <code class="language-plaintext highlighter-rouge">npm outdated</code> to get a list of outdated dependencies and the available newer versions, and then updating the versions dependency by dependency.</p>

<ul>
  <li>Open a <strong>Terminal</strong></li>
  <li>Switch to the <em>theia</em> folder</li>
  <li>Execute the following command to show the outdated dependencies.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ncu
</code></pre></div>    </div>
    <p>This will show that <code class="language-plaintext highlighter-rouge">lerna</code> is quite outdated. Although that is not really an issue, let’s also update that dependency.</p>
  </li>
  <li>Execute the following command to update the outdated dependencies in the <em>package.json</em>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ncu -u
</code></pre></div>    </div>
  </li>
  <li>Open the file <em>theia/lerna.json</em>
    <ul>
      <li>Change the content to the following
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"lerna"</span><span class="p">:</span><span class="w"> </span><span class="s2">"9.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"npmClient"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"run"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"stream"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Switch back to the <strong>Terminal</strong>
    <ul>
      <li>Switch to the folder <em>theia/browser-app</em></li>
      <li>Execute the following command to update the dependencies
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ncu -u
</code></pre></div>        </div>
      </li>
      <li>Switch to the folder <em>theia/theia-customization</em></li>
      <li>Execute the following command to update the dependencies
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ncu -u -i
</code></pre></div>        </div>

        <ul>
          <li>Uncheck the <code class="language-plaintext highlighter-rouge">typescript</code> package to avoid that it gets updated automatically to version 6.x.</li>
          <li>Answer the question <code class="language-plaintext highlighter-rouge">Run npm install to install new versions?</code> with <code class="language-plaintext highlighter-rouge">n</code> as we need to execute <code class="language-plaintext highlighter-rouge">npm install</code> from the <em>theia</em> parent folder.</li>
        </ul>
      </li>
      <li>Switch to the folder <em>theia/electron-app</em></li>
      <li>Execute the following command to update the dependencies in interactive mode
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ncu -u -i
</code></pre></div>        </div>
        <ul>
          <li>Uncheck the <code class="language-plaintext highlighter-rouge">electron</code> package to avoid that it gets updated automatically.<br />
This is necessary because the package <code class="language-plaintext highlighter-rouge">@theia/electron@1.70.0</code> has a peer dependency to <code class="language-plaintext highlighter-rouge">electron@39.7.0</code> and a newer dependency would break the build.</li>
          <li>Answer the question <code class="language-plaintext highlighter-rouge">Run npm install to install new versions?</code> with <code class="language-plaintext highlighter-rouge">n</code> as we need to execute <code class="language-plaintext highlighter-rouge">npm install</code> from the <em>theia</em> parent folder.</li>
        </ul>
      </li>
      <li>Open the file <em>theia/electron-app/package.json</em>
        <ul>
          <li>Update the <code class="language-plaintext highlighter-rouge">electron</code> version
            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"devDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"electron"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^39.7.0"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>            </div>
          </li>
        </ul>
      </li>
      <li>Switch back to <strong>Terminal</strong> to the <em>theia</em> folder</li>
      <li>Run <code class="language-plaintext highlighter-rouge">npm install</code> to update the dependencies</li>
    </ul>
  </li>
</ul>

<p><em><strong>Note:</strong></em><br />
Newer versions of <code class="language-plaintext highlighter-rouge">@theia/electron</code> might have a dependency to a newer <code class="language-plaintext highlighter-rouge">electron</code> version. You can find out which version is needed by looking into the <a href="https://github.com/eclipse-theia/theia/blob/master/packages/electron/package.json"><code class="language-plaintext highlighter-rouge">@theia/electron</code> - package.json</a> and check for the <code class="language-plaintext highlighter-rouge">peerDependencies</code>.</p>

<p><em><strong>Note:</strong></em><br />
If you get some strange errors after the dependency updates, try to cleanup the workspace first.</p>

<ul>
  <li>Delete the folder <em>theia/node_modules</em></li>
  <li>Delete the file <em>theia/package-lock.json</em></li>
  <li>Run <code class="language-plaintext highlighter-rouge">npm install</code> again</li>
</ul>

<h3 id="create-the-new-theia-ai-extension-project">Create the new Theia AI Extension project</h3>

<p>In the following section we will create a new Theia extension to add custom AI features to our Theia application.</p>

<ul>
  <li>Open a <strong>Terminal</strong></li>
  <li>Switch to the <em>theia</em> folder</li>
  <li>
    <p>Create a new Theia extension by using the <code class="language-plaintext highlighter-rouge">theia-extension</code> generator</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yo theia-extension
? The extension's type Empty
? The extension's name ai-extension
</code></pre></div>    </div>
  </li>
  <li>
    <p>Select <code class="language-plaintext highlighter-rouge">do not overwrite</code> (n) for every file that comes up with a conflict. It would otherwise remove all changes we applied before.</p>
  </li>
  <li>Add the necessary modifications manually
    <ul>
      <li>Update the <em>theia/package.json</em>
        <ul>
          <li>Add a script to build the <code class="language-plaintext highlighter-rouge">ai-extension</code> and add the execution of that script to <code class="language-plaintext highlighter-rouge">build:browser</code> and <code class="language-plaintext highlighter-rouge">build:electron</code></li>
          <li>
            <p>Change <code class="language-plaintext highlighter-rouge">build:browser</code> and <code class="language-plaintext highlighter-rouge">build:electron</code> to use <code class="language-plaintext highlighter-rouge">lerna</code></p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"build:ai-extension"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix ai-extension run build"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"build:customization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix theia-customization run build"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"build:browser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lerna run build --ignore electron-app"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"build:electron"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lerna run build --ignore browser-app"</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>            </div>

            <p><em><strong>Note:</strong></em><br />
This configuration is helpful for this tutorial as we only have two extensions, and if you now execute <code class="language-plaintext highlighter-rouge">npm run build:browser</code> it will first build the <em>ai-extension</em> and the <em>theia-customization</em> and then the <em>browser-app</em> but not the <em>electron-app</em>. In bigger setups it might be more efficient to build the changed extension and then the application. Or to have dedicated build jobs for different scenarios.</p>
          </li>
          <li>
            <p>Add the new <em>ai-extension</em> module to the <code class="language-plaintext highlighter-rouge">workspaces</code> section</p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"workspaces"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"ai-extension"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"theia-customization"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"browser-app"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"electron-app"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span></code></pre></div>            </div>
          </li>
        </ul>
      </li>
      <li>Add the <em>ai-extension</em> module as a dependency to the <em>browser-app</em> and the <em>electron-app</em>
        <ul>
          <li>Open the <em>package.json</em></li>
          <li>
            <p>Add the dependency to the <code class="language-plaintext highlighter-rouge">dependencies</code> section</p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="err">...</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ai-extension"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.0.0"</span><span class="w">
  </span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>            </div>
          </li>
        </ul>
      </li>
      <li>
        <p>Delete the newly created <em>theia/.vscode</em> folder<br />
We already copied the content to the <em>.vscode</em> folder in the <a href="https://vogella.com/blog/theia_getting_started/">previous tutorial</a>.</p>
      </li>
      <li>Switch back to <strong>Terminal</strong> to the <em>theia</em> folder</li>
      <li>Run <code class="language-plaintext highlighter-rouge">npm install</code> to update the project after the manual modifications</li>
    </ul>
  </li>
</ul>

<h3 id="add-ai-features">Add AI features</h3>

<p>The code generator is generating basic project stubs. They do not contain the AI features already, so first we need to extend the <em>browser-app</em> and the <em>electron-app</em> to support AI.</p>

<ul>
  <li>
    <p>Add Theia AI to the Theia application.</p>

    <p><em><strong>Note:</strong></em><br />
We add the dependencies to support AI Chat capabilities and MCP support in this tutorial. To also add AI support in the Editor and the Terminal, install the corresponding packages <code class="language-plaintext highlighter-rouge">@theia/ai-editor</code> and <code class="language-plaintext highlighter-rouge">@theia/ai-terminal</code>.</p>

    <p><em><strong>Note:</strong></em><br />
We add the dependencies to support LLMs that use the Google API, the OpenAI API and Ollama in this tutorial. If you need additional LLM API support, have a look at <a href="https://theia-ide.org/docs/user_ai/#llm-providers-overview">LLM Providers Overview</a> to see what is available. If you for example have a GitHub Copilot license and want to use that, add the <code class="language-plaintext highlighter-rouge">@theia/ai-copilot</code> module which was added with Theia 1.68.0.</p>
    <ul>
      <li>Update the <em>package.json</em> of the <em>browser-app</em> and the <em>electron-app</em>
        <ul>
          <li>Open a <strong>Terminal</strong></li>
          <li>
            <p>Switch to the Theia Application directory (<em>browser-app</em> and <em>electron-app</em>)</p>
          </li>
          <li>Add <code class="language-plaintext highlighter-rouge">@theia/ai-core-ui</code> to add UI Core</li>
          <li>Add <code class="language-plaintext highlighter-rouge">@theia/ai-history</code> to add the AI history view</li>
          <li>Add <code class="language-plaintext highlighter-rouge">@theia/ai-ide</code> to add the Chat and the default agents</li>
          <li>Add <code class="language-plaintext highlighter-rouge">@theia/ai-mcp-ui</code> to add MCP support</li>
          <li>Add <code class="language-plaintext highlighter-rouge">@theia/ai-google</code> to add Google API support</li>
          <li>Add <code class="language-plaintext highlighter-rouge">@theia/ai-ollama</code> to add Ollama support</li>
          <li>
            <p>Add <code class="language-plaintext highlighter-rouge">@theia/ai-openai</code> to add OpenAI API support</p>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @theia/ai-core-ui @theia/ai-history @theia/ai-ide @theia/ai-mcp-ui @theia/ai-google @theia/ai-ollama @theia/ai-openai
</code></pre></div>            </div>
          </li>
        </ul>

        <p>The additional required packages will be added transitively. If you want to use any other LLM than <em>Google AI</em>, <em>Ollama</em> or <em>OpenAI</em>, add the corresponding module for your LLM.</p>
      </li>
      <li>Build and run the Theia browser application<br />
If you created the tasks as explained in <a href="https://vogella.com/blog/theia_getting_started/#optional-define-tasks">Optional: Define Tasks</a> you can either
        <ul>
          <li>Press <strong>F1</strong>
            <ul>
              <li>Select <code class="language-plaintext highlighter-rouge">Tasks: Run Task</code></li>
              <li>Select <code class="language-plaintext highlighter-rouge">Build Theia Browser</code> to build the Theia browser application</li>
              <li>Select <code class="language-plaintext highlighter-rouge">Start Theia Browser</code> to start the Theia browser application</li>
            </ul>
          </li>
          <li>Use the <a href="https://marketplace.visualstudio.com/items?itemName=axelrindle.task-explorer">Task Explorer</a> Visual Studio Code Extension
            <ul>
              <li>Expand <em>npm</em> in the <em>Task Explorer</em> view</li>
              <li>Run the <code class="language-plaintext highlighter-rouge">Build Theia Browser</code> task from the tree to build the Theia browser application</li>
              <li>Run the <code class="language-plaintext highlighter-rouge">Start Theia Browser</code> task from the tree to start the Theia browser application</li>
            </ul>
          </li>
        </ul>

        <p>If you want to work with the Terminal and execute the <code class="language-plaintext highlighter-rouge">npm</code> scripts manually</p>
        <ul>
          <li>Open a <strong>Terminal</strong></li>
          <li>Switch to the <em>theia</em> folder</li>
          <li>Run <code class="language-plaintext highlighter-rouge">npm run build:browser</code> to build the Theia browser application</li>
          <li>Run <code class="language-plaintext highlighter-rouge">npm run start:browser</code> to start the Theia browser application</li>
        </ul>

        <p><em><strong>Note:</strong></em><br />
You can also use the <em>Launch Configuration</em> from the <em>.vscode/launch.json</em> via <em>Run and Debug</em>. This will start the application and enable debugging. But you first need to build the application via commandline or Task.</p>
      </li>
    </ul>
  </li>
</ul>

<h3 id="configure-ai">Configure AI</h3>

<ul>
  <li>Open a browser on http://localhost:3000 and see the started Theia application.
    <ul>
      <li>Open the settings page by pressing <strong>CTRL</strong> + <strong>,</strong>
        <ul>
          <li>Select the <strong>AI Features</strong></li>
          <li>Check <strong>Enable AI</strong></li>
          <li>Configure the LLM you want to use
            <ul>
              <li>Google
                <ul>
                  <li><strong>Api Key</strong>: Copy and paste your Google AI API key (see above)<br />
For this tutorial we simply configure the API key via preferences as it is easier than setting up the environment.
In a productive environment you should use the environment variable <code class="language-plaintext highlighter-rouge">GOOGLE_API_KEY</code> to set the key securely.</li>
                  <li><strong>Models</strong>: Ensure to have models in the list that are currently available according to <a href="https://ai.google.dev/gemini-api/docs/models">Gemini Models</a></li>
                </ul>
              </li>
              <li>Ollama
                <ul>
                  <li><strong>Ollama Host</strong>: <em>http://localhost:11434</em></li>
                  <li><strong>Ollama Models</strong>: <em>llama3.2</em></li>
                </ul>
              </li>
            </ul>
          </li>
          <li>Configure the <em>Model Aliases</em>
            <ul>
              <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
              <li>Switch to the <em>Model Aliases</em> tab</li>
              <li>For every model alias select the model that you configured</li>
            </ul>
          </li>
        </ul>
      </li>
      <li>Instead of configuring everything via user interface, you can also directly paste one of the following configurations directly in the settings JSON
        <ul>
          <li>Switch to the JSON view of the settings by clicking the curly braces on the upper right corner of the editor (<em>Open Settings (JSON)</em>)</li>
          <li>Alternatively use the <em>Command Palette</em> (F1) and search for <em>Preferences: Open Settings (JSON)</em></li>
          <li>Copy one of the following snippets and paste it in the editor
            <ul>
              <li>Google
                <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"ai-features.AiEnable.enableAI"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.google.apiKey"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;your-api-key&gt;"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.google.models"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"gemini-2.5-pro"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-2.5-flash"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-2.5-flash-preview-09-2025"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-2.0-flash"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-2.0-flash-lite"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"ai-features.languageModelAliases"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"default/code"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-2.5-flash"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/universal"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-2.5-flash"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/code-completion"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-2.5-flash"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/summarize"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-2.5-flash"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>                </div>
              </li>
              <li>
                <p>Ollama</p>

                <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"ai-features.AiEnable.enableAI"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.ollama.ollamaHost"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:11434"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.ollama.ollamaModels"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"llama3.2"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"ai-features.languageModelAliases"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"default/code"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama/llama3.2"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/universal"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama/llama3.2"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/code-completion"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama/llama3.2"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/summarize"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama/llama3.2"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>                </div>
              </li>
            </ul>
          </li>
        </ul>
      </li>
      <li>Open the <em>AI Chat</em> view if it is not open already via <strong>CTRL</strong> + <strong>ALT</strong> + <strong>I</strong> or <em>Menu Bar</em> -&gt; <em>View</em> -&gt; <em>AI Chat</em></li>
      <li>Enter something in the chat like <em>@Universal Tell me a bar joke</em> and check if the AI configuration works</li>
    </ul>
  </li>
</ul>

<p><em><strong>Note:</strong></em><br />
If you want to use the GitHub Copilot language model integration, you need to click on the Copilot indicator in the status bar and follow the instructions in the dialog to authorize the application via the GitHub device authorization page. After the authorization succeeded, you can select one of the available Copilot language models in the <em>AI Configuration</em>.</p>

<h2 id="tool-functions">Tool Functions</h2>

<p>The first component you can contribute in Visual Studio Code to Copilot is a <a href="https://code.visualstudio.com/api/extension-guides/ai/ai-extensibility-overview#language-model-tool">Language Model Tool</a>. 
In Theia they are called <a href="https://theia-ide.org/docs/theia_ai/#tool-functions"><em>Tool Functions</em></a>.
Such tools are used to extend the chat with domain-specific capabilities that can use the VSCode API to perform actions in Visual Studio Code.</p>

<p>In Visual Studio Code you configure the Language Model Tool in the <code class="language-plaintext highlighter-rouge">contributes</code> section of the <em>package.json</em>, implement the <code class="language-plaintext highlighter-rouge">vscode.LanguageModelTool</code> and register it via <code class="language-plaintext highlighter-rouge">vscode.lm.registerTool()</code> on activation of the extension. See <a href="https://vogella.com/blog/vscode_copilot_extension/#language-model-tool">Extending Copilot in Visual Studio Code - Language Model Tool</a> for further details.</p>

<p>In Theia you implement a <code class="language-plaintext highlighter-rouge">ToolProvider</code> and bind it to the injection context. In this section we will create a <strong>Tool Function</strong> that creates a file in the current workspace that contains a joke as content.</p>

<p><em><strong>Note:</strong></em><br />
Theia already contains a tool function with the name <code class="language-plaintext highlighter-rouge">writeFileContent</code> that can be used to create a file in the workspace. The implementation is <a href="https://github.com/eclipse-theia/theia/blob/b8c56743a958d3d830d9907d345ef961016683f3/packages/ai-ide/src/browser/file-changeset-functions.ts#L113">WriteFileContent</a> in case you are interested in the default implementation. So the following implementation is not necessary to achieve the result. It is intended as an example how the implementation of a <strong>Tool Function</strong> could look like.</p>

<ul>
  <li>Open a <strong>Terminal</strong>
    <ul>
      <li>Switch to the <em>theia/ai-extension</em> directory</li>
      <li>
        <p>Add <code class="language-plaintext highlighter-rouge">@theia/ai-core</code> as a dependency</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @theia/ai-core
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Open the file <em>theia/ai-extension/src/browser/ai-extension-contribution.ts</em>
    <ul>
      <li>Implement a <code class="language-plaintext highlighter-rouge">ToolProvider</code> where the important things to notice are
        <ul>
          <li>The definition of an ID which is used to call the <em>Tool Function</em></li>
          <li>The implementation of the <code class="language-plaintext highlighter-rouge">ToolRequest</code> that is returned by <code class="language-plaintext highlighter-rouge">getTool()</code> where the <code class="language-plaintext highlighter-rouge">handler</code> is defined that represents the function</li>
        </ul>
      </li>
      <li>
        <p>Replace the content with the following and adjust it if you want. This makes it easier to follow the tutorial instead of implementing everything by your own.</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">ToolProvider</span><span class="p">,</span>
  <span class="nx">ToolRequest</span><span class="p">,</span>
  <span class="nx">ToolInvocationContext</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CommandRegistry</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">BinaryBuffer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/common/buffer</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">inject</span><span class="p">,</span> <span class="nx">injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">FileService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/filesystem/lib/browser/file-service</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">WorkspaceService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/workspace/lib/browser</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">FileNavigatorCommands</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/navigator/lib/browser/navigator-contribution</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">CREATE_JOKE_FILE_FUNCTION_ID</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">jokeFileCreator</span><span class="dl">"</span><span class="p">;</span>

<span class="p">@</span><span class="nd">injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">JokeFileCreationFunction</span> <span class="k">implements</span> <span class="nx">ToolProvider</span> <span class="p">{</span>
  <span class="k">static</span> <span class="nx">ID</span> <span class="o">=</span> <span class="nx">CREATE_JOKE_FILE_FUNCTION_ID</span><span class="p">;</span>

  <span class="p">@</span><span class="nd">inject</span><span class="p">(</span><span class="nx">WorkspaceService</span><span class="p">)</span>
  <span class="k">protected</span> <span class="nx">workspaceService</span><span class="p">:</span> <span class="nx">WorkspaceService</span><span class="p">;</span>

  <span class="p">@</span><span class="nd">inject</span><span class="p">(</span><span class="nx">FileService</span><span class="p">)</span>
  <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">fileService</span><span class="p">:</span> <span class="nx">FileService</span><span class="p">;</span>

  <span class="p">@</span><span class="nd">inject</span><span class="p">(</span><span class="nx">CommandRegistry</span><span class="p">)</span>
  <span class="k">protected</span> <span class="nx">commandRegistry</span><span class="p">:</span> <span class="nx">CommandRegistry</span><span class="p">;</span>

  <span class="nf">getTool</span><span class="p">():</span> <span class="nx">ToolRequest</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">{</span>
      <span class="na">id</span><span class="p">:</span> <span class="nx">JokeFileCreationFunction</span><span class="p">.</span><span class="nx">ID</span><span class="p">,</span>
      <span class="na">name</span><span class="p">:</span> <span class="nx">JokeFileCreationFunction</span><span class="p">.</span><span class="nx">ID</span><span class="p">,</span>
      <span class="na">description</span><span class="p">:</span> <span class="s2">`Create a file at the given path that contains a joke.`</span><span class="p">,</span>
      <span class="na">parameters</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">properties</span><span class="p">:</span> <span class="p">{</span>
          <span class="na">path</span><span class="p">:</span> <span class="p">{</span>
            <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
            <span class="na">description</span><span class="p">:</span>
              <span class="dl">"</span><span class="s2">The name of the folder in the workspace where the joke file should be created.</span><span class="dl">"</span><span class="p">,</span>
          <span class="p">},</span>
          <span class="na">filename</span><span class="p">:</span> <span class="p">{</span>
            <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
            <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">The name of the jokefile that should be created.</span><span class="dl">"</span><span class="p">,</span>
          <span class="p">},</span>
          <span class="na">joke</span><span class="p">:</span> <span class="p">{</span>
            <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
            <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">The joke content to be written in the joke file.</span><span class="dl">"</span><span class="p">,</span>
          <span class="p">},</span>
        <span class="p">},</span>
        <span class="na">required</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">path</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">filename</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">joke</span><span class="dl">"</span><span class="p">],</span>
      <span class="p">},</span>
      <span class="na">handler</span><span class="p">:</span> <span class="k">async </span><span class="p">(</span>
        <span class="na">args</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="na">ctx</span><span class="p">:</span> <span class="nx">ToolInvocationContext</span><span class="p">,</span>
      <span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">ctx</span><span class="p">?.</span><span class="nx">cancellationToken</span><span class="p">?.</span><span class="nx">isCancellationRequested</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Operation cancelled by user</span><span class="dl">"</span> <span class="p">});</span>
        <span class="p">}</span>

        <span class="kd">const</span> <span class="p">{</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">filename</span><span class="p">,</span> <span class="nx">joke</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">args</span><span class="p">);</span>

        <span class="kd">const</span> <span class="nx">wsRoots</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">workspaceService</span><span class="p">.</span><span class="nx">roots</span><span class="p">;</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">wsRoots</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">No workspace has been opened yet</span><span class="dl">"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="kd">const</span> <span class="nx">workspaceRoot</span> <span class="o">=</span> <span class="nx">wsRoots</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">resource</span><span class="p">;</span>
        <span class="kd">const</span> <span class="nx">uri</span> <span class="o">=</span> <span class="nx">workspaceRoot</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="nx">path</span><span class="p">);</span>

        <span class="k">try</span> <span class="p">{</span>
          <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">fileService</span><span class="p">.</span><span class="nf">createFolder</span><span class="p">(</span><span class="nx">uri</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="nx">error</span><span class="p">.</span><span class="nx">message</span> <span class="p">});</span>
        <span class="p">}</span>

        <span class="kd">const</span> <span class="nx">fileUri</span> <span class="o">=</span> <span class="nx">uri</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="nx">filename</span><span class="p">);</span>

        <span class="c1">// ensure that we do not overwrite existing files</span>
        <span class="k">if </span><span class="p">(</span><span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">fileService</span><span class="p">.</span><span class="nf">exists</span><span class="p">(</span><span class="nx">fileUri</span><span class="p">))</span> <span class="p">{</span>
          <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span>
            <span class="na">error</span><span class="p">:</span> <span class="s2">`File </span><span class="p">${</span><span class="nx">fileUri</span><span class="p">}</span><span class="s2"> already exists`</span><span class="p">,</span>
          <span class="p">});</span>
        <span class="p">}</span>

        <span class="k">try</span> <span class="p">{</span>
          <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">fileService</span><span class="p">.</span><span class="nf">createFile</span><span class="p">(</span>
            <span class="nx">fileUri</span><span class="p">,</span>
            <span class="nx">BinaryBuffer</span><span class="p">.</span><span class="nf">fromString</span><span class="p">(</span><span class="nx">joke</span><span class="p">),</span>
          <span class="p">);</span>

          <span class="k">this</span><span class="p">.</span><span class="nx">commandRegistry</span><span class="p">.</span><span class="nf">executeCommand</span><span class="p">(</span>
            <span class="nx">FileNavigatorCommands</span><span class="p">.</span><span class="nx">REFRESH_NAVIGATOR</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
          <span class="p">);</span>

          <span class="k">return</span> <span class="s2">`Successfully wrote a joke in the file </span><span class="p">${</span><span class="nx">fileUri</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="nx">error</span><span class="p">.</span><span class="nx">message</span> <span class="p">});</span>
        <span class="p">}</span>
      <span class="p">},</span>
    <span class="p">};</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Open the file <em>theia/ai-extension/src/browser/ai-extension-frontend-module.ts</em>
    <ul>
      <li>
        <p>Replace the content with the following</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">JokeFileCreationFunction</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ai-extension-contribution</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">bindToolProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-core/lib/common</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ContainerModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="k">new</span> <span class="nc">ContainerModule</span><span class="p">((</span><span class="nx">bind</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">bindToolProvider</span><span class="p">(</span><span class="nx">JokeFileCreationFunction</span><span class="p">,</span> <span class="nx">bind</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>You need an open workspace to make the <strong>Tool Function</strong> work. To be able to open a workspace in the Theia application create a folder <em>example</em> in the home directory of the <em>node</em> user in the Dev Container</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir ~/example
</code></pre></div>    </div>
  </li>
  <li>Build the Theia browser application</li>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em>
    <ul>
      <li>Switch to the <em>Tools</em> tab</li>
      <li>Verify that there is an entry for <code class="language-plaintext highlighter-rouge">jokeFileCreator</code> that is set to <em>Always Allow</em><br />
If you like to get a confirmation dialog before executing the tool, change the value to <em>Confirm</em></li>
    </ul>
  </li>
  <li>Click on <em>Open Folder</em> in the <em>Explorer</em> tab to open a workspace
    <ul>
      <li>Select the created <em>node/example</em> folder</li>
    </ul>
  </li>
  <li>In the <em>AI Chat</em> enter a prompt that uses the created tool function. This can be done by typing <code class="language-plaintext highlighter-rouge">~</code> followed by the tool id, in our case <code class="language-plaintext highlighter-rouge">~jokeFileCreator</code>.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal create a file that contains a joke in the folder test. use a file name that relates to the joke. ~jokeFileCreator
</code></pre></div>    </div>
  </li>
  <li>Check in the response if the <code class="language-plaintext highlighter-rouge">jokeFileCreator</code> function was executed and if a file with a joke was created.</li>
</ul>

<p><em><strong>Note:</strong></em><br />
If you compare the prompt we use in Theia with the prompt used in the Visual Studio Copilot tutorial, you notice some differences:</p>

<ul>
  <li>You specify a Theia Agent via <code class="language-plaintext highlighter-rouge">@Universal</code><br />
In previous versions of Theia the <code class="language-plaintext highlighter-rouge">@Orchestrator</code> agent was called when no other agent was explicitly mentioned. This caused additional requests in the background to find a matching agent. Since 1.67.0 you can also select a default agent in the settings under <em>AI Features -&gt; Chat: Default Agent</em>. This agent will be used when no other agent is mentioned explicitly using the <code class="language-plaintext highlighter-rouge">@</code> symbol. Note that specifying the default agent is done via agent ID without the leading <code class="language-plaintext highlighter-rouge">@</code>.</li>
  <li>The <strong>Tool Function</strong> is specified via <code class="language-plaintext highlighter-rouge">~jokeFileCreator</code> while the <em>Language Model Tool</em> in Visual Studio Code is targeted via <code class="language-plaintext highlighter-rouge">#jokeFileCreator</code></li>
  <li>The prompt is more descriptive, otherwise it might fail, where in Copilot the values are somehow set correctly. But that might be related to the model that is used.</li>
</ul>

<h2 id="mcp-server">MCP Server</h2>

<p>The Model Context Protocol (MCP), is an open protocol to standardize how AI applications connect with external tools and data sources. MCP servers can offer resources, prompts and tools that can be used by a client.</p>

<p>In this section we will cover how to add MCP servers to a Theia application and how to use the provided MCP tools.</p>

<p>There are basically two types of MCP servers:</p>

<ul>
  <li>Local MCP servers (stdio transport)<br />
A local MCP server runs on the same machine as the MCP client and is used to access local resources like files or run local scripts. Local servers are essential for tasks that require accessing local files or data that is not available remotely.</li>
  <li>Remote MCP servers (streamable HTTP or server-sent events)</li>
</ul>

<p>There are several ways to add MCP servers to Theia:</p>

<ul>
  <li>Configure them via <em>settings.json</em></li>
  <li>Programmatically via a Theia Extension</li>
</ul>

<p><em><strong>Note:</strong></em><br />
The usage of a <em>mcp.json</em> server configuration file, like in Visual Studio Code, is currently not supported, but proposed via <a href="https://github.com/eclipse-theia/theia/issues/16024">this ticket</a>.
If you are interested in the <em>mcp.json</em> format in Visual Studio Code, have a look at <a href="https://vogella.com/blog/vscode_copilot_extension/#mcp-server">Extending Copilot in Visual Studio Code - MCP Server</a> for further details.</p>

<p>In this section I will describe how to manually configure MCP servers via <em>settings.json</em> and programmatically via Theia extension.</p>

<p>We will install</p>

<ul>
  <li>the <a href="https://modelcontextprotocol.io/quickstart/user#installing-the-filesystem-server">Filesystem MCP Server</a> as a local MCP Server</li>
  <li>the <a href="https://github.com/modelcontextprotocol/servers/tree/main/src/fetch">Fetch MCP Server</a> as a remote MCP Server</li>
  <li>the <a href="https://github.com/github/github-mcp-server">GitHub MCP Server</a> as a remote MCP Server that requires an authorization</li>
</ul>

<h3 id="add-mcp-server-via-settingsjson">Add MCP server via settings.json</h3>

<p>In the following section we will add MCP servers via <em>settings.json</em> file. For this you need of course a running Theia application. At this point you don’t need to build anything, so simply start the Theia browser application or use the running instance if you did not stop the server from the previous steps.</p>

<h4 id="local-mcp-server">Local MCP Server</h4>

<ul>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Open the settings page by pressing <strong>CTRL</strong> + <strong>,</strong></li>
  <li>Switch to the JSON view of the settings by clicking the curly braces on the upper right corner of the editor (<em>Open Settings (JSON)</em>)
    <ul>
      <li>Alternatively use the <em>Command Palette</em> (F1) and search for <em>Preferences: Open Settings (JSON)</em></li>
    </ul>
  </li>
  <li>
    <p>Add the following content to configure the filesystem server as local MCP server.</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"ai-features.mcp.mcpServers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"filesystem"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"-y"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"@modelcontextprotocol/server-filesystem"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"/home/node/example"</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<p><em><strong>Note:</strong></em><br />
The <a href="https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem">Filesystem MCP Server</a> supports <a href="https://modelcontextprotocol.info/docs/concepts/roots/">Roots</a>. Visual Studio Code as a MCP client supports roots and sets the workspace as such. Since version 1.69.0 Theia also supports roots which was added via this <a href="https://github.com/eclipse-theia/theia/pull/16911">pull request</a>. In Theia it is possible to configure whether the workspace should be used as <em>Root</em> or not via the preference <code class="language-plaintext highlighter-rouge">ai-features.mcp.useWorkspaceAsRoot</code>.</p>

<p>The above configuration sets the <code class="language-plaintext highlighter-rouge">autostart</code> flag by default to <code class="language-plaintext highlighter-rouge">true</code> if the current open workspace is trusted and the workspace trust setting is enabled. If <code class="language-plaintext highlighter-rouge">autostart</code> is disabled or the server does not start automatically, you can start the server manually.</p>

<ul>
  <li>To start the filesystem MCP server you can either
    <ul>
      <li>Use the command palette: <em>F1 -&gt; MCP: Start MCP Server -&gt; filesystem</em></li>
      <li>Use the <em>AI Configuration</em> view: <em>Menu</em> -&gt; <em>View</em> -&gt; <em>AI Configuration</em>
        <ul>
          <li>Open the <em>MCP Servers</em> tab</li>
          <li>
            <p>Click on the <em>Play</em> button for the <strong>filesystem</strong> MCP server</p>

            <figure class="">
<img src="/blog/assets/images/theia_mcp_filesystem.png" alt="" /></figure>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>Starting the server will discover the capabilities and tools provided by the server. These tools can then be used for example in the Chat.</p>

<ul>
  <li>
    <p>Test if the configuration works by entering the following to the AI Chat</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal list files in /home/node/example ~mcp_filesystem_list_directory
</code></pre></div>    </div>
  </li>
  <li>
    <p>You should now see that the <code class="language-plaintext highlighter-rouge">mcp_filesystem_list_directory</code> tool from the <em>filesystem (MCP Server)</em> is executed to solve your request.</p>
  </li>
</ul>

<p><em><strong>Note:</strong></em><br />
The name of the tool to use can be found either by typing <code class="language-plaintext highlighter-rouge">~</code> and go through the list, or from the <em>AI Configuration - MCP Servers</em> view, if you expand the <em>Tools</em> section and click on the <em>Copy tool</em> icon of the corresponding tool.</p>

<p>To compare the results with the Visual Studio Code Copilot Extension example, you can also try to execute one of the following prompts:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal list allowed directories ~mcp_filesystem_list_allowed_directories
</code></pre></div></div>

<p>or a more extended one</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal list files in the allowed directories and all subdirectories ~mcp_filesystem_list_allowed_directories ~mcp_filesystem_list_directory
</code></pre></div></div>

<h4 id="remote-mcp-server">Remote MCP Server</h4>

<ul>
  <li>Add the following content to the <em>settings.json</em> in the <code class="language-plaintext highlighter-rouge">ai-features.mcp.mcpServers</code> section to configure the fetch server as remote MCP server.
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"fetch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://remote.mcpservers.org/fetch/mcp"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Click on the <em>Connect</em> button to connect to the <strong>fetch</strong> remote MCP server</p>

    <figure class="">
<img src="/blog/assets/images/theia_mcp_fetch.png" alt="" /></figure>
  </li>
  <li>
    <p>Test if the configuration works by entering the following to the AI Chat</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal fetch the content from https://eclipse.dev/nattable ~mcp_fetch_fetch
</code></pre></div>    </div>
  </li>
  <li>You should now see that the <code class="language-plaintext highlighter-rouge">mcp_fetch_fetch</code> tool from the <em>fetch (MCP Server)</em> is executed to solve your request.</li>
</ul>

<h4 id="remote-mcp-server-with-authorization">Remote MCP Server with authorization</h4>

<p>Most of the remote MCP servers require an authorization in order to work. Currently it is only possible to write the token in plain text into the <em>settings.json</em> file. As long as the <em>settings.json</em> is locally on your system, this is not an issue. But it can become an issue if you want to share your settings with team colleagues or store them on a central server. There is an <a href="https://github.com/eclipse-theia/theia/issues/16198">open ticket</a> that addresses this issue.</p>

<p>In the following section we will configure MCP servers with an authorization token in plain text in the <em>settings.json</em>.</p>

<p>To demonstrate this we use the <a href="https://github.com/github/github-mcp-server">GitHub MCP Server</a> with a PAT (Personal Access Token). This is also described in <a href="https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp/use-the-github-mcp-server">Using the GitHub MCP Server</a>.</p>

<ul>
  <li>Create a Personal Access Token as described in <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic">Creating a personal access token (classic)</a></li>
  <li>Use <strong>repo</strong> as scope</li>
</ul>

<p>You can use the GitHub MCP server locally via Docker. To make this work in a Dev Container you need the <a href="https://github.com/devcontainers/features/tree/main/src/docker-in-docker">docker-in-docker</a> feature added to the <em>devcontainer.json</em></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"features"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"ghcr.io/devcontainers/features/docker-in-docker:2"</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The local GitHub MCP server can then be configured in the <em>settings.json</em> file like this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"docker"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"run"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"-i"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"--rm"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"-e"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"GITHUB_PERSONAL_ACCESS_TOKEN"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"ghcr.io/github/github-mcp-server"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"env"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"GITHUB_PERSONAL_ACCESS_TOKEN"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;your-token&gt;"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The token is configured in the <code class="language-plaintext highlighter-rouge">env</code> section of the local MCP server configuration. Replace <code class="language-plaintext highlighter-rouge">&lt;your-token&gt;</code> with the PAT created before.</p>

<p>Instead of using the GitHub MCP server locally, you can directly use the GitHub hosted server as explained in <a href="https://github.blog/ai-and-ml/generative-ai/a-practical-guide-on-how-to-use-the-github-mcp-server/">A practical guide on how to use the GitHub MCP server</a>. The token can be configured via the <code class="language-plaintext highlighter-rouge">serverAuthToken</code> property, which resolves to a <code class="language-plaintext highlighter-rouge">Bearer</code> authorization token. If <code class="language-plaintext highlighter-rouge">Bearer</code> is not correct for the specific server, you can change this via the <code class="language-plaintext highlighter-rouge">serverAuthTokenHeader</code> property.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"serverAuthToken"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;your-token&gt;"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Alternatively you can configure the header also in the <code class="language-plaintext highlighter-rouge">headers</code> section directly, e.g. if you need to use a custom header for the authorization.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"Authorization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Bearer &lt;your-token&gt;"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><em><strong>Note:</strong></em><br />
GitHub recommends to authenticate against the MCP remote servers via OAuth. At the time writing this tutorial there is no OAuth support for MCP servers available in Theia, so you need to stick with the <code class="language-plaintext highlighter-rouge">Authorization</code> token until the OAuth support is added in Theia.</p>

<p>As the GitHub MCP Server provides a huge set of tools, the tools are categorized and not all tools are enabled by default. If you for example want to use the GitHub Gist related tools, you need to enable that. This can either be done by using the explicit MCP URL</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/x/gists"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"serverAuthToken"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;your-token&gt;"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>or by setting the optional header <code class="language-plaintext highlighter-rouge">X-MCP-Toolsets</code></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"Authorization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Bearer &lt;your-token&gt;"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"X-MCP-Toolsets"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gists"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Further details about this are available in <a href="https://github.com/github/github-mcp-server/blob/main/docs/remote-server.md">Remote GitHub MCP Server</a>.</p>

<ul>
  <li>Test if the GitHub MCP Server configuration works
    <ul>
      <li>Start the server</li>
      <li>Enter the following in the chat
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal fetch the publications written by Dirk Fauth in the gists of fipro78. Provide the links to blog posts about VS Code and Eclipse Theia in the chat that are extracted from a related gists file. Use ~mcp_github_list_gists  to list the available gists. Then use ~mcp_fetch_fetch  to fetch the content of the found gists with a max-length parameter of 15000.
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<h3 id="add-mcp-server-programmatically-via-theia-extension">Add MCP server programmatically via Theia Extension</h3>

<p>You can also register MCP server programmatically via a Theia extension. This way you can for example bundle AI extensions like agents or the direct usage of the AI API with the registration of MCP servers that are needed for the provided functionality. You can also share default MCP servers with an authorization token that is configured in an environment variable.</p>

<p>To register a MCP server programmatically you can either use the <code class="language-plaintext highlighter-rouge">MCPFrontendService</code> or the <code class="language-plaintext highlighter-rouge">MCPServerManager</code> directly, which is used by the <code class="language-plaintext highlighter-rouge">MCPFrontendService</code> internally. To retrieve environment variables you can use the <code class="language-plaintext highlighter-rouge">EnvVariablesServer</code>.</p>

<p>In the following section we will register the MCP servers from the section above programmatically.</p>

<p><em><strong>Note:</strong></em><br />
Remember to remove the MCP server configuration from the <em>settings.json</em> or use different names for the MCP servers to avoid naming collisions.</p>

<ul>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-contribution.ts</em>
    <ul>
      <li>Add the necessary import statements
        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">CommandRegistry</span><span class="p">,</span> <span class="nx">MessageService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">FrontendApplicationContribution</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/browser</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">EnvVariablesServer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/common/env-variables</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
  <span class="nx">LocalMCPServerDescription</span><span class="p">,</span>
  <span class="nx">MCPFrontendService</span><span class="p">,</span>
  <span class="nx">RemoteMCPServerDescription</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-mcp/lib/common</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
      <li>Add a new class <code class="language-plaintext highlighter-rouge">McpFrontendContribution</code> that implements <code class="language-plaintext highlighter-rouge">FrontendApplicationContribution</code></li>
      <li>Get the <code class="language-plaintext highlighter-rouge">MCPFrontendService</code> and the <code class="language-plaintext highlighter-rouge">EnvVariablesServer</code> injected</li>
      <li>Implement the <code class="language-plaintext highlighter-rouge">onStart()</code> method
        <ul>
          <li>Register the <code class="language-plaintext highlighter-rouge">filesystem</code> MCP server as local MCP server via <code class="language-plaintext highlighter-rouge">LocalMCPServerDescription</code></li>
          <li>Register the <code class="language-plaintext highlighter-rouge">fetch</code> MCP server as remote MCP server via <code class="language-plaintext highlighter-rouge">RemoteMCPServerDescription</code></li>
          <li>Register the <code class="language-plaintext highlighter-rouge">github</code> MCP server as remote MCP server via <code class="language-plaintext highlighter-rouge">RemoteMCPServerDescription</code> and add a <code class="language-plaintext highlighter-rouge">resolve</code> function to provide the <code class="language-plaintext highlighter-rouge">GITHUB_TOKEN</code> environment variable</li>
        </ul>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">McpFrontendContribution</span> <span class="k">implements</span> <span class="nx">FrontendApplicationContribution</span> <span class="p">{</span>
  <span class="p">@</span><span class="nd">inject</span><span class="p">(</span><span class="nx">EnvVariablesServer</span><span class="p">)</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">envVariablesServer</span><span class="p">:</span> <span class="nx">EnvVariablesServer</span><span class="p">;</span>

  <span class="p">@</span><span class="nd">inject</span><span class="p">(</span><span class="nx">MCPFrontendService</span><span class="p">)</span>
  <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">mcpFrontendService</span><span class="p">:</span> <span class="nx">MCPFrontendService</span><span class="p">;</span>

  <span class="p">@</span><span class="nd">inject</span><span class="p">(</span><span class="nx">MessageService</span><span class="p">)</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">messageService</span><span class="p">:</span> <span class="nx">MessageService</span><span class="p">;</span>

  <span class="k">async</span> <span class="nf">onStart</span><span class="p">():</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">try</span> <span class="p">{</span>
      <span class="c1">// add a local MCP server</span>
      <span class="kd">const</span> <span class="na">fileSystemServer</span><span class="p">:</span> <span class="nx">LocalMCPServerDescription</span> <span class="o">=</span> <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">filesystem</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">command</span><span class="p">:</span> <span class="dl">"</span><span class="s2">npx</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">args</span><span class="p">:</span> <span class="p">[</span>
          <span class="dl">"</span><span class="s2">-y</span><span class="dl">"</span><span class="p">,</span>
          <span class="dl">"</span><span class="s2">@modelcontextprotocol/server-filesystem</span><span class="dl">"</span><span class="p">,</span>
          <span class="dl">"</span><span class="s2">/home/node/example</span><span class="dl">"</span><span class="p">,</span>
        <span class="p">],</span>
      <span class="p">};</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">mcpFrontendService</span><span class="p">.</span><span class="nf">addOrUpdateServer</span><span class="p">(</span><span class="nx">fileSystemServer</span><span class="p">);</span>

      <span class="c1">// add a remote MCP server</span>
      <span class="kd">const</span> <span class="na">fetchServer</span><span class="p">:</span> <span class="nx">RemoteMCPServerDescription</span> <span class="o">=</span> <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">fetch</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">serverUrl</span><span class="p">:</span> <span class="dl">"</span><span class="s2">https://remote.mcpservers.org/fetch/mcp</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">};</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">mcpFrontendService</span><span class="p">.</span><span class="nf">addOrUpdateServer</span><span class="p">(</span><span class="nx">fetchServer</span><span class="p">);</span>

      <span class="c1">// get the GITHUB_TOKEN environment variable</span>
      <span class="kd">const</span> <span class="nx">githubTokenVar</span> <span class="o">=</span>
        <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">envVariablesServer</span><span class="p">.</span><span class="nf">getValue</span><span class="p">(</span><span class="dl">"</span><span class="s2">GITHUB_TOKEN</span><span class="dl">"</span><span class="p">);</span>
      <span class="c1">// add a remote MCP server with resolve method</span>
      <span class="kd">const</span> <span class="na">githubServer</span><span class="p">:</span> <span class="nx">RemoteMCPServerDescription</span> <span class="o">=</span> <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">github</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">serverUrl</span><span class="p">:</span> <span class="dl">"</span><span class="s2">https://api.githubcopilot.com/mcp/</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">serverAuthToken</span><span class="p">:</span> <span class="nx">githubTokenVar</span><span class="p">?.</span><span class="nx">value</span><span class="p">,</span>
        <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
          <span class="dl">"</span><span class="s2">X-MCP-Toolsets</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">gists</span><span class="dl">"</span><span class="p">,</span>
        <span class="p">},</span>
      <span class="p">};</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">mcpFrontendService</span><span class="p">.</span><span class="nf">addOrUpdateServer</span><span class="p">(</span><span class="nx">githubServer</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Error configuring MCP server:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">messageService</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span>
        <span class="dl">"</span><span class="s2">Failed to configure MCP server. Please check the console for details.</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>        </div>

        <p>Alternatively it is possible to get the token interactively by implementing <code class="language-plaintext highlighter-rouge">resolve()</code> of the <code class="language-plaintext highlighter-rouge">RemoteMCPServerDescription</code>:</p>
        <ul>
          <li>Get the <code class="language-plaintext highlighter-rouge">QuickInputService</code> injected</li>
        </ul>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">inject</span><span class="p">(</span><span class="nx">QuickInputService</span><span class="p">)</span>
<span class="k">protected</span> <span class="k">readonly</span> <span class="nx">quickInputService</span><span class="p">:</span> <span class="nx">QuickInputService</span><span class="p">;</span>
</code></pre></div>        </div>

        <ul>
          <li>Change the <code class="language-plaintext highlighter-rouge">githubServer</code> definition
            <ul>
              <li>Remove the <code class="language-plaintext highlighter-rouge">serverAuthToken</code></li>
              <li>Add a <code class="language-plaintext highlighter-rouge">resolve</code> function that asks for the authentication token by using the <code class="language-plaintext highlighter-rouge">QuickInputService</code></li>
            </ul>
          </li>
        </ul>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">githubServer</span><span class="p">:</span> <span class="nx">RemoteMCPServerDescription</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">github</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">serverUrl</span><span class="p">:</span> <span class="dl">"</span><span class="s2">https://api.githubcopilot.com/mcp/</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">X-MCP-Toolsets</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">gists</span><span class="dl">"</span><span class="p">,</span>
  <span class="p">},</span>
  <span class="na">resolve</span><span class="p">:</span> <span class="k">async </span><span class="p">(</span><span class="nx">serverDescription</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Resolving GitHub MCP server description</span><span class="dl">"</span><span class="p">);</span>

    <span class="c1">// Prompt user for authentication token</span>
    <span class="kd">const</span> <span class="nx">authToken</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">quickInputService</span><span class="p">.</span><span class="nf">input</span><span class="p">({</span>
      <span class="na">prompt</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Enter authentication token for GitHubMCP server</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">password</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
      <span class="na">value</span><span class="p">:</span>
        <span class="dl">"</span><span class="s2">serverAuthToken</span><span class="dl">"</span> <span class="k">in</span> <span class="nx">serverDescription</span>
          <span class="p">?</span> <span class="nx">serverDescription</span><span class="p">.</span><span class="nx">serverAuthToken</span> <span class="o">||</span> <span class="dl">""</span>
          <span class="p">:</span> <span class="dl">""</span><span class="p">,</span>
    <span class="p">});</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">authToken</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// Return updated server description with new token</span>
      <span class="k">return</span> <span class="p">{</span>
        <span class="p">...</span><span class="nx">serverDescription</span><span class="p">,</span>
        <span class="na">serverAuthToken</span><span class="p">:</span> <span class="nx">authToken</span><span class="p">,</span>
      <span class="p">}</span> <span class="kd">as </span><span class="nx">RemoteMCPServerDescription</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// If no token provided, return original description</span>
    <span class="k">return</span> <span class="nx">serverDescription</span><span class="p">;</span>
  <span class="p">},</span>
<span class="p">};</span>
<span class="k">this</span><span class="p">.</span><span class="nx">mcpFrontendService</span><span class="p">.</span><span class="nf">addOrUpdateServer</span><span class="p">(</span><span class="nx">githubServer</span><span class="p">);</span>
</code></pre></div>        </div>
      </li>
    </ul>

    <p><em><strong>Note:</strong></em><br />
If you want to automatically start the programmatically registered MCP servers, you need to call the corresponding API at the end:</p>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">this</span><span class="p">.</span><span class="nx">mcpFrontendService</span><span class="p">.</span><span class="nf">startServer</span><span class="p">(</span><span class="nx">fileSystemServer</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">mcpFrontendService</span><span class="p">.</span><span class="nf">startServer</span><span class="p">(</span><span class="nx">fetchServer</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">mcpFrontendService</span><span class="p">.</span><span class="nf">startServer</span><span class="p">(</span><span class="nx">githubServer</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
</code></pre></div>    </div>
  </li>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-frontend-module.ts</em>
    <ul>
      <li>Register the <code class="language-plaintext highlighter-rouge">McpFrontendContribution</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">McpFrontendContribution</span><span class="p">,</span>
  <span class="nx">JokeFileCreationFunction</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ai-extension-contribution</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">bindToolProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-core/lib/common</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">FrontendApplicationContribution</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/browser</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ContainerModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="k">new</span> <span class="nc">ContainerModule</span><span class="p">((</span><span class="nx">bind</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">bindToolProvider</span><span class="p">(</span><span class="nx">JokeFileCreationFunction</span><span class="p">,</span> <span class="nx">bind</span><span class="p">);</span>

  <span class="nf">bind</span><span class="p">(</span><span class="nx">McpFrontendContribution</span><span class="p">).</span><span class="nf">toSelf</span><span class="p">().</span><span class="nf">inSingletonScope</span><span class="p">();</span>
  <span class="nf">bind</span><span class="p">(</span><span class="nx">FrontendApplicationContribution</span><span class="p">).</span><span class="nf">toService</span><span class="p">(</span><span class="nx">McpFrontendContribution</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div>    </div>
  </li>
  <li>Build the Theia browser application</li>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
  <li>Switch to the <em>MCP Servers</em> tab</li>
  <li>Start the MCP servers if they are not configured to <code class="language-plaintext highlighter-rouge">autostart</code></li>
  <li>In the <em>AI Chat</em> view use prompts like in the section before to verify that the MCP servers are registered and working correctly.</li>
</ul>

<p>Further information about MCP in Theia:</p>

<ul>
  <li><a href="https://github.com/eclipse-theia/theia/tree/master/packages/ai-mcp">@theia/ai-mcp</a></li>
  <li><a href="https://github.com/eclipse-theia/theia/tree/master/packages/ai-mcp-ui">@theia/ai-mcp-ui</a></li>
  <li><a href="https://theia-ide.org/docs/user_ai/#mcp-integration">MCP Integration</a></li>
</ul>

<h2 id="agents">Agents</h2>

<p>Custom agents in Theia allow the creation of custom workflows and extending the Theia IDE with new capabilities. An agent in Theia AI is comparable to the <a href="https://vogella.com/blog/vscode_copilot_extension/#chat-participant"><em>Chat Participant</em> in Visual Studio Code Copilot</a>. So it is a specialized assistant to extend the IDE with domain specific experts knowledge.
In comparison to Visual Studio Code, this is not limited to the chat, it can also be integrated into other parts of the IDE like the editor, the terminal or a custom widget. Similar to the <em>Chat Participant</em> in Visual Studio Code, an agent can use the available and exposed Theia AI to access and integrate in the Theia application. This applies of course to agents that are implemented and contributed programmatically.</p>

<p><em><strong>Note:</strong></em><br />
Defining a custom agent via configuration file similar to custom agents in Visual Studio Code will be described in <a href="#further-customizations">Further customizations</a>.</p>

<h3 id="implement-a-custom-agent">Implement a Custom Agent</h3>

<p>In this section we will implement a <em>Custom Agent</em>. In the section after this one, we will extend the agent to have more influence on the processing, access the Theia API or add agent specific variables and tools.</p>

<ul>
  <li>Create a new file <em>ai-extension/src/browser/ai-extension-joker-agent.ts</em>
    <ul>
      <li>Create a new class <code class="language-plaintext highlighter-rouge">JokerChatAgent</code> that extends <code class="language-plaintext highlighter-rouge">AbstractStreamParsingChatAgent</code>
        <ul>
          <li>Define a <code class="language-plaintext highlighter-rouge">BasePromptFragment</code> that basically consists of an <code class="language-plaintext highlighter-rouge">id</code> and a <code class="language-plaintext highlighter-rouge">template</code></li>
          <li>Set the required fields according to your needs
            <ul>
              <li>Define the <code class="language-plaintext highlighter-rouge">LanguageModelRequirement</code> for the purpose <code class="language-plaintext highlighter-rouge">chat</code> and by default uses <code class="language-plaintext highlighter-rouge">default/universal</code> model alias</li>
              <li>Set the <code class="language-plaintext highlighter-rouge">prompts</code> and define a simple <code class="language-plaintext highlighter-rouge">PromptVariantSet</code> based on the single `BasePromptFragment</li>
              <li>Add the <em>Tool Function</em> <code class="language-plaintext highlighter-rouge">jokeFileCreator</code> that we created before to the <code class="language-plaintext highlighter-rouge">functions</code></li>
            </ul>
          </li>
          <li>Copy and paste the following code to the new file to make it easy to proceed</li>
        </ul>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">AbstractStreamParsingChatAgent</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-chat</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
  <span class="nx">BasePromptFragment</span><span class="p">,</span>
  <span class="nx">LanguageModelRequirement</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CREATE_JOKE_FILE_FUNCTION_ID</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ai-extension-contribution</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">jokerTemplate</span><span class="p">:</span> <span class="nx">BasePromptFragment</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">joker-system-default</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">template</span><span class="p">:</span> <span class="s2">`
  # Instructions
    
  You are the Joker, the arch enemy of Batman.
  To attack Batman, you tell a joke that is so funny, it distracts him from his mission.
  To keep the distraction going on, write the joke to a file. Use **~{</span><span class="p">${</span><span class="nx">CREATE_JOKE_FILE_FUNCTION_ID</span><span class="p">}</span><span class="s2">}** to write the joke to a file.
  If the user does not provide a path, create a new folder "bat-jokes" in the current workspace folder and store the file in that folder.
  Choose a filename that is related to the joke itself.
  `</span><span class="p">,</span>
<span class="p">};</span>

<span class="p">@</span><span class="nd">injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">JokerChatAgent</span> <span class="kd">extends</span> <span class="nc">AbstractStreamParsingChatAgent</span> <span class="p">{</span>
  <span class="nl">id</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Joker</span><span class="dl">"</span><span class="p">;</span>
  <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Joker</span><span class="dl">"</span><span class="p">;</span>
  <span class="nl">languageModelRequirements</span><span class="p">:</span> <span class="nx">LanguageModelRequirement</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
      <span class="na">purpose</span><span class="p">:</span> <span class="dl">"</span><span class="s2">chat</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">identifier</span><span class="p">:</span> <span class="dl">"</span><span class="s2">default/universal</span><span class="dl">"</span><span class="p">,</span>
    <span class="p">},</span>
  <span class="p">];</span>
  <span class="k">protected</span> <span class="nx">defaultLanguageModelPurpose</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">chat</span><span class="dl">"</span><span class="p">;</span>
  <span class="nx">override</span> <span class="nx">description</span> <span class="o">=</span>
    <span class="dl">"</span><span class="s2">This agent creates a file with a joke about batman.</span><span class="dl">"</span><span class="p">;</span>

  <span class="nx">override</span> <span class="nx">iconClass</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">codicon codicon-feedback</span><span class="dl">"</span><span class="p">;</span>
  <span class="k">protected</span> <span class="nx">override</span> <span class="nx">systemPromptId</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">joker-system</span><span class="dl">"</span><span class="p">;</span>
  <span class="nx">override</span> <span class="nx">prompts</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">joker-system</span><span class="dl">"</span><span class="p">,</span> <span class="na">defaultVariant</span><span class="p">:</span> <span class="nx">jokerTemplate</span><span class="p">,</span> <span class="na">variants</span><span class="p">:</span> <span class="p">[]</span> <span class="p">},</span>
  <span class="p">];</span>
  <span class="nx">override</span> <span class="nx">functions</span> <span class="o">=</span> <span class="p">[</span><span class="nx">CREATE_JOKE_FILE_FUNCTION_ID</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-frontend-module.ts</em>
    <ul>
      <li>Import the necessary dependencies</li>
      <li>Register the <code class="language-plaintext highlighter-rouge">JokerChatAgent</code> via dependency injection</li>
      <li>Register it as an <code class="language-plaintext highlighter-rouge">Agent</code></li>
      <li>
        <p>Register it as a <code class="language-plaintext highlighter-rouge">ChatAgent</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">McpFrontendContribution</span><span class="p">,</span>
  <span class="nx">JokeFileCreationFunction</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ai-extension-contribution</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Agent</span><span class="p">,</span> <span class="nx">bindToolProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-core/lib/common</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ChatAgent</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-chat/lib/common</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">FrontendApplicationContribution</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/browser</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ContainerModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JokerChatAgent</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ai-extension-joker-agent</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="k">new</span> <span class="nc">ContainerModule</span><span class="p">((</span><span class="nx">bind</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">bindToolProvider</span><span class="p">(</span><span class="nx">JokeFileCreationFunction</span><span class="p">,</span> <span class="nx">bind</span><span class="p">);</span>

  <span class="nf">bind</span><span class="p">(</span><span class="nx">McpFrontendContribution</span><span class="p">).</span><span class="nf">toSelf</span><span class="p">().</span><span class="nf">inSingletonScope</span><span class="p">();</span>
  <span class="nf">bind</span><span class="p">(</span><span class="nx">FrontendApplicationContribution</span><span class="p">).</span><span class="nf">toService</span><span class="p">(</span><span class="nx">McpFrontendContribution</span><span class="p">);</span>

  <span class="nf">bind</span><span class="p">(</span><span class="nx">JokerChatAgent</span><span class="p">).</span><span class="nf">toSelf</span><span class="p">().</span><span class="nf">inSingletonScope</span><span class="p">();</span>
  <span class="nf">bind</span><span class="p">(</span><span class="nx">Agent</span><span class="p">).</span><span class="nf">toService</span><span class="p">(</span><span class="nx">JokerChatAgent</span><span class="p">);</span>
  <span class="nf">bind</span><span class="p">(</span><span class="nx">ChatAgent</span><span class="p">).</span><span class="nf">toService</span><span class="p">(</span><span class="nx">JokerChatAgent</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Build the Theia browser application</li>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em>
    <ul>
      <li>Switch to the <em>Agents</em> tab</li>
      <li>Verify that the <code class="language-plaintext highlighter-rouge">Joker</code> agent is listed</li>
    </ul>
  </li>
  <li>In the <em>AI Chat</em> enter a prompt that uses the <code class="language-plaintext highlighter-rouge">@Joker</code> agent.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Joker tell me a joke about batman
</code></pre></div>    </div>
  </li>
  <li>
    <p>Check in the response if the <code class="language-plaintext highlighter-rouge">jokeFileCreator</code> function was executed and if a file with a joke was created.</p>

    <figure class="">
<img src="/blog/assets/images/theia_ai_chat_joker.png" alt="" /></figure>
  </li>
</ul>

<p>Further information about <em>Custom Agents</em> in Theia AI can be found here:</p>

<ul>
  <li><a href="theia-ide.org/docs/user_ai/#current-agents-in-the-theia-ide">Current Agents in the Theia IDE</a></li>
  <li><a href="https://theia-ide.org/docs/theia_ai/">Building Custom AI assistants and AI support with Theia AI</a></li>
</ul>

<h3 id="advanced-theia-ai-agent-features">Advanced Theia AI Agent features</h3>

<p>The above agent implementation is pretty simple, and actually you can achieve the same by creating a <em>Custom Agent</em> via configuration file, as the agent basically only consists of a specialized prompt. The advantage of a programmatically registered <em>Custom Agent</em> is:</p>

<ul>
  <li>it can be integrated and delivered with the Theia application, without the need to have a user to define a <em>customAgents.yml</em> file</li>
  <li>you can use the Theia API to, for example customize the response rendering, access the platform to perform additional tasks, start a related MCP server in case it is not already started but needed for the processing.</li>
</ul>

<p>We will have a look at those features in the following sections.</p>

<h4 id="prompt-variants">Prompt Variants</h4>

<p>Theia supports a feature called <a href="https://eclipsesource.com/blogs/2024/12/06/eclipse-theia-1-56-release-news-and-noteworthy/#support-for-prompt-variantshttpsgithubcomeclipse-theiatheiapull14487"><em>Prompt Variants</em></a> which enables tool builders to define multiple prompts per agents that a user can switch between. To demonstrate that we will add a new <em>Prompt Variant</em> to the <code class="language-plaintext highlighter-rouge">JokerChatAgent</code> that does not use the <em>Tool Function</em> to write the joke into a file.</p>

<ul>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-joker-agent.ts</em>
    <ul>
      <li>
        <p>Define a new <code class="language-plaintext highlighter-rouge">BasePromptFragment</code> with a simplified prompt</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">jokerTemplateSimple</span><span class="p">:</span> <span class="nx">BasePromptFragment</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">joker-system-simple</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">template</span><span class="p">:</span> <span class="s2">`
  # Instructions
    
  You are the Joker, the arch enemy of Batman.
  To attack Batman, you tell a joke that is so funny, it distracts him from his mission.
  `</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Extend the <code class="language-plaintext highlighter-rouge">prompts</code> field to specify the prompt variants</p>
        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">override</span> <span class="nx">prompts</span> <span class="o">=</span> <span class="p">[</span>
  <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">joker-system</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">defaultVariant</span><span class="p">:</span> <span class="nx">jokerTemplate</span><span class="p">,</span>
    <span class="na">variants</span><span class="p">:</span> <span class="p">[</span><span class="nx">jokerTemplate</span><span class="p">,</span> <span class="nx">jokerTemplateSimple</span><span class="p">],</span>
  <span class="p">},</span>
<span class="p">];</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Build the Theia browser application</li>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em>
    <ul>
      <li>Switch to the <em>Agents</em> tab</li>
      <li>Select the <code class="language-plaintext highlighter-rouge">Joker</code> agent</li>
      <li>Select the <em>joker-system-simple</em> entry in the <em>Prompt Templates</em> combobox</li>
    </ul>
  </li>
  <li>In the <em>AI Chat</em> enter a prompt that uses the <code class="language-plaintext highlighter-rouge">@Joker</code> agent.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Joker tell me a joke about batman
</code></pre></div>    </div>
  </li>
  <li>Check that now there is only a joke in the response, but no file is generated.</li>
</ul>

<p>By using <em>Prompt Variants</em> you are able to provide users different ways how an agent works. In Theia <em>Prompt Variants</em> are for example used to switch the <code class="language-plaintext highlighter-rouge">@Coder</code> chat agent to the <strong>Agent</strong> mode. This is explained in <a href="https://eclipsesource.com/blogs/2025/07/08/theia-coder-agent-mode/">Theia Coder Agent Mode: From AI Assistant to Autonomous Developer</a> that also has a video linked to show it in more detail.</p>

<p><em><strong>Note:</strong></em><br />
A user is even able to adjust prompt templates via the <em>AI Configuration</em>. By clicking the <em>Edit</em> button next to the selected <em>Prompt Template</em>, the <em>Prompt Template</em> can be modified, which will create a <em>.prompttemplate</em> file in the <em>.theia/prompt-templates</em> folder in the user home.</p>

<h4 id="agent-modes">Agent Modes</h4>

<p>It is possible to define multiple operational modes for a <em>Chat Agent</em> that allow users to control how the agent responds. This way agents can adjust their behavior based on user preferences.
The <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-ide/src/browser/coder-agent.ts">Coder</a> agent and the <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-ide/src/browser/architect-agent.ts">Architect</a> agent in Theia support <em>Agent Modes</em>. They extend the <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-ide/src/browser/mode-aware-chat-agent.ts"><code class="language-plaintext highlighter-rouge">AbstractModeAwareChatAgent</code></a>, which is defined in the <code class="language-plaintext highlighter-rouge">@theia/ai-ide</code> package. This implementation maps <em>Prompt Variants</em> to <em>Agent Modes</em>, so a user can directly change the behavior of the agent by selecting a mode and does not need to switch to the settings every time. The mode can also control other behavior, for example how a response is rendered or whether the agent should <em>explain</em> or <em>fix</em> something.</p>

<p>As mentioned, there is an <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-ide/src/browser/mode-aware-chat-agent.ts"><code class="language-plaintext highlighter-rouge">AbstractModeAwareChatAgent</code></a> implementation that can be extended to achieve mode-to-prompt-fragment mapping. But first, we will implement similar behavior in a simpler way in the <code class="language-plaintext highlighter-rouge">Joker</code> agent to better understand <em>Agent Mode</em> support details.</p>

<ul>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-joker-agent.ts</em>
    <ul>
      <li>
        <p>Define a new <code class="language-plaintext highlighter-rouge">BasePromptFragment</code> with a simplified prompt that swears and uses emojis and does not save the joke to a file.</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">jokerTemplateSwear</span><span class="p">:</span> <span class="nx">BasePromptFragment</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">joker-system-swear</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">template</span><span class="p">:</span> <span class="s2">`
  # Instructions
    
  You are the Joker, the arch enemy of Batman.
  To attack Batman, you tell a joke that is so funny, it distracts him from his mission.
  You swear a lot and use a lot of emojis in your jokes to make them even more distracting for Batman.
  `</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Extend the <code class="language-plaintext highlighter-rouge">prompts</code> field to specify the prompt variants</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">override</span> <span class="nx">prompts</span> <span class="o">=</span> <span class="p">[</span>
  <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">joker-system</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">defaultVariant</span><span class="p">:</span> <span class="nx">jokerTemplate</span><span class="p">,</span>
    <span class="na">variants</span><span class="p">:</span> <span class="p">[</span><span class="nx">jokerTemplate</span><span class="p">,</span> <span class="nx">jokerTemplateSimple</span><span class="p">,</span> <span class="nx">jokerTemplateSwear</span><span class="p">],</span>
  <span class="p">},</span>
<span class="p">];</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Define the <code class="language-plaintext highlighter-rouge">modes</code> the agent supports.<br />
Use the IDs of the templates and a corresponding user-facing name.</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">modes</span> <span class="o">=</span> <span class="p">[</span>
  <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">jokerTemplate</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Default Mode</span><span class="dl">"</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">jokerTemplateSimple</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Simple Mode</span><span class="dl">"</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">jokerTemplateSwear</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Swear Mode</span><span class="dl">"</span> <span class="p">},</span>
<span class="p">];</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Override the method <code class="language-plaintext highlighter-rouge">getSystemMessageDescription()</code> to get the prompt fragment to use from the selected mode in the request context.</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span> <span class="nx">override</span> <span class="k">async</span> <span class="nf">getSystemMessageDescription</span><span class="p">(</span>
  <span class="nx">context</span><span class="p">:</span> <span class="nx">AIVariableContext</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">SystemMessageDescription</span> <span class="o">|</span> <span class="kc">undefined</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">systemPromptId</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kc">undefined</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// Check for mode-based override from request</span>
  <span class="kd">let</span> <span class="nx">modeId</span> <span class="o">=</span> <span class="nx">ChatSessionContext</span><span class="p">.</span><span class="k">is</span><span class="p">(</span><span class="nx">context</span><span class="p">)</span>
    <span class="p">?</span> <span class="nx">context</span><span class="p">.</span><span class="nx">request</span><span class="p">?.</span><span class="nx">request</span><span class="p">.</span><span class="nx">modeId</span>
    <span class="p">:</span> <span class="kc">undefined</span><span class="p">;</span>
  <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">modeId</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">modeId</span> <span class="o">=</span> <span class="nx">jokerTemplate</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span> <span class="c1">// fallback to default mode if no mode is specified in the request</span>
  <span class="p">}</span>

  <span class="kd">const</span> <span class="nx">isCustomized</span> <span class="o">=</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">promptService</span><span class="p">.</span><span class="nf">getPromptVariantInfo</span><span class="p">(</span><span class="nx">modeId</span><span class="p">)?.</span><span class="nx">isCustomized</span> <span class="o">??</span> <span class="kc">false</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">resolvedPrompt</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">promptService</span><span class="p">.</span><span class="nf">getResolvedPromptFragment</span><span class="p">(</span>
    <span class="nx">modeId</span><span class="p">,</span>
    <span class="kc">undefined</span><span class="p">,</span>
    <span class="nx">context</span><span class="p">,</span>
  <span class="p">);</span>
  <span class="k">return</span> <span class="nx">resolvedPrompt</span>
    <span class="p">?</span> <span class="nx">SystemMessageDescription</span><span class="p">.</span><span class="nf">fromResolvedPromptFragment</span><span class="p">(</span>
        <span class="nx">resolvedPrompt</span><span class="p">,</span>
        <span class="nx">modeId</span><span class="p">,</span>
        <span class="nx">isCustomized</span><span class="p">,</span>
      <span class="p">)</span>
    <span class="p">:</span> <span class="kc">undefined</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Build the Theia browser application</li>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>In the <em>AI Chat</em> enter a prompt that uses the <code class="language-plaintext highlighter-rouge">@Joker</code> agent. Do not send the chat request yet.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Joker tell me a joke about batman
</code></pre></div>    </div>
  </li>
  <li>After selecting the <code class="language-plaintext highlighter-rouge">Joker</code> agent, a combobox appears to allow the user to select an <em>Agent Mode</em></li>
  <li>Select the <em>Swear Mode</em>
    <figure class="">
<img src="/blog/assets/images/theia_chat_agent_mode.png" alt="" /></figure>
  </li>
  <li>Send the chat request</li>
  <li>Check that there is now only a joke in the response and no file is generated. The response should also contain multiple emojis. :smile:</li>
</ul>

<p>At this point, we use the <em>Agent Mode</em> to let a user easily switch between available <em>Prompt Variants</em>. The implementation shown above is a simplified version of the existing <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-ide/src/browser/mode-aware-chat-agent.ts"><code class="language-plaintext highlighter-rouge">AbstractModeAwareChatAgent</code></a>, and it helps explain <em>Agent Mode</em> support in general. For this use case, we now change the implementation to extend <code class="language-plaintext highlighter-rouge">AbstractModeAwareChatAgent</code>.</p>

<ul>
  <li>Open a <strong>Terminal</strong>
    <ul>
      <li>Switch to the <em>theia/ai-extension</em> directory</li>
      <li>
        <p>Add <code class="language-plaintext highlighter-rouge">@theia/ai-ide</code> as a dependency</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @theia/ai-ide
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-joker-agent.ts</em>
    <ul>
      <li>Update the imports and add the <code class="language-plaintext highlighter-rouge">AbstractModeAwareChatAgent</code> from the <code class="language-plaintext highlighter-rouge">@theia/ai-ide</code> package and the <code class="language-plaintext highlighter-rouge">ChatMode</code> from the <code class="language-plaintext highlighter-rouge">@theia/ai-chat</code> package
        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">ChatAgentService</span><span class="p">,</span> <span class="nx">ChatMode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-chat</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
  <span class="nx">BasePromptFragment</span><span class="p">,</span>
  <span class="nx">LanguageModelRequirement</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AbstractModeAwareChatAgent</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-ide/lib/browser/mode-aware-chat-agent</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CREATE_JOKE_FILE_FUNCTION_ID</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ai-extension-contribution</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">inject</span><span class="p">,</span> <span class="nx">injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
      <li>Change the <code class="language-plaintext highlighter-rouge">JokerChatAgent</code> to extend <code class="language-plaintext highlighter-rouge">AbstractModeAwareChatAgent</code> instead of <code class="language-plaintext highlighter-rouge">AbstractStreamParsingChatAgent</code>
        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">JokerChatAgent</span> <span class="kd">extends</span> <span class="nc">AbstractModeAwareChatAgent</span> <span class="p">{</span>
</code></pre></div>        </div>
      </li>
      <li>Change the <code class="language-plaintext highlighter-rouge">modes</code> field to <code class="language-plaintext highlighter-rouge">modeDefinitions</code> in the following format
        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span> <span class="k">readonly</span> <span class="nx">modeDefinitions</span><span class="p">:</span> <span class="nb">Omit</span><span class="o">&lt;</span><span class="nx">ChatMode</span><span class="p">,</span> <span class="dl">"</span><span class="s2">isDefault</span><span class="dl">"</span><span class="o">&gt;</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[</span>
  <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">jokerTemplate</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Default Mode</span><span class="dl">"</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">jokerTemplateSimple</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Simple Mode</span><span class="dl">"</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">jokerTemplateSwear</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Swear Mode</span><span class="dl">"</span> <span class="p">},</span>
<span class="p">];</span>
</code></pre></div>        </div>
      </li>
      <li>Remove the method <code class="language-plaintext highlighter-rouge">getSystemMessageDescription()</code> as it is implemented in <code class="language-plaintext highlighter-rouge">AbstractModeAwareChatAgent</code></li>
    </ul>
  </li>
  <li>Build the Theia browser application</li>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>In the <em>AI Chat</em> enter a prompt that uses the <code class="language-plaintext highlighter-rouge">@Joker</code> agent. Do not send the chat request yet.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Joker tell me a joke about batman
</code></pre></div>    </div>
  </li>
  <li>After selecting the <code class="language-plaintext highlighter-rouge">Joker</code> agent, a combobox appears to allow the user to select an <em>Agent Mode</em></li>
  <li>Select the <em>Swear Mode</em>
    <figure class="">
<img src="/blog/assets/images/theia_chat_agent_mode.png" alt="" /></figure>
  </li>
  <li>Send the chat request</li>
  <li>Check that there is now only a joke in the response and no file is generated. The response should also contain multiple emojis. :smile:</li>
</ul>

<p>Further information about <em>Agent Modes</em> can be found here:</p>

<ul>
  <li><a href="https://theia-ide.org/docs/theia_ai/#agent-modes">Agent Modes</a></li>
  <li><a href="https://eclipsesource.com/blogs/2026/02/12/eclipse-theia-1-68-release-news-and-noteworthy/">Eclipse Theia 1.68 Release: News and Noteworthy</a></li>
  <li><a href="https://github.com/eclipse-theia/theia/pull/16860">eclipse-theia/theia#16860</a></li>
</ul>

<h4 id="agent-specific-variables">Agent-specific Variables</h4>

<p>Additionally to <em>Tool Functions</em> that can be used in prompts, you can define <em>Variables</em> that can be resolved at runtime. There are <a href="https://theia-ide.org/docs/theia_ai/#global-variables"><em>Global Variables</em></a> and <a href="https://theia-ide.org/docs/theia_ai/#agent-specific-variables"><em>Agent-specific Variables</em></a>. <em>Global Variables</em> are available to all agents. Theia provides some <em>Global Variables</em> like for example <strong>#selectedText</strong> for the currently selected text or <strong>#currentFileContent</strong> for the whole content of the currently opened file.</p>

<p>You can see which <em>Global Variables</em> are available in the <em>AI Configuration</em> on the <em>Variables</em> tab. To use a <em>Global Variable</em> you can type a <strong>#</strong> in the <em>AI Chat</em> and then select it from the list of available variables, e.g. open a joke file that was created before and write something like the following to the chat:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>explain #currentFileContent
</code></pre></div></div>

<p>I will not go into detail on how to implement <em>Global Variables</em> here. The <a href="https://theia-ide.org/docs/theia_ai/#global-variables"><em>Global Variables</em></a> section in the Theia documentation and the implementation of the <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-core/src/common/today-variable-contribution.ts"><strong>#today</strong></a> variable.</p>

<p>For creating custom agents programmatically, it can be interesting to define <a href="https://theia-ide.org/docs/theia_ai/#agent-specific-variables"><em>Agent-specific Variables</em></a>. Such variables can be filled by any Theia API and enables extended use cases, like adding data to a prompt dynamically and even adding data before the work is delegated to another agent.</p>

<p>In this section we will implement another chat agent that uses <em>Agent-specific Variables</em> and is able to write content into a file in the workspace. The following implementation is used to get a better understanding of a programmatically defined agent and the usage of <em>Agent-specific Variables</em>.</p>

<ul>
  <li>Create a new file <em>ai-extension/src/browser/ai-extension-writer-agent.ts</em>
    <ul>
      <li>Create a new class <code class="language-plaintext highlighter-rouge">WriterChatAgent</code> that extends <code class="language-plaintext highlighter-rouge">AbstractStreamParsingChatAgent</code></li>
      <li>Specify a prompt that
        <ul>
          <li>uses the variable placeholders <code class="language-plaintext highlighter-rouge">{{folder}}</code> and <code class="language-plaintext highlighter-rouge">{{content}}</code></li>
          <li>uses the <code class="language-plaintext highlighter-rouge">jokeFileCreator</code> <em>Tool Function</em></li>
        </ul>
      </li>
      <li>Define the <code class="language-plaintext highlighter-rouge">agentSpecificVariables</code> field to declare the variables <code class="language-plaintext highlighter-rouge">folder</code> and <code class="language-plaintext highlighter-rouge">content</code> which enables to keep track of used variables and even shows that information on the agent configuration page.</li>
      <li>Implement <code class="language-plaintext highlighter-rouge">getSystemMessageDescription()</code> where the variables are resolved
        <ul>
          <li>Extract the values from the <code class="language-plaintext highlighter-rouge">request</code></li>
          <li>If they are not present in the <code class="language-plaintext highlighter-rouge">request</code>, define a default value</li>
        </ul>
      </li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">AbstractStreamParsingChatAgent</span><span class="p">,</span>
  <span class="nx">SystemMessageDescription</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-chat</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
  <span class="nx">AIVariableContext</span><span class="p">,</span>
  <span class="nx">BasePromptFragment</span><span class="p">,</span>
  <span class="nx">LanguageModelRequirement</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CREATE_JOKE_FILE_FUNCTION_ID</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ai-extension-contribution</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">writerTemplate</span><span class="p">:</span> <span class="nx">BasePromptFragment</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">writer-system-default</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">template</span><span class="p">:</span> <span class="s2">`
      # Instructions
      
      You are an agent that operates in the current workspace of the Theia IDE. 
      You are able to persist the provided content into a file by using ~{</span><span class="p">${</span><span class="nx">CREATE_JOKE_FILE_FUNCTION_ID</span><span class="p">}</span><span class="s2">}.
      Derive the filename out of the provided content if no filename is provided.
      The file should be created in the folder {{folder}} in the current workspace.
  
      The content to persist is as follows:
      {{content}}
      `</span><span class="p">,</span>
<span class="p">};</span>

<span class="p">@</span><span class="nd">injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">WriterChatAgent</span> <span class="kd">extends</span> <span class="nc">AbstractStreamParsingChatAgent</span> <span class="p">{</span>
  <span class="nl">id</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Writer</span><span class="dl">"</span><span class="p">;</span>
  <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Writer</span><span class="dl">"</span><span class="p">;</span>
  <span class="nl">languageModelRequirements</span><span class="p">:</span> <span class="nx">LanguageModelRequirement</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
      <span class="na">purpose</span><span class="p">:</span> <span class="dl">"</span><span class="s2">chat</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">identifier</span><span class="p">:</span> <span class="dl">"</span><span class="s2">default/universal</span><span class="dl">"</span><span class="p">,</span>
    <span class="p">},</span>
  <span class="p">];</span>
  <span class="k">protected</span> <span class="nx">defaultLanguageModelPurpose</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">chat</span><span class="dl">"</span><span class="p">;</span>
  <span class="nx">override</span> <span class="nx">description</span> <span class="o">=</span>
    <span class="dl">"</span><span class="s2">This is an agent that is able to persist content into a file in the workspace.</span><span class="dl">"</span><span class="p">;</span>

  <span class="nx">override</span> <span class="nx">iconClass</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">codicon codicon-new-file</span><span class="dl">"</span><span class="p">;</span>
  <span class="k">protected</span> <span class="nx">override</span> <span class="nx">systemPromptId</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">writer-system</span><span class="dl">"</span><span class="p">;</span>
  <span class="nx">override</span> <span class="nx">prompts</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">writer-system</span><span class="dl">"</span><span class="p">,</span> <span class="na">defaultVariant</span><span class="p">:</span> <span class="nx">writerTemplate</span><span class="p">,</span> <span class="na">variants</span><span class="p">:</span> <span class="p">[]</span> <span class="p">},</span>
  <span class="p">];</span>
  <span class="nx">override</span> <span class="nx">functions</span> <span class="o">=</span> <span class="p">[</span><span class="nx">CREATE_JOKE_FILE_FUNCTION_ID</span><span class="p">];</span>
  <span class="nx">override</span> <span class="nx">agentSpecificVariables</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
      <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">folder</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">The folder in which the file should be created.</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">usedInPrompt</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
      <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">content</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">The content to persist into the file.</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">usedInPrompt</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="p">},</span>
  <span class="p">];</span>

  <span class="k">protected</span> <span class="nx">override</span> <span class="k">async</span> <span class="nf">getSystemMessageDescription</span><span class="p">(</span>
    <span class="nx">context</span><span class="p">:</span> <span class="nx">AIVariableContext</span><span class="p">,</span>
  <span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">SystemMessageDescription</span> <span class="o">|</span> <span class="kc">undefined</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// extract data from the context</span>
    <span class="kd">let</span> <span class="nx">request</span> <span class="o">=</span> <span class="p">(</span><span class="nx">context</span> <span class="kd">as </span><span class="kr">any</span><span class="p">).</span><span class="nx">request</span><span class="p">;</span>
    <span class="kd">let</span> <span class="nx">folder</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nf">getDataByKey</span><span class="p">(</span><span class="s2">`folder`</span><span class="p">)</span> <span class="kd">as </span><span class="kr">string</span><span class="p">;</span>
    <span class="kd">let</span> <span class="nx">content</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nf">getDataByKey</span><span class="p">(</span><span class="s2">`content`</span><span class="p">)</span> <span class="kd">as </span><span class="kr">string</span><span class="p">;</span>

    <span class="c1">// provide default values if not set</span>
    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">folder</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">folder</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">temp</span><span class="dl">"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">content</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">content</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Lorem ipsum dolor sit amet.</span><span class="dl">"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">const</span> <span class="nx">variableValues</span> <span class="o">=</span> <span class="p">{</span>
      <span class="na">folder</span><span class="p">:</span> <span class="nx">folder</span><span class="p">,</span>
      <span class="na">content</span><span class="p">:</span> <span class="nx">content</span><span class="p">,</span>
    <span class="p">};</span>

    <span class="c1">// get the resolved prompt</span>
    <span class="kd">const</span> <span class="nx">resolvedPrompt</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">promptService</span><span class="p">.</span><span class="nf">getResolvedPromptFragment</span><span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">systemPromptId</span><span class="p">,</span>
      <span class="nx">variableValues</span><span class="p">,</span>
      <span class="nx">context</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// return the system message description</span>
    <span class="k">return</span> <span class="nx">resolvedPrompt</span>
      <span class="p">?</span> <span class="nx">SystemMessageDescription</span><span class="p">.</span><span class="nf">fromResolvedPromptFragment</span><span class="p">(</span><span class="nx">resolvedPrompt</span><span class="p">)</span>
      <span class="p">:</span> <span class="kc">undefined</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-frontend-module.ts</em>
    <ul>
      <li>Register the new <code class="language-plaintext highlighter-rouge">WriterChatAgent</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">bind</span><span class="p">(</span><span class="nx">WriterChatAgent</span><span class="p">).</span><span class="nf">toSelf</span><span class="p">().</span><span class="nf">inSingletonScope</span><span class="p">();</span>
<span class="nf">bind</span><span class="p">(</span><span class="nx">Agent</span><span class="p">).</span><span class="nf">toService</span><span class="p">(</span><span class="nx">WriterChatAgent</span><span class="p">);</span>
<span class="nf">bind</span><span class="p">(</span><span class="nx">ChatAgent</span><span class="p">).</span><span class="nf">toService</span><span class="p">(</span><span class="nx">WriterChatAgent</span><span class="p">);</span>
</code></pre></div>    </div>
  </li>
  <li>Build the Theia browser application</li>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>In the <em>AI Chat</em> enter a prompt that uses the <code class="language-plaintext highlighter-rouge">@Writer</code> agent.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Writer write a file
</code></pre></div>    </div>
  </li>
</ul>

<p>You should see that a new file is generated in a new folder <em>temp</em> that contains the text <em>Lorem ipsum dolor sit amet.</em></p>

<p>Further information about variables in Theia AI can be found here:</p>

<ul>
  <li><a href="https://theia-ide.org/docs/user_ai/#context-variables">AI Usage - Context Variables</a></li>
  <li><a href="https://theia-ide.org/docs/theia_ai/#variables">AI Developer - Variables</a></li>
</ul>

<h4 id="agent-to-agent-delegation-programmatically">Agent-to-Agent Delegation (programmatically)</h4>

<p>You can delegate the processing from one agent to another by using the <code class="language-plaintext highlighter-rouge">delegateToAgent</code> <em>Tool Function</em>. This is described in <a href="https://theia-ide.org/docs/user_ai/#agent-to-agent-delegation">Agent-to-Agent Delegation</a> in the Theia documentation, and I describe this also later in <a href="#agent-to-agent-delegation-function">Agent-to-Agent Delegation (function)</a>. As in some cases it might be interesting to delegate to another agent programmatically, I will describe this approach in the following section. Delegating to another agent can be done by using the <code class="language-plaintext highlighter-rouge">ChatAgentService</code> Theia API.</p>

<p>In the following section we will extend the <code class="language-plaintext highlighter-rouge">JokerChatAgent</code> to programmatically forward to the <code class="language-plaintext highlighter-rouge">WriterChatAgent</code> if the <em>joker-system-simple</em> prompt template was selected.
There are different ways to find out which <em>Prompt Template</em> was selected, dependent on whether <em>Agent Modes</em> are used that map to <em>Prompt Templates</em> or if the selection can only be done in the settings.</p>

<ul>
  <li>To retrieve the selected <em>Prompt Template</em> from the settings, use <code class="language-plaintext highlighter-rouge">PromptService#getSelectedVariantId(promptVariantSetId)</code> or <code class="language-plaintext highlighter-rouge">PromptService#getEffectiveVariantId(promptVariantSetId)</code></li>
  <li>
    <p>To retrieve the selected <em>Agent Mode</em> inspect the chat request object via <code class="language-plaintext highlighter-rouge">request.request.modeId</code>.</p>
  </li>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-joker-agent.ts</em>
    <ul>
      <li>Get the <code class="language-plaintext highlighter-rouge">ChatAgentService</code> injected
        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">inject</span><span class="p">(</span><span class="nx">ChatAgentService</span><span class="p">)</span>
<span class="k">protected</span> <span class="nx">chatAgentService</span><span class="p">:</span> <span class="nx">ChatAgentService</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
      <li>Override <code class="language-plaintext highlighter-rouge">addContentsToResponse()</code>
        <ul>
          <li>Check if the selected variant was the <em>joker-system-simple</em> template by using the <code class="language-plaintext highlighter-rouge">PromptService</code></li>
          <li>Add the <em>content</em> and the <em>folder</em> data to the <code class="language-plaintext highlighter-rouge">request</code></li>
          <li>Get the <code class="language-plaintext highlighter-rouge">WriterChatAgent</code> by using the <code class="language-plaintext highlighter-rouge">ChatAgentService</code></li>
          <li>Delegate the request to the <code class="language-plaintext highlighter-rouge">WriterChatAgent</code></li>
        </ul>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span> <span class="nx">override</span> <span class="k">async</span> <span class="nf">addContentsToResponse</span><span class="p">(</span>
  <span class="nx">response</span><span class="p">:</span> <span class="nx">LanguageModelResponse</span><span class="p">,</span>
  <span class="nx">request</span><span class="p">:</span> <span class="nx">MutableChatRequestModel</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="k">await</span> <span class="k">super</span><span class="p">.</span><span class="nf">addContentsToResponse</span><span class="p">(</span><span class="nx">response</span><span class="p">,</span> <span class="nx">request</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">selectedVariantId</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">modeId</span>
    <span class="p">?</span> <span class="nx">request</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">modeId</span>
    <span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">promptService</span><span class="p">.</span><span class="nf">getEffectiveVariantId</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">systemPromptId</span><span class="p">);</span>

  <span class="k">if </span><span class="p">(</span><span class="nx">selectedVariantId</span> <span class="o">===</span> <span class="nx">jokerTemplateSimple</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// extract information from the result and add it to the request</span>
    <span class="kd">const</span> <span class="nx">responseText</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getTextOfResponse</span><span class="p">(</span><span class="nx">response</span><span class="p">);</span>
    <span class="nx">request</span><span class="p">.</span><span class="nf">addData</span><span class="p">(</span><span class="dl">"</span><span class="s2">content</span><span class="dl">"</span><span class="p">,</span> <span class="nx">responseText</span><span class="p">);</span>
    <span class="nx">request</span><span class="p">.</span><span class="nf">addData</span><span class="p">(</span><span class="dl">"</span><span class="s2">folder</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">bat-jokes</span><span class="dl">"</span><span class="p">);</span>

    <span class="kd">const</span> <span class="nx">agent</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">chatAgentService</span><span class="p">.</span><span class="nf">getAgent</span><span class="p">(</span><span class="dl">"</span><span class="s2">Writer</span><span class="dl">"</span><span class="p">);</span>
    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">agent</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="s2">`Chat agent "Writer" not found.`</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="k">await</span> <span class="nx">agent</span><span class="p">.</span><span class="nf">invoke</span><span class="p">(</span><span class="nx">request</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Update the import statements to fix the compile errors</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">AbstractStreamParsingChatAgent</span><span class="p">,</span>
  <span class="nx">ChatAgentService</span><span class="p">,</span>
  <span class="nx">MutableChatRequestModel</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-chat</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
  <span class="nx">BasePromptFragment</span><span class="p">,</span>
  <span class="nx">getTextOfResponse</span><span class="p">,</span>
  <span class="nx">LanguageModelRequirement</span><span class="p">,</span>
  <span class="nx">LanguageModelResponse</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CREATE_JOKE_FILE_FUNCTION_ID</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ai-extension-contribution</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">inject</span><span class="p">,</span> <span class="nx">injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Build the Theia browser application</li>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em>
    <ul>
      <li>Switch to the <em>Agents</em> tab</li>
      <li>Select the <code class="language-plaintext highlighter-rouge">Joker</code> agent</li>
      <li>Ensure that the <em>joker-system-simple</em> entry is selected in the <em>Prompt Templates</em> combobox<br />
(note that this will preselect the <em>Simple Mode</em> agent mode)</li>
    </ul>
  </li>
  <li>In the <em>AI Chat</em> enter a prompt that uses the <code class="language-plaintext highlighter-rouge">@Joker</code> agent.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Joker a joke about scarecrow
</code></pre></div>    </div>
  </li>
  <li>Verify that the <em>Simple Mode</em> is selected in the <em>Agent Mode</em> combobox</li>
  <li>Check that now there is a joke in the response and a file is generated.</li>
</ul>

<p>The above implementation is of course a pretty simple one with the intention to show how it basically works. A more advanced example on how to programmatically forward from one agent to another can be seen in the <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-ide/src/common/orchestrator-chat-agent.ts"><code class="language-plaintext highlighter-rouge">@Orchestrator</code> chat agent implementation</a> provided by Theia itself.</p>

<p>And if you are interested in the implementation of the <code class="language-plaintext highlighter-rouge">delegateToAgent</code> <em>Tool Function</em>, have a look <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-chat/src/browser/agent-delegation-tool.ts">here</a>.</p>

<h4 id="custom-response-part-rendering">Custom Response Part Rendering</h4>

<p>Another interesting feature when implementing a <em>Custom Agent</em> in Theia, is the ability to fully customize the chat response rendering. This can be done by overriding <code class="language-plaintext highlighter-rouge">AbstractChatAgent#addContentsToResponse()</code> and adding additional content of type <code class="language-plaintext highlighter-rouge">ChatResponseContent</code> to the response.</p>

<p>There are already several default implementations of <code class="language-plaintext highlighter-rouge">ChatResponseContent</code> available that can be used to customize the response:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">CodeChatResponseContent</code></li>
  <li><code class="language-plaintext highlighter-rouge">CommandChatResponseContent</code></li>
  <li><code class="language-plaintext highlighter-rouge">ErrorChatResponseContent</code></li>
  <li><code class="language-plaintext highlighter-rouge">HorizontalLayoutChatResponseContent</code></li>
  <li><code class="language-plaintext highlighter-rouge">InformationalChatResponseContent</code></li>
  <li><code class="language-plaintext highlighter-rouge">MarkdownChatResponseContent</code></li>
  <li><code class="language-plaintext highlighter-rouge">ProgressChatResponseContent</code></li>
  <li><code class="language-plaintext highlighter-rouge">QuestionResponseContent</code></li>
  <li><code class="language-plaintext highlighter-rouge">TextChatResponseContent</code></li>
  <li><code class="language-plaintext highlighter-rouge">ThinkingChatResponseContent</code></li>
  <li><code class="language-plaintext highlighter-rouge">ToolCallChatResponseContent</code></li>
</ul>

<p>Those default interfaces and implementations are located in <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-chat/src/common/chat-model.ts">chat-model.ts</a>.</p>

<p>To add a simple static text to the response, you can use the <code class="language-plaintext highlighter-rouge">TextChatResponseContent</code>, or if it should be formatted in some way the <code class="language-plaintext highlighter-rouge">MarkdownChatResponseContent</code>.</p>

<ul>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-joker-agent.ts</em>
    <ul>
      <li>In the method <code class="language-plaintext highlighter-rouge">addContentsToResponse()</code> add a <code class="language-plaintext highlighter-rouge">TextChatResponseContentImpl</code> or a <code class="language-plaintext highlighter-rouge">MarkdownChatResponseContentImpl</code>
        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">request</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nf">addContent</span><span class="p">(</span>
  <span class="k">new</span> <span class="nc">TextChatResponseContentImpl</span><span class="p">(</span>
    <span class="s2">`Hilarious, Batman will never recover from this as he will always try to remember my distracting jokes!`</span><span class="p">,</span>
  <span class="p">),</span>
<span class="p">);</span>
</code></pre></div>        </div>
        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">request</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nf">addContent</span><span class="p">(</span>
  <span class="k">new</span> <span class="nc">MarkdownChatResponseContentImpl</span><span class="p">(</span>
    <span class="s2">`Hilarious, **Batman** :bat: will never recover from this :dizzy_face: as he will always try to remember my _distracting jokes_!`</span><span class="p">,</span>
  <span class="p">),</span>
<span class="p">);</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>You can also implement your own <code class="language-plaintext highlighter-rouge">ChatResponseContent</code> with a corresponding renderer, to have even more control on how the response should be rendered. This is described in more detail in <a href="https://theia-ide.org/docs/theia_ai/#custom-response-part-rendering">Custom Response Part Rendering</a> of the Theia documentation.</p>

<p>You can also register a custom renderer for the existing <code class="language-plaintext highlighter-rouge">ChatResponseContent</code> implementations. We will create a customized renderer for the <code class="language-plaintext highlighter-rouge">QuestionResponseContent</code> and extend the <code class="language-plaintext highlighter-rouge">JokerChatAgent</code> to ask the user if the joke should be saved in a file.</p>

<p><em><strong>Note:</strong></em><br />
We are doing this because the default <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-chat-ui/src/browser/chat-response-renderer/question-part-renderer.tsx">QuestionPartRenderer</a> implementation is intended to ask the user as part of the prompt and shows the buttons disabled if the process is <strong>not</strong> waiting for input. In our case the first step is done and we do not put the process in a waiting state. If you are interested in the question-response-handling with the waiting state, have a look at the Theia <a href="https://github.com/eclipse-theia/theia/blob/master/examples/api-samples/src/browser/chat/ask-and-continue-chat-agent-contribution.ts">AskAndContinueChatAgent</a> example.</p>

<ul>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-joker-agent.ts</em>
    <ul>
      <li>In the method <code class="language-plaintext highlighter-rouge">addContentsToResponse()</code> add a <code class="language-plaintext highlighter-rouge">QuestionResponseContentImpl</code>
        <ul>
          <li>Ask the user if the joke should be saved to a file.</li>
          <li>If the answer is <em>Yes</em> delegate to the <code class="language-plaintext highlighter-rouge">Writer</code> agent.</li>
          <li>If the answer is <em>No</em> show the markup message from the previous section.</li>
        </ul>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span> <span class="nx">override</span> <span class="k">async</span> <span class="nf">addContentsToResponse</span><span class="p">(</span>
  <span class="nx">response</span><span class="p">:</span> <span class="nx">LanguageModelResponse</span><span class="p">,</span>
  <span class="nx">request</span><span class="p">:</span> <span class="nx">MutableChatRequestModel</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="k">await</span> <span class="k">super</span><span class="p">.</span><span class="nf">addContentsToResponse</span><span class="p">(</span><span class="nx">response</span><span class="p">,</span> <span class="nx">request</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">selectedVariantId</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">modeId</span>
    <span class="p">?</span> <span class="nx">request</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">modeId</span>
    <span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">promptService</span><span class="p">.</span><span class="nf">getEffectiveVariantId</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">systemPromptId</span><span class="p">);</span>

  <span class="k">if </span><span class="p">(</span><span class="nx">selectedVariantId</span> <span class="o">===</span> <span class="nx">jokerTemplateSimple</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// if the simple variant is selected, ask if the user wants to delegate to the Writer agent</span>
    <span class="nx">request</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nf">addContent</span><span class="p">(</span>
      <span class="k">new</span> <span class="nc">QuestionResponseContentImpl</span><span class="p">(</span>
        <span class="s2">`I have created a funny joke for you. Would you like me to save it to a file using the Writer agent?`</span><span class="p">,</span>
        <span class="p">[</span>
          <span class="p">{</span> <span class="na">text</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Yes</span><span class="dl">"</span><span class="p">,</span> <span class="na">value</span><span class="p">:</span> <span class="dl">"</span><span class="s2">yes</span><span class="dl">"</span> <span class="p">},</span>
          <span class="p">{</span> <span class="na">text</span><span class="p">:</span> <span class="dl">"</span><span class="s2">No</span><span class="dl">"</span><span class="p">,</span> <span class="na">value</span><span class="p">:</span> <span class="dl">"</span><span class="s2">no</span><span class="dl">"</span> <span class="p">},</span>
        <span class="p">],</span>
        <span class="nx">request</span><span class="p">,</span>
        <span class="k">async </span><span class="p">(</span><span class="nx">selectedOption</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="k">if </span><span class="p">(</span><span class="nx">selectedOption</span><span class="p">.</span><span class="nx">value</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">yes</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// get the response text and add it to the request for the Writer agent</span>
            <span class="kd">const</span> <span class="nx">responseText</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getTextOfResponse</span><span class="p">(</span><span class="nx">response</span><span class="p">);</span>
            <span class="nx">request</span><span class="p">.</span><span class="nf">addData</span><span class="p">(</span><span class="dl">"</span><span class="s2">content</span><span class="dl">"</span><span class="p">,</span> <span class="nx">responseText</span><span class="p">);</span>
            <span class="nx">request</span><span class="p">.</span><span class="nf">addData</span><span class="p">(</span><span class="dl">"</span><span class="s2">folder</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">bat-jokes</span><span class="dl">"</span><span class="p">);</span>

            <span class="c1">// delegate to the Writer agent to save the joke to a file</span>
            <span class="kd">const</span> <span class="nx">agent</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">chatAgentService</span><span class="p">.</span><span class="nf">getAgent</span><span class="p">(</span><span class="dl">"</span><span class="s2">Writer</span><span class="dl">"</span><span class="p">);</span>
            <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">agent</span><span class="p">)</span> <span class="p">{</span>
              <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="s2">`Chat agent "Writer" not found.`</span><span class="p">);</span>
            <span class="p">}</span>

            <span class="k">await</span> <span class="nx">agent</span><span class="p">.</span><span class="nf">invoke</span><span class="p">(</span><span class="nx">request</span><span class="p">);</span>
          <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// don't save to file, just add a funny closing remark</span>
            <span class="nx">request</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nf">addContent</span><span class="p">(</span>
              <span class="k">new</span> <span class="nc">MarkdownChatResponseContentImpl</span><span class="p">(</span>
                <span class="s2">`Hilarious, **Batman** :bat: will never recover from this :dizzy_face: as he will always try to remember my _distracting jokes_!`</span><span class="p">,</span>
              <span class="p">),</span>
            <span class="p">);</span>
          <span class="p">}</span>
        <span class="p">},</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Create a new file <em>ai-extension/src/browser/no-wait-question-part-renderer.tsx</em>
    <ul>
      <li>Copy the code from <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-chat-ui/src/browser/chat-response-renderer/question-part-renderer.tsx">QuestionPartRenderer</a></li>
      <li>Change the name to <code class="language-plaintext highlighter-rouge">NoWaitQuestionPartRenderer</code></li>
      <li>In the <code class="language-plaintext highlighter-rouge">canHandle()</code> method return a value &gt; 10 to ensure that our renderer is picked in the rendering process</li>
      <li>
        <p>Change the definition of <code class="language-plaintext highlighter-rouge">isDisabled</code> to only check the <code class="language-plaintext highlighter-rouge">question.selectedOption</code> to ensure the buttons are shown disabled once the user selected an option. The other statements would show the buttons always disabled in our case, as we do not put the processing in a waiting state.</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">ChatResponseContent</span><span class="p">,</span>
  <span class="nx">QuestionResponseContent</span><span class="p">,</span>
  <span class="nx">QuestionResponseHandler</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-chat</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">React</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/react</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ReactNode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/react</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ChatResponsePartRenderer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-chat-ui/lib/browser/chat-response-part-renderer</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ResponseNode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-chat-ui/lib/browser/chat-tree-view</span><span class="dl">"</span><span class="p">;</span>

<span class="p">@</span><span class="nd">injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">NoWaitQuestionPartRenderer</span> <span class="k">implements</span> <span class="nx">ChatResponsePartRenderer</span><span class="o">&lt;</span><span class="nx">QuestionResponseContent</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="nf">canHandle</span><span class="p">(</span><span class="na">response</span><span class="p">:</span> <span class="nx">ChatResponseContent</span><span class="p">):</span> <span class="kr">number</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">QuestionResponseContent</span><span class="p">.</span><span class="k">is</span><span class="p">(</span><span class="nx">response</span><span class="p">))</span> <span class="p">{</span>
      <span class="k">return</span> <span class="mi">100</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">render</span><span class="p">(</span><span class="na">question</span><span class="p">:</span> <span class="nx">QuestionResponseContent</span><span class="p">,</span> <span class="na">node</span><span class="p">:</span> <span class="nx">ResponseNode</span><span class="p">):</span> <span class="nx">ReactNode</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">isDisabled</span> <span class="o">=</span> <span class="nx">question</span><span class="p">.</span><span class="nx">selectedOption</span> <span class="o">!==</span> <span class="kc">undefined</span><span class="p">;</span>

    <span class="k">return </span><span class="p">(</span>
      <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">theia-QuestionPartRenderer-root</span><span class="dl">"</span><span class="o">&gt;</span>
        <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">theia-QuestionPartRenderer-question</span><span class="dl">"</span><span class="o">&gt;</span>
          <span class="p">{</span><span class="nx">question</span><span class="p">.</span><span class="nx">question</span><span class="p">}</span>
        <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">theia-QuestionPartRenderer-options</span><span class="dl">"</span><span class="o">&gt;</span>
          <span class="p">{</span><span class="nx">question</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">option</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
            <span class="o">&lt;</span><span class="nx">button</span>
              <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="s2">`theia-button theia-QuestionPartRenderer-option </span><span class="p">${</span>
                <span class="nx">question</span><span class="p">.</span><span class="nx">selectedOption</span><span class="p">?.</span><span class="nx">text</span> <span class="o">===</span> <span class="nx">option</span><span class="p">.</span><span class="nx">text</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">selected</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">""</span>
              <span class="p">}</span><span class="s2">`</span><span class="p">}</span>
              <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">question</span><span class="p">.</span><span class="nx">isReadOnly</span> <span class="o">&amp;&amp;</span> <span class="nx">question</span><span class="p">.</span><span class="nx">handler</span><span class="p">)</span> <span class="p">{</span>
                  <span class="nx">question</span><span class="p">.</span><span class="nx">selectedOption</span> <span class="o">=</span> <span class="nx">option</span><span class="p">;</span>
                  <span class="p">(</span><span class="nx">question</span><span class="p">.</span><span class="nx">handler</span> <span class="kd">as </span><span class="nx">QuestionResponseHandler</span><span class="p">)(</span><span class="nx">option</span><span class="p">);</span>
                <span class="p">}</span>
              <span class="p">}}</span>
              <span class="nx">disabled</span><span class="o">=</span><span class="p">{</span><span class="nx">isDisabled</span><span class="p">}</span>
              <span class="nx">key</span><span class="o">=</span><span class="p">{</span><span class="nx">index</span><span class="p">}</span>
            <span class="o">&gt;</span>
              <span class="p">{</span><span class="nx">option</span><span class="p">.</span><span class="nx">text</span><span class="p">}</span>
            <span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt;
</span>          <span class="p">))}</span>
        <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Open the file <em>ai-extension/src/browser/ai-extension-frontend-module.ts</em>
    <ul>
      <li>Import the <code class="language-plaintext highlighter-rouge">ChatResponsePartRenderer</code> interface
        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">ChatResponsePartRenderer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/ai-chat-ui/lib/browser/chat-response-part-renderer</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Register the new <code class="language-plaintext highlighter-rouge">NoWaitQuestionPartRenderer</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">bind</span><span class="p">(</span><span class="nx">ChatResponsePartRenderer</span><span class="p">)</span>
  <span class="p">.</span><span class="nf">to</span><span class="p">(</span><span class="nx">NoWaitQuestionPartRenderer</span><span class="p">)</span>
  <span class="p">.</span><span class="nf">inSingletonScope</span><span class="p">();</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Build the Theia browser application</li>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em>
    <ul>
      <li>Switch to the <em>Agents</em> tab</li>
      <li>Select the <code class="language-plaintext highlighter-rouge">Joker</code> agent</li>
      <li>Ensure that the <em>joker-system-simple</em> entry is selected in the <em>Prompt Templates</em> combobox</li>
    </ul>
  </li>
  <li>In the <em>AI Chat</em> enter a prompt that uses the <code class="language-plaintext highlighter-rouge">@Joker</code> agent.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Joker a joke about scarecrow
</code></pre></div>    </div>
  </li>
  <li>
    <p>Check that additionally to the joke you are asked whether to save the joke to a file and two buttons to select how to proceed</p>

    <figure class="">
<img src="/blog/assets/images/theia_chat_question.png" alt="" /></figure>
  </li>
  <li>
    <p>If you select <em>Yes</em> the file should be saved</p>

    <figure class="">
<img src="/blog/assets/images/theia_chat_question_yes.png" alt="" /></figure>
  </li>
  <li>
    <p>If you select <em>No</em> you should only get the markdown formatted response</p>

    <figure class="">
<img src="/blog/assets/images/theia_chat_question_no.png" alt="" /></figure>
  </li>
</ul>

<h2 id="further-customizations">Further Customizations</h2>

<p>Users can further customize the Theia AI experience by configuring <em>Task Contexts</em>, <em>Prompt Fragments</em>, <em>Slash Commands</em>, <em>Custom Agents</em> and <em>Agent Skills</em>.</p>

<ul>
  <li>Task Context<br />
<a href="https://theia-ide.org/docs/user_ai/#task-context">Task Context</a> is a concept to externalize your intent into dedicated files that serve as persistent, editable records of what you want the AI to accomplish. <em>Task Context</em> files are stored in <em>.prompts/task-context/</em> with the default settings. Additional information is available in <a href="https://eclipsesource.com/blogs/2025/07/01/structure-ai-coding-with-task-context/">Structured AI Coding with Task Context: A Better Way to Work with AI Agents</a>.</li>
  <li>Prompt Fragments<br />
<a href="https://theia-ide.org/docs/user_ai/#prompt-fragments">Prompt Fragments</a> are used to define reusable prompts to execute reusable development tasks. <em>Prompt Fragment</em> files are stored in the <em>.prompts</em> folder with the default settings.<br />
They are similar to Visual Studio Code Copilot <a href="https://code.visualstudio.com/docs/copilot/customization/prompt-files">Prompt Files</a>.</li>
  <li>Slash Commands<br />
<a href="https://theia-ide.org/docs/user_ai/#slash-commands">Slash Commands</a> are basically a way to execute a <em>Prompt Fragment</em> in a more intuitive way. Configuring a <em>Prompt Fragment</em> as a <em>Slash Command</em> makes them feel even more like using <a href="https://code.visualstudio.com/docs/copilot/customization/prompt-files">Prompt Files</a> in Visual Studio Code Copilot.</li>
  <li>Custom Agents<br />
<a href="https://theia-ide.org/docs/user_ai/#custom-agents">Custom Agents</a> are used to create a specialist assistant for specific tasks that can be used in the chat for planning or research or to define specialized workflows. <em>Custom Agents</em> are stored in a <em>.prompts/customAgents.yml</em> file with the default settings.<br />
They are similar to Visual Studio Code Copilot <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents">Custom Agents</a>.</li>
  <li>Agent Skills<br />
<a href="https://theia-ide.org/docs/user_ai/#agent-skills-alpha">Agent Skills</a> are used to create reusable capabilities that work across different AI tools. They can include scripts, examples, or other resources alongside instructions. You can define specialized workflows like testing, debugging, or deployment processes by using <em>Agent Skills</em>. Project skills can be stored in the folder <em>.prompts/skills/</em>. Personal skills can be stored in the user home in the folder <em>~/.theia/skills/</em>.</li>
</ul>

<p>By having the task contexts, prompt fragments, custom agents and agent skills in the workspace, it is possible to have a dedicated set of AI enhancements per project that are checked in the repository.</p>

<p>Further information can be found in <a href="https://theia-ide.org/docs/user_ai/">Using the AI Features in the Theia IDE as an End User</a>.</p>

<p>I will not go into details of every possible customization. But as an example and comparison to the previous programmatically registered <em>Custom Agent</em>, we will create a prompt, a custom agent and an agent skill, each able to achieve the same result.</p>

<h3 id="prompt-fragments">Prompt Fragments</h3>

<p><a href="https://theia-ide.org/docs/user_ai/#prompt-fragments">Prompt Fragments</a> are used to define reusable prompts to execute reusable development tasks. <em>Prompt Fragment</em> files are stored in the <em>.prompts</em> folder with the default settings. They are similar to Visual Studio Code Copilot <a href="https://code.visualstudio.com/docs/copilot/customization/prompt-files">Prompt Files</a>.</p>

<p><em>Prompt Fragments</em> can be used by using the special variable <code class="language-plaintext highlighter-rouge">#prompt:promptFragmentID</code>. It does not support any additional arguments.</p>

<ul>
  <li>Start the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Ensure that you have a workspace open otherwise open a folder somewhere (e.g. <em>/home/node/example</em>)</li>
  <li>Create a new folder <em>.prompts</em> in your workspace</li>
  <li>Create a new file <em>harley.prompttemplate</em> in that folder</li>
  <li>
    <p>Add the following content</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Harley Quinn</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">Tell a joke as the girlfriend of the Joker</span>
<span class="nn">---</span>

You are Harley Quinn, the girlfriend of the Joker, who is the arch enemy of Batman.
To attack Batman, you tell a joke that is so funny, it distracts him from his mission.

To keep the distraction going on, write the joke to a file. Use ~jokeFileCreator to write the joke to a file.
If the user does not provide a path, create a new folder "bat-jokes" in the current workspace folder and store the file in that folder.
Choose a filename that is related to the joke itself.
</code></pre></div>    </div>
  </li>
  <li>Test the <em>Prompt Fragment</em> with the following chat message in the <em>AI Chat</em>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal #prompt:harley joke about batgirl
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="slash-commands">Slash Commands</h3>

<p><a href="https://theia-ide.org/docs/user_ai/#slash-commands">Slash Commands</a> are basically a way to execute a <em>Prompt Fragment</em> in a more intuitive way. Configuring a <em>Prompt Fragment</em> as a <em>Slash Command</em> makes them feel even more like using <a href="https://code.visualstudio.com/docs/copilot/customization/prompt-files">Prompt Files</a> in Visual Studio Code Copilot.</p>

<p>By configuring a <em>Prompt Fragment</em> as a <em>Slash Command</em> you can call the prompt via <code class="language-plaintext highlighter-rouge">/commandname</code> and don’t need to use the <code class="language-plaintext highlighter-rouge">#prompt:promptFragmentID</code> variable. Additionally you can pass arguments to a <em>Slash Command</em> that can be used in the prompt via placeholders (<code class="language-plaintext highlighter-rouge">$ARGUMENTS</code> for all arguments in a single string, <code class="language-plaintext highlighter-rouge">$1</code>,<code class="language-plaintext highlighter-rouge">$2</code>,<code class="language-plaintext highlighter-rouge">$3</code> for the individual argument by position). Note that the <code class="language-plaintext highlighter-rouge">commandAgents</code> header is used to limit the usage of the <em>Slash Command</em> to specific agents, while in Visual Studio Code Prompt Files the <code class="language-plaintext highlighter-rouge">agent</code> header is used to specify the agent that is used to execute the prompt.</p>

<ul>
  <li>Open the file <em>.prompts/harley.prompttemplate</em> in your workspace</li>
  <li>Change the content by adding the necessary metadata to the <em>Frontmatter</em> block</li>
  <li>
    <p>Modify the prompt to tell a joke about the person passed as first argument</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Harley Quinn</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">Tell a joke as the girlfriend of the Joker</span>
<span class="na">isCommand</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">commandName</span><span class="pi">:</span> <span class="s">harley</span>
<span class="na">commandDescription</span><span class="pi">:</span> <span class="s">Tell a joke and write it to a file</span>
<span class="na">commandArgumentHint</span><span class="pi">:</span> <span class="s">The person to tell a joke about</span>
<span class="na">commandAgents</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">Universal</span>
<span class="nn">---</span>

You are Harley Quinn, the girlfriend of the Joker, who is the arch enemy of Batman.
To attack Batman, you tell a joke about $1 that is so funny, it distracts him from his mission.

To keep the distraction going on, write the joke to a file. Use ~jokeFileCreator to write the joke to a file.
If the user does not provide a path, create a new folder "bat-jokes" in the current workspace folder and store the file in that folder.
Choose a filename that is related to the joke itself.
</code></pre></div>    </div>
  </li>
  <li>Test the <em>Prompt Fragment</em> by using the <em>Slash Command</em> in the <em>AI Chat</em> like this
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal /harley batgirl
</code></pre></div>    </div>
  </li>
</ul>

<p>As mentioned before, the creation of a <em>Prompt Fragment</em> and configure it as a <em>Slash Command</em> is similar to the creation and usage of a prompt file in Visual Studio Code as explained in <a href="https://vogella.com/blog/vscode_copilot_extension/#further-customizations">Extending Copilot in Visual Studio Code - Further Customizations</a>.</p>

<h3 id="custom-agents">Custom Agents</h3>

<p>As a user you can create a <em>Custom Agent</em> in Theia via a configuration file. Dependent on the available tools, the integration into the user interface is limited compared to <a href="#implement-a-custom-agent">Implementing a Custom Agent</a>, as you have no access to the Theia API of course.</p>

<p><a href="https://theia-ide.org/docs/user_ai/#custom-agents">Custom Agents</a> are used to create a specialist assistant for specific tasks that can be used in the chat for planning or research or to define specialized workflows. <em>Custom Agents</em> are stored in a <em>.prompts/customAgents.yml</em> file with the default settings. They are similar to Visual Studio Code Copilot <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents">Custom Agents</a>.</p>

<ul>
  <li>Start the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Ensure that you have a workspace open otherwise open a folder somewhere (e.g. <em>/home/node/example</em>)</li>
  <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
  <li>Switch to the <em>Agents</em> tab</li>
  <li>
    <p>Click on <strong>Add Custom Agent</strong></p>

    <figure class="">
<img src="/blog/assets/images/theia_add_custom_agent.png" alt="" /></figure>
  </li>
  <li>Select the <em>.prompts</em> folder of the current workspace</li>
  <li>Verify that a <em>.prompts</em> folder is generated in your workspace that contains a <em>customAgents.yml</em> file</li>
  <li>Define a custom agent by defining the following information
    <ul>
      <li><em>id</em>: A unique identifier for the agent.</li>
      <li><em>name</em>: The display name of the agent.</li>
      <li><em>description</em>: A brief explanation of what the agent does.</li>
      <li><em>prompt</em>: The default prompt that the agent will use for processing requests.</li>
      <li><em>defaultLLM</em>: The language model used by default.</li>
      <li><em>showInChat</em>: Whether the agent should be shown in the chat UI. This one is optional and defaults to <code class="language-plaintext highlighter-rouge">true</code>.</li>
    </ul>
  </li>
  <li>
    <p>Replace the content of the <em>customAgents.yml</em> with the following snippet</p>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Blog</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Blog</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of blog posts related to VS Code and Theia written by Dirk Fauth.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>

    <span class="s">You are an agent that helps the developer by providing links to blog posts about VS Code and Theia written by Dirk Fauth.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>
    <span class="s">1. Fetch the publications of Dirk Fauth in the gists of fipro78. Use ~{mcp_github_list_gists} to find the correct gist.</span>
    <span class="s">2. Use ~{mcp_fetch_fetch} to fetch the content of the gists with a max-length parameter of 15000.</span>
    <span class="s">3. Filter the found links for information about VS Code or Theia</span>
    <span class="s">4. Provide a list of links to the blog posts about VS Code or Theia</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>
  </li>
  <li>Test the new <em>Custom Agent</em>
    <ul>
      <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
      <li>Switch to the <em>MCP Servers</em> tab</li>
      <li>Ensure that the <em>fetch</em> MCP server and the <em>github</em> MCP server configured for the <em>gists</em> tools are available and started</li>
      <li>Open the <em>AI Chat</em> and enter the following prompt
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Blog show me the list
</code></pre></div>        </div>
      </li>
      <li>Verify the result in the chat response</li>
    </ul>
  </li>
</ul>

<p>The creation of a <em>Custom Agent</em> in Theia via configuration file is similar to creating a <a href="https://vogella.com/blog/vscode_copilot_extension/#custom-agents"><em>Custom Agent</em> in Visual Studio Code</a>.</p>

<h4 id="agent-to-agent-delegation-function">Agent-to-Agent Delegation (function)</h4>

<p>The tasks that can be performed by AI agents are getting more and more complicated when they are used to solve complex tasks. Such complex tasks typically contain repetitive tasks that can be useful for multiple scenarios. And like in all programming languages, you usually want to modularize and reuse such tasks instead of define them over and over again. In terms of agents this means to create agents for specialized tasks and then tell the more complex agents to call those specialized agents to solve a specific task. In Theia AI this can be done by using the Agent-to-Agent Delegation, either in the prompt via a dedicated delegate function, or programmatically using the <code class="language-plaintext highlighter-rouge">ChatAgentService</code>. The programmatical approach was already shown in <a href="#agent-to-agent-delegation-programmatically">Agent-to-Agent Delegation (programmatically)</a>. The following section describes the Agent-to-Agent Delegation function that can be used in prompts.</p>

<ul>
  <li>Run the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Ensure that you have a workspace open otherwise open a folder somewhere (e.g. <em>/home/node/example</em>)</li>
  <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
  <li>Switch to the <em>Agents</em> tab</li>
  <li>Click on <strong>Add Custom Agent</strong></li>
  <li>Alternatively simply open the <em>.prompts/customAgents.yaml</em> file created before</li>
  <li>
    <p>Add a new <code class="language-plaintext highlighter-rouge">FileWriter</code> agent that uses the Theia built-in <em>Tool Function</em> <code class="language-plaintext highlighter-rouge">writeFileContent</code> to persist content to a file</p>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">FileWriter</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">FileWriter</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This is an agent that is able to persist content into a file in the workspace.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>

    <span class="s">You are an agent that operates in the current workspace of the Theia IDE.</span>
    <span class="s">You are able to persist the provided content into a file by using ~{writeFileContent}.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Add a new step to the <code class="language-plaintext highlighter-rouge">Blog</code> agent that delegates to the new <code class="language-plaintext highlighter-rouge">FileWriter</code> agent to persist the result by using the <code class="language-plaintext highlighter-rouge">delegateToAgent</code> <em>Tool Function</em></p>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Blog</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Blog</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of blog posts related to VS Code and Theia written by Dirk Fauth.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>

    <span class="s">You are an agent that helps the developer by providing links to blog posts about VS Code and Theia written by Dirk Fauth.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>
    <span class="s">1. Fetch the publications of Dirk Fauth in the gists of fipro78. Use ~{mcp_github_list_gists} to find the correct gist.</span>
    <span class="s">2. Use ~{mcp_fetch_fetch} to fetch the content of the gists with a max-length parameter of 15000.</span>
    <span class="s">3. Filter the found links for information about VS Code or Theia</span>
    <span class="s">4. Provide a list of links to the blog posts about VS Code or Theia</span>
    <span class="s">5. Persist the result by delegating to `FileWriter` via ~{delegateToAgent} and write to the links folder in a file named fauth.html</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>
  </li>
  <li>Open the <em>AI Chat</em> and enter the following prompt
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Blog show me the list
</code></pre></div>    </div>
  </li>
  <li>
    <p>Verify that at the end the new <code class="language-plaintext highlighter-rouge">FileWriter</code> agent is called to persist the result and created the file <em>links/fauth.html</em></p>

    <figure class="">
<img src="/blog/assets/images/theia_custom_agent_delegate.png" alt="" /></figure>
  </li>
</ul>

<p>The <code class="language-plaintext highlighter-rouge">delegateToAgent</code> function is a very nice and powerful way to delegate tasks from one agent to another by using a prompt to create multi-agent-workflows.</p>

<h3 id="agent-skills">Agent Skills</h3>

<p><a href="https://theia-ide.org/docs/user_ai/#agent-skills-alpha">Agent Skills</a> are used to create reusable capabilities that work across different AI tools. They can include scripts, examples, or other resources alongside instructions. You can define specialized workflows like testing, debugging, or deployment processes by using <em>Agent Skills</em>. Project skills can be stored in the folder <em>.prompts/skills/</em>. Personal skills can be stored in the user home in the folder <em>~/.theia/skills/</em>.</p>

<p><em>Agent Skills</em> can be used directly as a <em>Slash Command</em> in the chat, where the <code class="language-plaintext highlighter-rouge">name</code> of the skill is used as command.</p>

<p><em><strong>Note:</strong></em><br />
The support for <em>Agent Skills</em> was <a href="https://eclipsesource.com/blogs/2026/02/12/eclipse-theia-1-68-release-news-and-noteworthy/">introduced with Theia 1.68.0</a>. It is still in alpha, which means it is target to changes and does not yet work reliably as you can see for example via <a href="https://github.com/eclipse-theia/theia/issues/17217">this ticket</a>.</p>

<ul>
  <li>Start the Theia browser application</li>
  <li>Open the Theia browser application on http://localhost:3000</li>
  <li>Ensure that you have a workspace open otherwise open a folder somewhere (e.g. <em>/home/node/example</em>)</li>
  <li>Create a new folder <em>skills</em> in the folder <em>.prompts</em> in your workspace</li>
  <li>Create a new folder <em>blog-link-extraction</em> in the previously created <em>.prompts/skills</em> folder</li>
  <li>Create the <em>SKILL.md</em> file in that folder
    <ul>
      <li>
        <p>Add the following content to the <em>SKILL.md</em> file</p>

        <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">blog-link-extraction</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">This skill provides a collection of links about VSCode or Theia from blog posts written by Dirk Fauth. Use this when asked for resources about VSCode or Theia, or when asked to find blog posts by Dirk Fauth.</span>
<span class="nn">---</span>

<span class="gh"># Blog Link Extraction Skill</span>

This skill is designed to extract links from blog posts written by Dirk Fauth about VSCode or Theia. It first collects relevant blog posts from Dirk Fauth's gists and then extracts and filters the links contained within those blog posts to provide a structured list of resources related to VSCode or Theia.

<span class="gu">## Process Overview</span>
<span class="p">
1.</span> <span class="gs">**Define the Extraction Goal**</span>: Identify the specific information to be extracted (e.g., links to blog posts about VSCode or Theia).
<span class="p">2.</span> <span class="gs">**Blog Collection**</span>: Fetch a list of relevant blog posts from a specified data source (e.g., GitHub gists).
<span class="p">3.</span> <span class="gs">**Link Extraction**</span>: For each blog post, extract outbound links and filter them based on relevance to the topic. Perform this step without user interaction to provide a complete result set.
<span class="p">4.</span> <span class="gs">**Output**</span>: Provide a structured list grouped by source blog post, with deterministic ordering.

<span class="gu">## Execution Rules</span>
<span class="p">
1.</span> Run this workflow end-to-end without asking the user for intermediate confirmations.
<span class="p">2.</span> Preferred tools are ~{mcp_github_list_gists} and ~{mcp_fetch_fetch}. If those exact names are unavailable, use equivalent tools that provide the same capability.
<span class="p">3.</span> On fetch failures, retry once. If the second attempt fails, continue with remaining items and report the skipped URL in the final output.
<span class="p">4.</span> If fetched content appears truncated, fetch additional chunks (for example via start index or pagination) until no additional content is returned.

<span class="gu">## Blog Collection</span>
<span class="p">
1.</span> List gists for user <span class="sb">`fipro78`</span>.
<span class="p">2.</span> Select gist files whose filename contains <span class="sb">`publications`</span> (case-insensitive).
<span class="p">3.</span> If multiple matches exist, choose files from the most recently updated gist first.
<span class="p">4.</span> Fetch the selected gist content with max length <span class="sb">`25000`</span>; if needed, fetch additional chunks until complete.
<span class="p">5.</span> Extract candidate blog post URLs.
<span class="p">6.</span> Keep only blog posts relevant to the requested topic (default topic: VSCode or Theia).
<span class="p">7.</span> De-duplicate URLs and produce the final blog post list.

<span class="gu">## Link Extraction</span>
<span class="p">
1.</span> Fetch each blog post with max length <span class="sb">`25000`</span>; if needed, continue fetching additional chunks until complete.
<span class="p">2.</span> Extract outbound links from the blog post.
<span class="p">3.</span> Exclude non-http(s) links and non-content protocols (<span class="sb">`mailto:`</span>, <span class="sb">`javascript:`</span>, <span class="sb">`tel:`</span>).
<span class="p">4.</span> Filter links for relevance using topic keywords from surrounding context. For VSCode/Theia, use keywords such as: <span class="sb">`vscode`</span>, <span class="sb">`visual studio code`</span>, <span class="sb">`theia`</span>, <span class="sb">`eclipse theia`</span>, <span class="sb">`extension`</span>, <span class="sb">`webview`</span>, <span class="sb">`copilot`</span>.
<span class="p">5.</span> De-duplicate links per blog post.
<span class="p">6.</span> Determine display name for each link:
<span class="p">
-</span> Use anchor text when available.
<span class="p">-</span> Otherwise use the URL.
<span class="p">
7.</span> Sort links alphabetically by display name within each blog post.

<span class="gu">## Example Workflow</span>
<span class="p">
1.</span> A user asks for resources about VSCode or Theia.
<span class="p">2.</span> The blog collection process fetches the relevant blog posts from Dirk Fauth's gists.
<span class="p">3.</span> For each relevant blog post, the link extraction process reads the post, extracts outbound links, filters for relevance, and removes duplicates without user interaction.
<span class="p">4.</span> The final output is grouped by blog post, with links presented using anchor text when available, and sorted alphabetically by link name within each blog post.

<span class="gu">## Result</span>

The final output is an aggregated collection grouped by blog post. Blog posts are ordered alphabetically by title (or URL if no title is available). Links inside each blog post are ordered alphabetically by link display name.

<span class="gu">### Example Result</span>
<span class="p">
-</span> <span class="p">[</span><span class="nv">Blog Post 1</span><span class="p">](</span><span class="sx">http://example.com/blog1</span><span class="p">)</span>:
<span class="p">  -</span> <span class="p">[</span><span class="nv">Link 1</span><span class="p">](</span><span class="sx">http://example.com/link1</span><span class="p">)</span> - Anchor Text 1
<span class="p">  -</span> <span class="p">[</span><span class="nv">Link 2</span><span class="p">](</span><span class="sx">http://example.com/link2</span><span class="p">)</span> - Anchor Text 2
<span class="p">-</span> <span class="p">[</span><span class="nv">Blog Post 2</span><span class="p">](</span><span class="sx">http://example.com/blog2</span><span class="p">)</span>:
<span class="p">  -</span> <span class="p">[</span><span class="nv">Link 3</span><span class="p">](</span><span class="sx">http://example.com/link3</span><span class="p">)</span> - Anchor Text 3
<span class="p">  -</span> <span class="p">[</span><span class="nv">Link 4</span><span class="p">](</span><span class="sx">http://example.com/link4</span><span class="p">)</span> - Anchor Text 4

<span class="gu">## Guidelines</span>
<span class="p">
-</span> Ensure that the links are relevant to the topic of VSCode or Theia.
<span class="p">-</span> Avoid including duplicate links or links that are not relevant to the topic.
<span class="p">-</span> Provide clear and concise output that is easy to understand and navigate.
<span class="p">-</span> Use the anchor text as the name of the link when available, and use the URL as the name of the link when anchor text is not available.
<span class="p">-</span> Order links alphabetically by name within each blog post section.
<span class="p">-</span> Ensure that the output is structured in a way that clearly indicates which links are associated with which blog posts.
<span class="p">-</span> Include a short <span class="sb">`Skipped URLs`</span> section when any blog post could not be fetched after retry.
</code></pre></div>        </div>
      </li>
      <li>
        <p>Use the <em>Agent Skill</em> by using it as a slash command to call it directly</p>
        <ul>
          <li>Enter a prompt similar to the following to the chat: <code class="language-plaintext highlighter-rouge">@Universal /blog-link-extraction links about theia</code><br />
You will notice that it loads the skill, but it does not execute it directly.</li>
          <li>Enter something like <code class="language-plaintext highlighter-rouge">process</code> or <code class="language-plaintext highlighter-rouge">execute</code> to the chat to start the skill processing<br />
You will get a message that the required tools are not available. This is because there is currently an issue in the loading and processing of <em>Agent Skills</em>.</li>
          <li>Open the <em>Generic Capabilities</em> panel
            <ul>
              <li>Select the necessary MCP tools (<code class="language-plaintext highlighter-rouge">fetch/fetch</code> and <code class="language-plaintext highlighter-rouge">github/list_gists</code>)</li>
              <li>Click on <em>Save</em></li>
            </ul>

            <p><em><strong>Note:</strong></em><br />
This will configure the <em>Generic Capabilities</em> for the <code class="language-plaintext highlighter-rouge">@Universal</code> agent. This affects the usage of the agent in general. So you probably want to reset this later, to have the agent work as expected again. For this open the <em>settings.json</em> and remove the configuration for <code class="language-plaintext highlighter-rouge">ai-features.agentSettings/Universal</code>.</p>
          </li>
          <li>Enter something like <code class="language-plaintext highlighter-rouge">process</code> or <code class="language-plaintext highlighter-rouge">execute</code> again to the chat to start the skill processing. After that the processing starts as described in the skill.</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p><em><strong>Note:</strong></em><br />
In Theia 1.70.0 the automatic discovery of skills is not working. Also the automatic execution and the tool calls are not working. As the <em>Agent Skill</em> support is currently in alpha state, this will hopefully fixed soon.</p>

<p>Further information about <em>Agent Skills</em>:</p>

<ul>
  <li><a href="https://agentskills.io/home">agentskills.io</a></li>
  <li><a href="https://code.visualstudio.com/docs/copilot/customization/agent-skills">Use Agent Skills in VS Code</a></li>
  <li><a href="https://theia-ide.org/docs/user_ai/#agent-skills-alpha">Using the AI Features in the Theia IDE as an End User - Agent Skills</a></li>
  <li><a href="https://github.com/heilcheng/awesome-agent-skills">awesome-agent-skills</a></li>
</ul>

<h2 id="use-visual-studio-code-copilot-extension-in-theia">Use Visual Studio Code Copilot Extension in Theia</h2>

<p>If you read my previous articles, you might wonder if you could implement a Visual Studio Code Copilot Extension and use it in Theia, as Theia is in general compatible with Visual Studio Code Extensions. If you did not read my previous articles and you are interested in that topic, have a look at <a href="https://vogella.com/blog/theia_getting_started/#theia---visual-studio-code-extension">Getting Started with Eclipse Theia - Theia - Visual Studio Code Extension</a>.</p>

<p>As described above, there is some kind of a mapping between the AI contributions you can provide in a Visual Studio Code Copilot Extension and a Theia AI Extension.
At the time writing this article, the <code class="language-plaintext highlighter-rouge">@theia/plugin-ext</code> extension only supports the mapping of programmatically registered MCP servers via <code class="language-plaintext highlighter-rouge">McpServerDefinitionProvider</code> to Theia <code class="language-plaintext highlighter-rouge">MCPServerDescription</code>s.
The <em>Language Model Tools</em> and the <em>Chat Participants</em> are not supported and therefore will simply not be available in a Theia application.</p>

<h2 id="further-information">Further Information</h2>

<p>There are several blog posts, tutorials and videos available related to Theia AI. The following list contains some links, but is of course not complete and probably also not up-to-date at the time you are reading this blog post.</p>

<ul>
  <li><a href="https://blogs.eclipse.org/post/thomas-froment/why-extending-github-copilot-vs-code-may-not-be-best-fit-your-ai-native">Why Extending GitHub Copilot in VS Code May Not Be the Best Fit for Your AI-Native Development Tool</a></li>
  <li><a href="https://theia-ide.org/docs/">Theia Documentation</a>
    <ul>
      <li><a href="https://theia-ide.org/docs/user_ai/">Using the AI Features in the Theia IDE as an End User</a></li>
      <li><a href="https://theia-ide.org/docs/theia_coder/">Theia Coder: AI-Powered Development in the Theia IDE</a></li>
      <li><a href="https://theia-ide.org/docs/theia_ai/">Building Custom AI assistants and AI support with Theia AI</a></li>
    </ul>
  </li>
  <li><a href="https://eclipsesource.com/blogs/">EclipseSource Blog</a>
    <ul>
      <li><a href="https://eclipsesource.com/blogs/2025/08/05/agent-to-agent-delegation-in-theia-ai/">QA on Autopilot – How AI Agents Collaborate in Theia AI</a></li>
      <li><a href="https://eclipsesource.com/blogs/2026/01/08/slash-commands-theia-ai/">Slash Commands: Automating AI Workflows in Theia AI</a></li>
    </ul>
  </li>
  <li><a href="https://www.youtube.com/@EclipseSourceGmbH">EclipseSource on YouTube</a>
    <ul>
      <li><a href="https://www.youtube.com/watch?v=1XcsPPedIXA">AI Coding with the new Agent Mode of Theia Coder</a></li>
      <li><a href="https://www.youtube.com/watch?v=Wy9epGszWz0">AI Coding Isn’t a Group Chat. Use Task Contexts</a></li>
      <li><a href="https://www.youtube.com/watch?v=FSxw3VGw8T4">QA on Autopilot – How AI Agents Collaborate in Theia AI</a></li>
      <li><a href="https://www.youtube.com/watch?v=Rou4eiIPrK4">It’s Released: Your Native Claude Code IDE Integration in Theia</a></li>
    </ul>
  </li>
  <li><a href="https://www.youtube.com/playlist?list=PLy7t4z5SYNaQyTt3QT9nddDLIuEiUKPoX">TheiaCon 2025 on YouTube</a></li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>In this comprehensive tutorial, we explored the extensive AI capabilities available in Eclipse Theia, focusing on how developers can implement and extend AI features in their Theia applications. We covered three main approaches to enhance Theia’s AI functionality:</p>

<p><strong>Tool Functions</strong> provide deep integration with Theia’s APIs, allowing you to create domain-specific capabilities that can interact directly with the workspace, file system, and other IDE features. While Theia already includes several built-in tools, custom Tool Functions enable specialized functionality tailored to your specific development needs.</p>

<p><strong>MCP (Model Context Protocol) Servers</strong> offer a standardized way to connect AI applications with external tools and data sources. We demonstrated both manual configuration via <em>settings.json</em> and programmatic registration through Theia extensions, covering local servers (stdio transport), remote servers, and authentication scenarios using various methods including PATs and OAuth.</p>

<p><strong>Custom Agents</strong> create specialized AI assistants with domain-specific expertise that can be integrated throughout the Theia IDE. We explored how to implement agents programmatically with advanced features like prompt variants, agent-specific variables, and agent-to-agent delegation. Custom Agents excel when you need specialized workflows, deep Theia integration, or the ability to distribute AI capabilities as part of your Theia-based application.</p>

<p>We also explored configuration-based approaches like defining custom agents via YAML files, which enable users and teams to create specialized workflows without coding, and the powerful concept of agent-to-agent delegation for building sophisticated multi-agent workflows.</p>

<p>The key takeaway is that Theia AI provides a comprehensive and flexible framework for AI integration that goes beyond simple chat interfaces. Whether you’re building an AI-native IDE, adding intelligent assistants for specialized domains, or creating complex multi-agent workflows, Theia AI offers the building blocks needed to create sophisticated AI experiences.</p>

<p>Compared to Visual Studio Code’s Copilot extension model, Theia AI provides more flexibility in how AI features can be integrated throughout the IDE, native support for MCP servers, and extensive customization options through both programmatic APIs and configuration files. While VS Code Copilot extensions can be partially used in Theia (currently limited to MCP server definitions), native Theia AI extensions offer deeper integration and more capabilities.</p>

<p>The complete source code and examples from this tutorial are available in my <a href="https://github.com/fipro78/vscode_theia_cookbook">GitHub repository</a>, providing a practical foundation for building your own AI-powered Theia applications.</p>

<p>Whether you’re looking to enhance developer productivity with domain-specific AI tools, create specialized development workflows, or build an entirely AI-native development environment, this tutorial provides the essential building blocks to get started with Theia AI.</p>]]></content><author><name>Dirk Fauth</name></author><category term="fipro" /><category term="vscode" /><category term="typescript" /><category term="theia" /><category term="ai" /><summary type="html"><![CDATA[Eclipse Theia comes with quite extensive AI capabilities. This includes the usage of AI from a users perspective as well as from a developers perspective to implement AI features in Theia. This tutorial focuses on implementing AI features in Theia. But as you can not describe how to implement something without knowing how to use it in the end, there are also some Theia AI usage guides in here. For a more detailed description on how to use Theia AI as an end-user, have a look at Using the AI Features in the Theia IDE as an End User.]]></summary></entry><entry><title type="html">Extending Copilot in Visual Studio Code</title><link href="https://vogella.com/blog/vscode_copilot_extension/" rel="alternate" type="text/html" title="Extending Copilot in Visual Studio Code" /><published>2025-10-22T00:00:00+00:00</published><updated>2025-10-22T00:00:00+00:00</updated><id>https://vogella.com/blog/vscode_copilot_extension</id><content type="html" xml:base="https://vogella.com/blog/vscode_copilot_extension/"><![CDATA[<p>In this tutorial I will explain how to extend Visual Studio Code with a customized AI experience. By creating a Visual Studio Code Copilot Extension, we will contribute tools, MCP servers and chat participants, to provide AI features with domain expert know-how.</p>

<p>Obviously we will use and extend Copilot to achieve this. There are also other Visual Studio Code extensions to integrate AI in Visual Studio Code, but Copilot is the most prominent integration and there are several tutorials available I will refer to. This way it is easier to get started with those kind of extensions.</p>

<p>The article <a href="https://code.visualstudio.com/api/extension-guides/ai/ai-extensibility-overview">AI extensibility in VS Code</a> gives an overview of the AI extensibility options in Visual Studio Code. In this tutorial we will</p>

<ul>
  <li>create a custom <em>Language Model Tool</em></li>
  <li>configure and contribute <em>MCP Server</em></li>
  <li>create a custom <em>Chat Participant</em></li>
</ul>

<p>In first place this tutorial is about extending GitHub Copilot in Visual Studio code and not a tutorial about using it. If you are interested in that, have a look at <a href="https://code.visualstudio.com/docs/copilot/getting-started">Get started with GitHub Copilot in VS Code</a>.</p>

<h2 id="prerequisites">Prerequisites</h2>

<p>To follow this tutorial you need the following tools and services:</p>

<ul>
  <li><a href="https://code.visualstudio.com/">Visual Studio Code</a> &gt;= 1.113.0</li>
  <li><a href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot">Copilot Extension</a></li>
  <li>Copilot Account (e.g. <a href="https://github.com/settings/copilot">GitHub Copilot Free</a>)</li>
</ul>

<h2 id="project-setup">Project Setup</h2>

<p>As this tutorial is part of my <a href="https://github.com/fipro78/vscode_theia_cookbook">Visual Studio Code Extension - Theia - Cookbook</a>, I will use the Dev Container created with the <a href="https://vogella.com/blog/vscode-extension-webview-getting-started/">Getting Started with Visual Studio Code Extension Development</a>.</p>

<p><em><strong>Note:</strong></em><br />
If you are familiar with setting up a new Visual Studio Code Extension project or you don’t want to use the Cookbook sources and the Dev Container setup defined there as a starting point, you can create the project setup yourself, skip the following section and directly move on to <a href="#language-model-tool">Language Model Tool</a>.</p>

<ul>
  <li>Clone the <a href="https://github.com/fipro78/vscode_theia_cookbook/tree/theia_getting_started">Visual Studio Code Extension - Theia - Cookbook GitHub Repository</a> from the <em>theia_getting_started</em> branch. You can also use the <em>getting_started</em> branch if you are not interested in the Eclipse Theia related sources in the repository.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone -b theia_getting_started https://github.com/fipro78/vscode_theia_cookbook.git
</code></pre></div>    </div>
  </li>
  <li>Switch to the new folder and open Visual Studio Code
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd vscode_theia_cookbook
code .
</code></pre></div>    </div>
  </li>
  <li>When asked, select <em>Reopen in Container</em> to build and open the project in the Dev Container</li>
</ul>

<h3 id="create-the-visual-studio-code-extension-project">Create the Visual Studio Code Extension project</h3>

<p>Create a new Visual Studio Code Extension project:</p>

<ul>
  <li>
    <p>Open a <strong>Terminal</strong> and execute the following command</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yo code
</code></pre></div>    </div>
  </li>
  <li>
    <p>Answer the questions of the wizard for example like shown below:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? copilot-extension
# ? What's the identifier of your extension? copilot-extension
# ? What's the description of your extension? LEAVE BLANK
# ? Initialize a git repository? No
# ? Which bundler to use? unbundled
# ? Which package manager to use? npm

# ? Do you want to open the new folder with Visual Studio Code? Skip
</code></pre></div>    </div>
  </li>
</ul>

<p>A new subfolder <em>copilot-extension</em> will be created that contains the sources of the Visual Studio Code Extension.</p>

<p>The cookbook repository is setup as a mono-repo, therefore the Visual Studio Code Extension is kept in a subfolder. The following modifications are needed to include the newly created extension project to the setup:</p>

<ul>
  <li>
    <p>Edit the <em>.vscode/tasks.json</em></p>

    <ul>
      <li>Add a task for watching the new copilot extension</li>
    </ul>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Copilot Extension Watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shell"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc-watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"presentation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"reveal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"never"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/copilot-extension"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>

    <ul>
      <li>Update the default build task <em>Watch Extensions</em> and add the new <em>Copilot Extension Watch</em> to the <code class="language-plaintext highlighter-rouge">dependsOn</code> configuration</li>
    </ul>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Watch Extensions"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"isDefault"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"dependsOn"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"VS Code Extension Watch"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"Angular Extension Watch"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"React Extension Watch"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"Copilot Extension Watch"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Edit the <em>.vscode/launch.json</em> and add the new project folder to the <code class="language-plaintext highlighter-rouge">extensionDevelopmentPath</code> and the <code class="language-plaintext highlighter-rouge">outFiles</code></p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Run Extension"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"extensionHost"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/vscode-extension"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/angular-extension"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/react-extension"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/copilot-extension"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"outFiles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"${workspaceFolder}/vscode-extension/out/**/*.js"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"${workspaceFolder}/angular-extension/dist/**/*.js"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"${workspaceFolder}/react-extension/dist/**/*.js"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"${workspaceFolder}/copilot-extension/dist/**/*.js"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"preLaunchTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${defaultBuildTask}"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"postDebugTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Terminate Tasks"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>

    <p>You can also add a new run configuration that only starts the new extension if you want to focus on the new extension:</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Run Copilot Extension"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"extensionHost"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/copilot-extension"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"outFiles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"${workspaceFolder}/copilot-extension/out/**/*.js"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"preLaunchTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Copilot Extension Watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"postDebugTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Terminate Tasks"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<p><em><strong>Note:</strong></em><br />
  If you are not starting from a plain project setup and not using the <a href="https://github.com/fipro78/vscode_theia_cookbook">Cookbook GitHub Repository</a>, the <code class="language-plaintext highlighter-rouge">postDebugTask</code> will fail as it does not exist. In that case add it to the <em>tasks.json</em> as explained in <a href="https://vogella.com/blog/multiple-webviews-single-extension/#bonus-automatic-termination-of-watch-tasks">Automatic Termination of Watch Tasks</a>.</p>

<ul>
  <li>
    <p>Delete the <em>copilot-extension/.vscode</em> folder</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm -rf copilot-extension/.vscode
</code></pre></div>    </div>
  </li>
</ul>

<p>To verify that the setup works, open the file <em>copilot-extension/src/extension.ts</em> and press F5 to start a new Visual Studio Code instance with the extension, open the <strong>Command Palette</strong> (CTRL + SHIFT + P) and search for <em>Hello</em> to run the command.</p>

<p>If you only want to start the new <em>copilot-extension</em> in the Extension Host, switch first to the <em>Run and Debug</em> view (CTRL + SHIFT + D) and select <em>Run Copilot Extension</em> in the dropdown.</p>

<h2 id="language-model-tool">Language Model Tool</h2>

<p>Adding a <strong>Language Model Tool</strong> enables you to extend the functionality of a large language model (LLM) in the chat with domain-specific capabilities.
This is also possible via specialized <strong>MCP Server</strong> which will be described later. The main difference between a <em>Language Model Tool</em> and a <em>MCP Server</em> is that a <em>Language Model Tool</em> can deeply integrate with Visual Studio Code by using the VS Code APIs, while <em>MCP Server</em> provide access to external tools that don’t need to access to the VS Code API.</p>

<p>Further details can be found in <a href="https://code.visualstudio.com/api/extension-guides/ai/tools">Language Model Tool API</a>.</p>

<p>In this section we will create a <strong>Language Model Tool</strong> that creates a file in the current workspace that contains a joke as content.</p>

<p><em><strong>Note:</strong></em><br />
Actually Visual Studio Code already contains built-in language model tools that are able to interact with the workspace. So the following implementation is not necessary to achieve the result. It is intended as an example how the implementation of a <strong>Language Model Tool</strong> could look like.</p>

<ul>
  <li>
    <p>Open the file <em>copilot-extension/package.json</em></p>

    <ul>
      <li>
        <p>Replace the <code class="language-plaintext highlighter-rouge">contributes</code> section with the following snippet:</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"contributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"languageModelTools"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"chat-tools-joke"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Joke File Creator"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"toolReferenceName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"jokeFileCreator"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"canBeReferencedInPrompt"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"icon"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$(files)"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"userDescription"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Create a file that contains a joke."</span><span class="p">,</span><span class="w">
      </span><span class="nl">"modelDescription"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Create a file at the given path that contains a joke."</span><span class="p">,</span><span class="w">
      </span><span class="nl">"inputSchema"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"object"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"path"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The name of the folder in the workspace where the joke file should be created."</span><span class="w">
          </span><span class="p">},</span><span class="w">
          </span><span class="nl">"filename"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The name of the jokefile that should be created."</span><span class="w">
          </span><span class="p">},</span><span class="w">
          </span><span class="nl">"joke"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The joke content to be written in the joke file."</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Create a new file <em>copilot-extension/src/joke-file-creator.ts</em></p>

    <ul>
      <li>
        <p>Import the VS Code API</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Define an interface for the tool parameters that matches the inputSchema in the package.json</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">IJokeFileParameters</span> <span class="p">{</span>
  <span class="nl">path</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">filename</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">joke</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Create a class that implements <code class="language-plaintext highlighter-rouge">vscode.LanguageModelTool</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nc">JokeFileCreatorTool</span>
  <span class="k">implements</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">LanguageModelTool</span><span class="o">&lt;</span><span class="nx">IJokeFileParameters</span><span class="o">&gt;</span> <span class="p">{}</span>
</code></pre></div>        </div>

        <ul>
          <li>Add the following <code class="language-plaintext highlighter-rouge">prepareInvocation()</code> method to provide tool configuration messages.</li>
        </ul>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">prepareInvocation</span><span class="p">?(</span>
    <span class="nx">options</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">LanguageModelToolInvocationPrepareOptions</span><span class="o">&lt;</span><span class="nx">IJokeFileParameters</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="nx">token</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CancellationToken</span>
<span class="p">):</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ProviderResult</span><span class="o">&lt;</span><span class="nx">vscode</span><span class="p">.</span><span class="nx">PreparedToolInvocation</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">confirmationMessages</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Create a joke file</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">message</span><span class="p">:</span> <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">MarkdownString</span><span class="p">(</span>
        <span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">input</span><span class="p">.</span><span class="nx">path</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&amp;&amp;</span> <span class="nx">options</span><span class="p">.</span><span class="nx">input</span><span class="p">.</span><span class="nx">path</span> <span class="o">!==</span> <span class="dl">""</span><span class="p">)</span>
        <span class="p">?</span> <span class="s2">`Create a joke file in </span><span class="p">${</span><span class="nx">options</span><span class="p">.</span><span class="nx">input</span><span class="p">.</span><span class="nx">path</span><span class="p">}</span><span class="s2">?`</span>
        <span class="p">:</span> <span class="dl">"</span><span class="s2">Create a joke file in the workspace root?</span><span class="dl">"</span>
    <span class="p">),</span>
    <span class="p">};</span>

    <span class="k">return</span> <span class="p">{</span>
    <span class="na">invocationMessage</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Create a joke file</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">confirmationMessages</span><span class="p">,</span>
    <span class="p">};</span>
<span class="p">}</span>
</code></pre></div>        </div>

        <p><em><strong>Note:</strong></em><br />
  If you return <code class="language-plaintext highlighter-rouge">undefined</code>, the generic confirmation message will be shown.</p>

        <ul>
          <li>Add the following <code class="language-plaintext highlighter-rouge">invoke()</code> method which is called when the language model tool is invoked while processing a chat prompt.</li>
        </ul>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="nf">invoke</span><span class="p">(</span>
    <span class="nx">options</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">LanguageModelToolInvocationOptions</span><span class="o">&lt;</span><span class="nx">IJokeFileParameters</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="nx">token</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CancellationToken</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">vscode</span><span class="p">.</span><span class="nx">LanguageModelToolResult</span> <span class="o">|</span> <span class="kc">null</span> <span class="o">|</span> <span class="kc">undefined</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">input</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">createJokeFile</span><span class="p">(</span>
        <span class="nx">params</span><span class="p">.</span><span class="nx">path</span><span class="p">,</span>
        <span class="nx">params</span><span class="p">.</span><span class="nx">filename</span><span class="p">,</span>
        <span class="nx">params</span><span class="p">.</span><span class="nx">joke</span>
    <span class="p">);</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">LanguageModelToolResult</span><span class="p">([</span>
            <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">LanguageModelTextPart</span><span class="p">(</span><span class="nx">result</span><span class="p">),</span>
    <span class="p">]);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">LanguageModelToolResult</span><span class="p">([</span>
            <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">LanguageModelTextPart</span><span class="p">(</span><span class="s2">`Joke file creation failed`</span><span class="p">),</span>
    <span class="p">]);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">async</span> <span class="nf">createJokeFile</span><span class="p">(</span>
    <span class="nx">path</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">filename</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">jokeContent</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">workspaceFolders</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nx">workspaceFolders</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">workspaceFolders</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nb">window</span><span class="p">.</span><span class="nf">showErrorMessage</span><span class="p">(</span><span class="dl">"</span><span class="s2">No workspace folder open.</span><span class="dl">"</span><span class="p">);</span>
        <span class="k">return</span> <span class="dl">""</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">let</span> <span class="nx">folder</span> <span class="o">=</span> <span class="nx">workspaceFolders</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
    <span class="kd">let</span> <span class="nx">pathUri</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="nx">folder</span><span class="p">.</span><span class="nx">uri</span><span class="p">,</span> <span class="nx">path</span><span class="p">);</span>

    <span class="k">try</span> <span class="p">{</span>
        <span class="k">await</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nx">fs</span><span class="p">.</span><span class="nf">stat</span><span class="p">(</span><span class="nx">pathUri</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nx">fs</span><span class="p">.</span><span class="nf">createDirectory</span><span class="p">(</span><span class="nx">pathUri</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kd">const</span> <span class="nx">fileUri</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="nx">pathUri</span><span class="p">,</span> <span class="nx">filename</span><span class="p">);</span>

    <span class="k">try</span> <span class="p">{</span>
        <span class="k">await</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nx">fs</span><span class="p">.</span><span class="nf">writeFile</span><span class="p">(</span>
            <span class="nx">fileUri</span><span class="p">,</span>
            <span class="nx">Buffer</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">jokeContent</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">)</span>
        <span class="p">);</span>
        <span class="k">return</span> <span class="s2">`Joke file "</span><span class="p">${</span><span class="nx">fileUri</span><span class="p">}</span><span class="s2">" created!`</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`Failed to create joke file: </span><span class="p">${</span><span class="nx">error</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Change <em>copilot-extension/src/extension.ts</em></p>

        <ul>
          <li>Replace the existing example code with the following snippet</li>
        </ul>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The module 'vscode' contains the VS Code extensibility API</span>
<span class="c1">// Import the module and reference it with the alias vscode in your code below</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JokeFileCreatorTool</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./joke-file-creator</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// This method is called when your extension is activated</span>
<span class="c1">// Your extension is activated the very first time the command is executed</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">activate</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// This line of code will only be executed once when your extension is activated</span>
  <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">Congratulations, your extension "copilot-extension" is now active!</span><span class="dl">'</span>
  <span class="p">);</span>

  <span class="c1">// Register our custom joke creator language model tool</span>
  <span class="c1">// Use the name property of the tool configured in the package.json</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">lm</span><span class="p">.</span><span class="nf">registerTool</span><span class="p">(</span><span class="dl">"</span><span class="s2">chat-tools-joke</span><span class="dl">"</span><span class="p">,</span> <span class="k">new</span> <span class="nc">JokeFileCreatorTool</span><span class="p">())</span>
  <span class="p">);</span>
<span class="p">}</span>

<span class="c1">// This method is called when your extension is deactivated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">deactivate</span><span class="p">()</span> <span class="p">{}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>If everything is correctly in place, you can verify the <strong>Language Model Tool</strong> like this:</p>

<ul>
  <li>You need an open workspace to make the <strong>Language Model Tool</strong> work. To be able to open a workspace in the Extension Host create a folder <em>example</em> in the home directory of the <em>node</em> user in the Dev Container
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir ~/example
</code></pre></div>    </div>
  </li>
  <li>Press F5 to start a new Visual Studio Code instance with the extension</li>
  <li>Open a folder via <em>File -&gt; Open Folder…</em> and select the created <em>example</em> folder</li>
  <li>Open the <em>Copilot Chat Editor</em> if it is not open yet</li>
  <li>
    <p>Enter the following input to the Copilot Chat to execute the contributed <em>Language Model Tool</em></p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#jokeFileCreator create a file that contains a joke in the folder test
</code></pre></div>    </div>

    <figure class="">
<img src="/blog/assets/images/copilot_language_model_tool.png" alt="" /></figure>
  </li>
  <li>
    <p>In the chat you will see that a prompt comes up that asks you whether to <em>Allow</em> the execution of the tool or if you want to <em>Skip</em> it. Click on <em>Allow</em> or even select for example <em>Allow in this Workspace</em> from the dropdown so you don’t need to allow the execution of the tool in the future.</p>

    <figure class="">
<img src="/blog/assets/images/copilot_language_model_tool_allow.png" alt="" /></figure>
  </li>
  <li>
    <p>Check the <em>Input</em> and <em>Output</em> of the tool in the chat and ensure that the file was created in the correct place with the desired content.</p>

    <figure class="">
<img src="/blog/assets/images/copilot_language_model_tool_done.png" alt="" /></figure>
  </li>
</ul>

<h2 id="mcp-server">MCP Server</h2>

<p>MCP, or Model Context Protocol, is an open protocol to standardize how AI applications connect with external tools and data sources. MCP servers can offer resources, prompts and tools that can be used by a client.</p>

<p>In this section we will cover how to add MCP servers to Visual Studio Code and the usage of tools.</p>

<p>There are basically two types of MCP servers:</p>

<ul>
  <li>Local MCP servers (stdio transport)<br />
A local MCP server runs on the same machine as the MCP client and is used to access local resources like files or run local scripts. Local servers are essential for tasks that require accessing local files or data that is not available remotely.</li>
  <li>Remote MCP servers (streamable HTTP or server-sent events)</li>
</ul>

<p>There are several ways to add MCP servers to Visual Studio Code:</p>

<ul>
  <li>Directly install them e.g. via a website like <a href="https://code.visualstudio.com/mcp">MCP Servers for agent mode</a><br />
This will create a <em>mcp.json</em> file in <em>C:\Users\&lt;NT_USER&gt;\AppData\Roaming\Code\User</em> with the configuration of the MCP server</li>
  <li>Via <em>mcp.json</em> server configuration file, e.g. in the workspace in <em>.vscode/mcp.json</em></li>
  <li>Programmatically via a Visual Studio Code Extension</li>
</ul>

<p>It is also possible to configure MCP servers in a Dev Container via <em>devcontainer.json</em>. This is described in <a href="https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_dev-container-support">Dev Container support</a>.</p>

<p>In the following section I will describe how to manually configure MCP servers via <em>mcp.json</em> and programmatically via Visual Studio Code extension.</p>

<p>We will install</p>

<ul>
  <li>the <a href="https://modelcontextprotocol.io/quickstart/user#installing-the-filesystem-server">Filesystem MCP Server</a> as a local MCP Server</li>
  <li>the <a href="https://github.com/modelcontextprotocol/servers/tree/main/src/fetch">Fetch MCP Server</a> as a remote MCP Server</li>
  <li>the <a href="https://github.com/github/github-mcp-server">GitHub MCP Server</a> as a remote MCP Server that requires an authorization</li>
</ul>

<h3 id="add-mcp-server-via-mcpjson">Add MCP server via mcp.json</h3>

<p>In the following section we will add MCP servers via <em>mcp.json</em> file. This can be done in the Visual Studio Code instance that you use to follow this tutorial (not the Extension Host for running the created extension).</p>

<h4 id="local-mcp-server">Local MCP Server</h4>

<ul>
  <li>Create a new file <em>.vscode/mcp.json</em></li>
  <li>
    <p>Add the following content to configure the filesystem server as local MCP server.</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"servers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"filesystem"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stdio"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"-y"</span><span class="p">,</span><span class="w"> </span><span class="s2">"@modelcontextprotocol/server-filesystem"</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"inputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<p><em><strong>Note:</strong></em><br />
The <a href="https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem">Filesystem MCP Server</a> supports <a href="https://modelcontextprotocol.info/docs/concepts/roots/">Roots</a> and Visual Studio Code sets the roots to the workspace directory. You can set additional allowed directories via <code class="language-plaintext highlighter-rouge">args</code>, but they will be replaced by the roots provided by Visual Studio Code. You can therefore skip setting allowed directories as they will be replaced automatically with the workspace folder.</p>

<ul>
  <li>In the editor you will see actions provided as <em>CodeLens</em> that let you interact with the server. Click on <em>Start</em> to start the filesystem MCP server.
    <figure class="">
<img src="/blog/assets/images/mcp_codelens.png" alt="" /></figure>

    <p>This will start the server and discover the capabilities and tools provided by the server. These tools can then be used in the Copilot Chat in agent mode.</p>
  </li>
  <li>
    <p>Test if the configuration works</p>

    <ul>
      <li>Ensure to have the <strong>Agent</strong> mode enabled.</li>
      <li>
        <p>Enter the following to the Copilot Chat</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>list allowed directories
</code></pre></div>        </div>
      </li>
      <li>When asked if the tool <code class="language-plaintext highlighter-rouge">list_allowed_directories</code> should be executed, select <em>Allow</em></li>
      <li>You should now see that the <code class="language-plaintext highlighter-rouge">list_allowed_directories</code> tool from the <em>filesystem (MCP Server)</em> is executed to solve your request.</li>
    </ul>
  </li>
</ul>

<h4 id="remote-mcp-server">Remote MCP Server</h4>

<ul>
  <li>Add the following content to the <em>mcp.json</em> to configure the <code class="language-plaintext highlighter-rouge">fetch</code> server as remote MCP server.
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"fetch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://remote.mcpservers.org/fetch/mcp"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
    <p><em><strong>Note:</strong></em><br />
Visual Studio Code already provides <code class="language-plaintext highlighter-rouge">fetch</code> as a built-in tool. So this is actually not needed for usage, but an example to show a simple remote MCP server configuration. For testing that the added remote <code class="language-plaintext highlighter-rouge">fetch</code> MCP server works</p>
    <ul>
      <li>Click on <em>Configure Tools…</em>
        <figure class="">
<img src="/blog/assets/images/copilot_tools_configuration.png" alt="" /></figure>
      </li>
      <li>Disable the built-in <code class="language-plaintext highlighter-rouge">fetch</code> tool</li>
      <li>Enable the added MCP server <code class="language-plaintext highlighter-rouge">fetch</code> in the configuration</li>
      <li>Click on <em>OK</em> to apply the changes
        <figure class="">
<img src="/blog/assets/images/copilot_tools_configuration_fetch.png" alt="" /></figure>
      </li>
    </ul>
  </li>
  <li>Start the <em>fetch</em> MCP server via Codelens</li>
  <li>Test if the configuration works by entering the following to the Copilot Chat
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fetch the content from https://eclipse.dev/nattable
</code></pre></div>    </div>
  </li>
  <li>When asked if the tool <em><code class="language-plaintext highlighter-rouge">fetch</code> - fetch (MCP Server)</em> should be executed, select <em>Allow</em></li>
  <li>You should now see that the <code class="language-plaintext highlighter-rouge">fetch</code> tool from the <em>fetch (MCP Server)</em> is executed to solve your request.</li>
</ul>

<h4 id="remote-mcp-server-with-authorization">Remote MCP Server with authorization</h4>

<p>Most of the remote MCP servers require an authorization in order to work. Although of course possible, you typically don’t want to write the token in plain text into the <em>mcp.json</em> file, as you don’t want to share your personal token with other project members. Instead you can deal with the authorization token the following ways:</p>

<ul>
  <li>use input variables</li>
  <li>use environment variables</li>
</ul>

<p>To demonstrate this we use the <a href="https://github.com/github/github-mcp-server">GitHub MCP Server</a> with a PAT (Personal Access Token). This is also described in <a href="https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp/use-the-github-mcp-server">Using the GitHub MCP Server</a>.</p>

<ul>
  <li>Create a Personal Access Token as described in <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic">Creating a personal access token (classic)</a></li>
  <li>Use <strong>repo</strong> as scope</li>
</ul>

<p>You can use the GitHub MCP server locally via Docker. To make this work in a Dev Container you need the <a href="https://github.com/devcontainers/features/tree/main/src/docker-in-docker">docker-in-docker</a> feature added to the <em>devcontainer.json</em>.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"features"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"ghcr.io/devcontainers/features/docker-in-docker:2"</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The local GitHub MCP server can then be configured in the <em>mcp.json</em> file like this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"docker"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"run"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"-i"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"--rm"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"-e"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"GITHUB_PERSONAL_ACCESS_TOKEN"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"ghcr.io/github/github-mcp-server"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"env"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"GITHUB_PERSONAL_ACCESS_TOKEN"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${input:github_token}"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Note the <code class="language-plaintext highlighter-rouge">${input:github_token}</code> in the <code class="language-plaintext highlighter-rouge">env</code> section. The input parameter needs to be configured in the following way in the <code class="language-plaintext highlighter-rouge">inputs</code> section of the <em>mcp.json</em> file.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"inputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"promptString"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"github_token"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GitHub Personal Access Token"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"password"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<p>Inputs will be prompted on first server start and then stored securely by Visual Studio Code.</p>

<p>Further information about input variables can be found here:</p>

<ul>
  <li><a href="https://code.visualstudio.com/docs/reference/variables-reference#_input-variables">Input Variables</a></li>
  <li><a href="https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_configuration-format">Configuration format</a></li>
</ul>

<p>Instead of using the GitHub MCP server locally, you can directly use the GitHub hosted server as explained in <a href="https://github.blog/ai-and-ml/generative-ai/a-practical-guide-on-how-to-use-the-github-mcp-server/">A practical guide on how to use the GitHub MCP server</a></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"Authorization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Bearer ${input:github_token}"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Instead of using an <code class="language-plaintext highlighter-rouge">input</code>, you can also use an environment variable as explained in <a href="https://code.visualstudio.com/docs/reference/variables-reference#_environment-variables">Environment variables</a></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"Authorization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Bearer ${env:GITHUB_TOKEN}"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>By using <code class="language-plaintext highlighter-rouge">${env:GITHUB_TOKEN}</code> instead of the <code class="language-plaintext highlighter-rouge">input</code> variable the environment variable with the name <code class="language-plaintext highlighter-rouge">GITHUB_TOKEN</code> will be used for setting the authorization token.</p>

<p>Once the environment variable is set in your system and available, update the <em>.devcontainer/devcontainer.json</em> file and add the following configuration as described in <a href="https://code.visualstudio.com/remote/advancedcontainers/environment-variables">Visual Studio Code - Environment variables</a></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"remoteEnv"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"GITHUB_TOKEN"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${localEnv:GITHUB_TOKEN}"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Instead of using the Bearer token for authorization, GitHub recommends the usage of OAuth for the GitHub MCP server. In that case simply drop the <code class="language-plaintext highlighter-rouge">headers</code> section and you will be asked for the authentication on starting the server.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>As the GitHub MCP Server provides a huge set of tools, the tools are categorized and not all tools are enabled by default. If you for example want to use the GitHub Gist related tools, you need to enable that. This can either be done by using the explicit MCP URL</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github_gists"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/x/gists"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>or by setting the optional header <code class="language-plaintext highlighter-rouge">X-MCP-Toolsets</code></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"X-MCP-Toolsets"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gists"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Further details about this are available in <a href="https://github.com/github/github-mcp-server/blob/main/docs/remote-server.md">Remote GitHub MCP Server</a>.</p>

<ul>
  <li>Test if the GitHub MCP Server configuration works
    <ul>
      <li>Start the server</li>
      <li>Ensure to have the <em>Agent</em> mode activated in the chat</li>
      <li>Enter the following in the chat
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fetch the publications written by Dirk Fauth. Inspect the gists of fipro78 and provide the links to blog posts about VS Code and Eclipse Theia in the chat that are extracted from a related gists file
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p><em><strong>Note:</strong></em><br />
The default <em>.gitignore</em> or at least the <em>.gitignore</em> in the cookbook repository by default ignores the whole <em>.vscode</em> folder and excludes the default settings JSON files.
With this configuration the <em>mcp.json</em> file will be ignored. This makes sense to avoid that by accident configurations with hard coded authorization tokens are added to the repository.
If you are sure that no private data is contained in the <em>mcp.json</em> file, e.g. because you use the OAuth mechanism for the GitHub MCP server, you can exclude the <em>mcp.json</em> file
from the ignore list by adding the following line to the <em>.gitignore</em> in the project root:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>!.vscode/mcp.json
</code></pre></div></div>

<h3 id="add-mcp-server-programmatically-via-vs-code-extension">Add MCP server programmatically via VS Code Extension</h3>

<p>You can also register MCP server programmatically via a Visual Studio Code extension. This way you can for example bundle AI extensions like chat participants or the direct usage of the Language Model API with the registration of MCP servers that are needed for the provided functionality.</p>

<ul>
  <li>
    <p>Open the file <em>copilot-extension/package.json</em></p>

    <ul>
      <li>
        <p>Extend the <code class="language-plaintext highlighter-rouge">contributes</code> section with the following <code class="language-plaintext highlighter-rouge">mcpServerDefinitionProviders</code></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"contributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"mcpServerDefinitionProviders"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"custom-mcp"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Custom MCP Server Provider"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p>To ensure that the MCP servers are registered automatically, you need to configure the <code class="language-plaintext highlighter-rouge">activationEvents</code> accordingly, e.g. <code class="language-plaintext highlighter-rouge">onStartupFinished</code>.
Otherwise the extension is not activated and the MCP servers are not registered. Alternatively you can of course use another activation event based on some other constraint.</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">activationEvents</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span>
  <span class="dl">"</span><span class="s2">onStartupFinished</span><span class="dl">"</span>
<span class="p">],</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Open the file <em>copilot-extension/src/extension.ts</em></p>

    <ul>
      <li>
        <p>Register the <code class="language-plaintext highlighter-rouge">McpServerDefinitionProvider</code> in the <code class="language-plaintext highlighter-rouge">activate()</code> method.</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">didChangeEmitter</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">EventEmitter</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span><span class="p">();</span>

<span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span>
  <span class="nx">vscode</span><span class="p">.</span><span class="nx">lm</span><span class="p">.</span><span class="nf">registerMcpServerDefinitionProvider</span><span class="p">(</span><span class="dl">"</span><span class="s2">custom-mcp</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">onDidChangeMcpServerDefinitions</span><span class="p">:</span> <span class="nx">didChangeEmitter</span><span class="p">.</span><span class="nx">event</span><span class="p">,</span>
    <span class="na">provideMcpServerDefinitions</span><span class="p">:</span> <span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kd">let</span> <span class="na">servers</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">McpServerDefinition</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span>

      <span class="c1">// add the servers</span>

      <span class="k">return</span> <span class="nx">servers</span><span class="p">;</span>
    <span class="p">},</span>
  <span class="p">})</span>
<span class="p">);</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Add a local MCP server by using <a href="https://code.visualstudio.com/api/references/vscode-api#McpStdioServerDefinition"><code class="language-plaintext highlighter-rouge">vscode.McpStdioServerDefinition</code></a> (right after the <code class="language-plaintext highlighter-rouge">// add the servers</code> comment)</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">servers</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span>
  <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">McpStdioServerDefinition</span><span class="p">(</span><span class="dl">"</span><span class="s2">filesystem</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">npx</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span>
    <span class="dl">"</span><span class="s2">-y</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">@modelcontextprotocol/server-filesystem</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">/home/node/example</span><span class="dl">"</span><span class="p">,</span>
  <span class="p">])</span>
<span class="p">);</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Add a remote MCP server by using <a href="https://code.visualstudio.com/api/references/vscode-api#McpHttpServerDefinition"><code class="language-plaintext highlighter-rouge">vscode.McpHttpServerDefinition</code></a></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">servers</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span>
  <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">McpHttpServerDefinition</span><span class="p">(</span>
    <span class="dl">"</span><span class="s2">fetch</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://remote.mcpservers.org/fetch/mcp</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">)</span>
<span class="p">);</span>
</code></pre></div>        </div>
      </li>
    </ul>

    <p>There are some flaws related to programmatically registered MCP servers:</p>
    <ul>
      <li>There is no programmatical way to autostart a MCP server.</li>
      <li>They do not show up in the <em>MCP Servers</em> section of the <em>Extensions</em> view.</li>
      <li>They don’t get <a href="https://modelcontextprotocol.info/docs/concepts/roots/">roots</a> set.</li>
    </ul>

    <p>The resources and tools provided by a MCP server are loaded and cached on the first start. But because of the above reasons, a programmatically registered MCP servers will not be started automatically and needs a user interaction for the first start. The only way to manage programmatically registered MCP servers manually is to use the command <strong>MCP: List Servers</strong> command from the Command Palette (F1) to view the list of configured MCP servers.</p>
    <ul>
      <li><em>Command Palette (F1) -&gt; <strong>MCP: List Servers</strong> -&gt; select the server to start -&gt; <strong>Start Server</strong></em></li>
    </ul>

    <p>There is also a configuration <strong>Chat &gt; MCP: Autostart</strong> available that lets a user define an autostart behavior for MCP servers.</p>
    <ul>
      <li><em>Command Palette (F1) -&gt; <strong>Preferences: Open User Settings</strong> -&gt; search for autostart -&gt; <strong>Chat &gt; MCP: Autostart</strong></em> - set the value to <code class="language-plaintext highlighter-rouge">newAndOutdated</code> (<code class="language-plaintext highlighter-rouge">"chat.mcp.autostart": "newAndOutdated"</code> in the settings JSON).</li>
    </ul>

    <p>With this setting even programmatically registered MCP servers can be autostarted. But as for example the <em>roots</em> are not set by Visual Studio Code to programmatically registered MCP servers, the <code class="language-plaintext highlighter-rouge">filesystem</code> MCP server can be configured to access an directory outside the workspace via the <em>allowed directories</em> parameter, in the above configuration <em>/home/node/example</em>.</p>
  </li>
  <li>
    <p>Run the extension by pressing F5</p>
    <ul>
      <li>Check that the MCP servers are started, either manually via command palette or have the <code class="language-plaintext highlighter-rouge">chat.mcp.autostart</code> setting enabled.</li>
      <li>Test if the programmatically registered MCP server works, e.g. by entering the following into the chat
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>show me the directory tree of the allowed directories
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>If you need to pass an authorization token via header like the PAT for the GitHub MCP server, you can for example read an environment variable or hard-code it and pass the <code class="language-plaintext highlighter-rouge">Authorization</code> header</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">GITHUB_TOKEN</span><span class="p">;</span>
<span class="nx">servers</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span>
  <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">McpHttpServerDefinition</span><span class="p">(</span>
    <span class="dl">"</span><span class="s2">github</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://api.githubcopilot.com/mcp/</span><span class="dl">"</span><span class="p">),</span>
    <span class="p">{</span>
      <span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
    <span class="p">}</span>
  <span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>

<p>Alternatively it is possible to get the token interactively by implementing <code class="language-plaintext highlighter-rouge">resolveMcpServerDefinition()</code> of the <code class="language-plaintext highlighter-rouge">McpServerDefinitionProvider</code></p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nf">activate</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">didChangeEmitter</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">EventEmitter</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span><span class="p">();</span>

  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">lm</span><span class="p">.</span><span class="nf">registerMcpServerDefinitionProvider</span><span class="p">(</span><span class="dl">"</span><span class="s2">etas-mcp</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">onDidChangeMcpServerDefinitions</span><span class="p">:</span> <span class="nx">didChangeEmitter</span><span class="p">.</span><span class="nx">event</span><span class="p">,</span>
      <span class="na">provideMcpServerDefinitions</span><span class="p">:</span> <span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="na">servers</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">McpServerDefinition</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span>

        <span class="c1">// add the servers</span>
        <span class="nx">servers</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span>
          <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">McpStdioServerDefinition</span><span class="p">(</span><span class="dl">"</span><span class="s2">filesystem</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">npx</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span>
            <span class="dl">"</span><span class="s2">-y</span><span class="dl">"</span><span class="p">,</span>
            <span class="dl">"</span><span class="s2">@modelcontextprotocol/server-filesystem</span><span class="dl">"</span><span class="p">,</span>
            <span class="dl">"</span><span class="s2">/home/node/example</span><span class="dl">"</span><span class="p">,</span>
          <span class="p">])</span>
        <span class="p">);</span>

        <span class="nx">servers</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span>
          <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">McpHttpServerDefinition</span><span class="p">(</span>
            <span class="dl">"</span><span class="s2">fetch</span><span class="dl">"</span><span class="p">,</span>
            <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://remote.mcpservers.org/fetch/mcp</span><span class="dl">"</span><span class="p">)</span>
          <span class="p">)</span>
        <span class="p">);</span>

        <span class="nx">servers</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span>
          <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">McpHttpServerDefinition</span><span class="p">(</span>
            <span class="dl">"</span><span class="s2">github</span><span class="dl">"</span><span class="p">,</span>
            <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://api.githubcopilot.com/mcp/</span><span class="dl">"</span><span class="p">)</span>
          <span class="p">)</span>
        <span class="p">);</span>

        <span class="k">return</span> <span class="nx">servers</span><span class="p">;</span>
      <span class="p">},</span>
      <span class="na">resolveMcpServerDefinition</span><span class="p">:</span> <span class="k">async </span><span class="p">(</span>
        <span class="na">server</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">McpServerDefinition</span>
      <span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">server</span><span class="p">.</span><span class="nx">label</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">github</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
          <span class="c1">// First check if token is available in environment variable</span>
          <span class="kd">let</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">GITHUB_TOKEN</span><span class="p">;</span>

          <span class="c1">// If no environment variable, ask for token from user</span>
          <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">token</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">vscode</span><span class="p">.</span><span class="nb">window</span><span class="p">.</span><span class="nf">showInputBox</span><span class="p">({</span>
              <span class="na">prompt</span><span class="p">:</span> <span class="s2">`Enter the authorization token for </span><span class="p">${</span><span class="nx">server</span><span class="p">.</span><span class="nx">label</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
              <span class="na">password</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
              <span class="na">placeHolder</span><span class="p">:</span> <span class="s2">`Enter your authorization token for </span><span class="p">${</span><span class="nx">server</span><span class="p">.</span><span class="nx">label</span><span class="p">}</span><span class="s2"> ...`</span><span class="p">,</span>
            <span class="p">});</span>

            <span class="k">if </span><span class="p">(</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
              <span class="c1">// If user provided a token, save it to environment variable for future use</span>
              <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">GITHUB_TOKEN</span> <span class="o">=</span> <span class="nx">token</span><span class="p">;</span>
              <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`GITHUB_TOKEN saved to environment for future use`</span><span class="p">);</span>
            <span class="p">}</span>
          <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span>
              <span class="s2">`Using GITHUB_TOKEN from environment for </span><span class="p">${</span><span class="nx">server</span><span class="p">.</span><span class="nx">label</span><span class="p">}</span><span class="s2">`</span>
            <span class="p">);</span>
          <span class="p">}</span>

          <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">vscode</span><span class="p">.</span><span class="nb">window</span><span class="p">.</span><span class="nf">showErrorMessage</span><span class="p">(</span>
              <span class="s2">`Authorization token is required for </span><span class="p">${</span><span class="nx">server</span><span class="p">.</span><span class="nx">label</span><span class="p">}</span><span class="s2">`</span>
            <span class="p">);</span>
            <span class="k">return</span> <span class="kc">undefined</span><span class="p">;</span> <span class="c1">// Don't start the server without a token</span>
          <span class="p">}</span>

          <span class="c1">// Update the server headers with the new token</span>
          <span class="kd">const</span> <span class="nx">updatedHeaders</span> <span class="o">=</span> <span class="p">{</span>
            <span class="p">...(</span><span class="nx">server</span> <span class="kd">as </span><span class="kr">any</span><span class="p">).</span><span class="nx">headers</span><span class="p">,</span>
            <span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
          <span class="p">};</span>

          <span class="p">(</span><span class="nx">server</span> <span class="kd">as </span><span class="kr">any</span><span class="p">).</span><span class="nx">headers</span> <span class="o">=</span> <span class="nx">updatedHeaders</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// Return undefined to indicate that the server should not be started or throw an error</span>
        <span class="c1">// If there is a pending tool call, the editor will cancel it and return an error message</span>
        <span class="c1">// to the language model.</span>
        <span class="k">return</span> <span class="nx">server</span><span class="p">;</span>
      <span class="p">},</span>
    <span class="p">})</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Additional information about registering a MCP server programmatically is also provided here:</p>

<ul>
  <li><a href="https://code.visualstudio.com/api/extension-guides/ai/mcp#register-an-mcp-server-in-your-extension">Register an MCP server in your extension</a></li>
  <li><a href="https://github.com/microsoft/vscode-extension-samples/tree/main/mcp-extension-sample">MCP Extension sample</a></li>
</ul>

<p><strong><em>Note:</em></strong><br />
If you plan to use a Visual Studio Code extension that contributes <code class="language-plaintext highlighter-rouge">mcpServerDefinitionProviders</code> in a Theia application, you can not use a type check for <code class="language-plaintext highlighter-rouge">vscode</code> types,
e.g. the statement <code class="language-plaintext highlighter-rouge">if (server instanceof vscode.McpHttpServerDefinition)</code> will not work in Theia, because Theia does not use the <code class="language-plaintext highlighter-rouge">vscode</code> types.
In such a case you need a different check like <code class="language-plaintext highlighter-rouge">if ((server as any).uri)</code> which checks for the property instead of the type, or the name check as shown in the above example.</p>

<p>Further information about MCP in Visual Studio Code:</p>

<ul>
  <li><a href="https://code.visualstudio.com/docs/copilot/chat/mcp-servers">Use MCP servers in VS Code</a></li>
  <li><a href="https://code.visualstudio.com/api/extension-guides/ai/mcp">MCP developer guide</a></li>
</ul>

<h2 id="chat-participant">Chat Participant</h2>

<p>With the implementation of a <strong>Chat Participant</strong> you can create an assistant to extend the chat with domain specific experts knowledge.</p>

<p><strong>Chat Participants</strong></p>

<ul>
  <li>are specialized AI assistants</li>
  <li>work within VS Code’s chat system and can interact with VS Code APIs</li>
  <li>can be distributed and deployed with an extension via marketplace, no need for additional installation mechanisms</li>
  <li>can control the end-to-end user chat prompt and response</li>
</ul>

<p>In the following section we will create a chat participant that is able to use one of the tools we created before.</p>

<p>We use the <a href="https://github.com/microsoft/vscode-chat-extension-utils"><code class="language-plaintext highlighter-rouge">chat-extension-utils</code></a> to implement tool calling in the chat participant. You can also implement the tool calling yourself to have more control over the tool calling process.
Check the <a href="https://github.com/microsoft/vscode-extension-samples/blob/main/chat-sample/src/toolParticipant.ts">tool calling by using prompt-tsx example</a> for an example on how to implement the tool calling yourself.</p>

<ul>
  <li>
    <p>Install <code class="language-plaintext highlighter-rouge">@vscode/chat-extension-utils</code> to use tools in the chat participant</p>

    <ul>
      <li>Open a <strong>Terminal</strong></li>
      <li>Switch to the folder <em>copilot-extension</em></li>
      <li>Execute the following command
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @vscode/chat-extension-utils
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Open the file <em>copilot-extension/package.json</em></p>

    <ul>
      <li>
        <p>Extend the <code class="language-plaintext highlighter-rouge">contributes</code> section with the following <code class="language-plaintext highlighter-rouge">chatParticipants</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">contributes</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
  <span class="dl">"</span><span class="s2">chatParticipants</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span>
    <span class="p">{</span>
      <span class="dl">"</span><span class="s2">id</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">joker-sample.joker-participant</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">joker</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">fullName</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">The Joker</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">description</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Let me tell you a joke!</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">isSticky</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span>
    <span class="p">}</span>
  <span class="p">]</span>
<span class="p">},</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Open the file <em>copilot-extension/src/extension.ts</em></p>

    <ul>
      <li>
        <p>Define the prompt to use</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">JOKER_PROMPT</span> <span class="o">=</span> <span class="s2">`You are the Joker, the arch enemy of Batman.
To attack Batman, you tell a joke that is so funny, it distracts him from his mission.
To keep the distraction going on, write the joke to a file.
If the user does not provide a path, create a new folder "bat-jokes" in the current workspace folder and store the file in that folder.`</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Import chat-extension-utils</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">chatUtils</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@vscode/chat-extension-utils</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Extend the <code class="language-plaintext highlighter-rouge">activate()</code> method</p>

    <ul>
      <li>
        <p>Define the <code class="language-plaintext highlighter-rouge">vscode.ChatRequestHandler</code> request handler</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">handler</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ChatRequestHandler</span> <span class="o">=</span> <span class="k">async </span><span class="p">(</span>
  <span class="nx">request</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ChatRequest</span><span class="p">,</span>
  <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ChatContext</span><span class="p">,</span>
  <span class="nx">stream</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ChatResponseStream</span><span class="p">,</span>
  <span class="nx">token</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CancellationToken</span>
<span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// Chat request handler implementation goes here</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">tools</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">lm</span><span class="p">.</span><span class="nx">tools</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span>
      <span class="p">(</span><span class="nx">tool</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">tool</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">chat-tools-joke</span><span class="dl">"</span>
    <span class="p">);</span>

    <span class="kd">const</span> <span class="nx">libResult</span> <span class="o">=</span> <span class="nx">chatUtils</span><span class="p">.</span><span class="nf">sendChatParticipantRequest</span><span class="p">(</span>
      <span class="nx">request</span><span class="p">,</span>
      <span class="nx">context</span><span class="p">,</span>
      <span class="p">{</span>
        <span class="na">prompt</span><span class="p">:</span> <span class="nx">JOKER_PROMPT</span><span class="p">,</span>
        <span class="na">responseStreamOptions</span><span class="p">:</span> <span class="p">{</span>
          <span class="nx">stream</span><span class="p">,</span>
          <span class="na">references</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
          <span class="na">responseText</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="nx">tools</span><span class="p">,</span>
      <span class="p">},</span>
      <span class="nx">token</span>
    <span class="p">);</span>

    <span class="k">return</span> <span class="k">await</span> <span class="nx">libResult</span><span class="p">.</span><span class="nx">result</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Error in chat request handler: </span><span class="p">${</span><span class="nx">err</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Register the chat participant</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">chatLibParticipant</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">chat</span><span class="p">.</span><span class="nf">createChatParticipant</span><span class="p">(</span>
  <span class="dl">"</span><span class="s2">joker-sample.joker-participant</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">handler</span>
<span class="p">);</span>
<span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">chatLibParticipant</span><span class="p">);</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Test the implementation by starting the extension host via F5.</p>

        <ul>
          <li>
            <p>Open the Copilot Chat and ask the joker chat participant for a joke by selecting him via <em>@joker</em></p>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@joker tell me a joke about batman
</code></pre></div>            </div>
          </li>
          <li>
            <p>Verify the chat response and check the generated file</p>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>Further information about chat participants can be found here</p>

<ul>
  <li><a href="https://code.visualstudio.com/api/extension-guides/ai/chat">Chat Participant API</a></li>
  <li><a href="https://code.visualstudio.com/api/extension-guides/ai/chat-tutorial">Tutorial: Build a code tutorial chat participant with the Chat API</a></li>
  <li><a href="https://github.com/microsoft/vscode-extension-samples/blob/main/chat-sample/src/chatUtilsSample.ts">Chat Utils Sample</a></li>
  <li><a href="https://github.com/microsoft/vscode-extension-samples/blob/main/chat-sample/src/toolParticipant.ts">Tool Calling with prompt-tsx</a></li>
</ul>

<h2 id="further-customizations">Further Customizations</h2>

<p>Users can further customize the Copilot experience by configuring <em>Instructions</em>, <em>Prompt Templates</em>, <em>Custom Agents</em> and <em>Agent Skills</em>.</p>

<ul>
  <li>Instructions<br />
<a href="https://code.visualstudio.com/docs/copilot/customization/custom-instructions">Custom Instructions</a> are used to define common guidelines for specific tasks and can be installed in the user profile or in the workspace <em>.github/instructions</em></li>
  <li>Prompts<br />
<a href="https://code.visualstudio.com/docs/copilot/customization/prompt-files">Prompt Files</a> are used to define reusable prompts to execute reusable development tasks and can be installed in the user profile or in the <em>.github/prompts</em> folder</li>
  <li>Custom Agents<br />
<a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents">Custom Agents</a> are used to create a specialist assistant for specific tasks that can be used in the chat for planning or research or to define specialized workflows. They can be installed in the user profile or in the workspace in the <em>.github/agents</em> folder</li>
  <li>Agent Skills<br />
<a href="https://code.visualstudio.com/docs/copilot/customization/agent-skills">Agent Skills</a> are used to create reusable capabilities that work across different AI tools. They can include scripts, examples, or other resources alongside instructions. You can define specialized workflows like testing, debugging, or deployment processes by using <em>Agent Skills</em>. Project skills can be stored in the folders <em>.github/skills/</em>, <em>.claude/skills/</em> or <em>.agents/skills/</em>. Personal skills can be stored in the user home in the folders <em>~/.copilot/skills/</em>, <em>~/.claude/skills/</em> or <em>~/.agents/skills/</em>.</li>
  <li>Tool Sets<br />
<a href="https://code.visualstudio.com/docs/copilot/chat/chat-tools#_group-tools-with-tool-sets">Tool Sets</a> can be defined in a
<em>.jsonc</em> file that is located in the user profile e.g. <em>C:\Users\&lt;username&gt;\AppData\Roaming\Code\User\prompts</em></li>
</ul>

<p>By having the instructions, prompts, custom agents and agent skills in the <strong>workspace</strong>, it is possible to have dedicated instructions, prompts, custom agents and agent skills per project that are checked in the repository.</p>

<p>Further information can be found in <a href="https://code.visualstudio.com/docs/copilot/customization/overview">Customize AI in Visual Studio Code</a>.</p>

<p>I will not go into details of every possible customization. But as an example and comparison to the previous programmatically registered <em>Chat Participant</em>, we will create a prompt and a custom agent, each able to achieve the same result.</p>

<h3 id="prompt-files">Prompt Files</h3>

<p><a href="https://code.visualstudio.com/docs/copilot/customization/prompt-files">Prompt Files</a> are used to define reusable prompts to execute reusable development tasks and can be installed in the user profile or in the <em>.github/prompts</em> folder.</p>

<p><em>Prompt Files</em> can be used by typing <code class="language-plaintext highlighter-rouge">/</code> followed by the prompt name in the chat input field. You can provide additional information as input variables via <code class="language-plaintext highlighter-rouge">&lt;name&gt;=&lt;value&gt;</code>.</p>

<ul>
  <li>
    <p>Start the Extension Host by pressing F5<br />
This is necessary because the <code class="language-plaintext highlighter-rouge">jokeFileCreator</code> language model tool is contributed by the developed Visual Studio Code Extension. If that tool is not needed or used, you can even try this in the Visual Studio Code instance in which you are developing.</p>
  </li>
  <li>
    <p>Create a new <em>Prompt File</em></p>
    <ul>
      <li>In the Copilot chat window, click the gear icon in the upper right corner (<em>Open Customizations</em>)
        <figure class="">
<img src="/blog/assets/images/copilot_chat_menu.png" alt="" /></figure>
      </li>
      <li>Select <em>Prompts</em> in the <em>Chat Customizations</em> dialog side menu</li>
      <li>Open the dropdown for generating a new prompt and select <em>New Prompt (Workspace)</em>
        <figure class="">
<img src="/blog/assets/images/copilot_customizations_prompts.png" alt="" /></figure>
      </li>
      <li>Enter the name <em>harley</em></li>
    </ul>

    <p>This creates the file <em>.github/prompts/harley.prompt.md</em>, which is directly opened in the <em>Chat Customizations</em> dialog.</p>

    <ul>
      <li>
        <p>Add the following content to the file</p>

        <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">agent</span><span class="pi">:</span> <span class="s">agent</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">undefined_publisher.copilot-extension/jokeFileCreator</span><span class="pi">]</span>
<span class="nn">---</span>

You are Harley Quinn, the girlfriend of the Joker, who is the arch enemy of Batman.
To attack Batman, you tell a joke that is so funny, it distracts him from his mission.

To keep the distraction going on, write the joke to a file.
If the user does not provide a path, create a new folder "bat-jokes" in the current workspace folder and store the file in that folder.
</code></pre></div>        </div>
      </li>
      <li>Execute the prompt by pressing the play button in the editor title area.
        <figure class="">
<img src="/blog/assets/images/copilot_prompt_play.png" alt="" /></figure>
      </li>
      <li>Execute the prompt by using it in the chat via slash command and pass additional information, e.g. <code class="language-plaintext highlighter-rouge">/harley joke about robin</code></li>
    </ul>
  </li>
</ul>

<p>The prompt can be more specific by using input variables and mentioning the tools to be used at the correct position via the <code class="language-plaintext highlighter-rouge">#tool:&lt;toolname&gt;</code> syntax.</p>

<ul>
  <li>Update the file <em>.github/prompts/harley.prompt.md</em>
    <ul>
      <li>Use the variable <em>target</em> to tell a joke about</li>
      <li>
        <p>Use the <em>jokeFileCreator</em> tool explicitly to create the file</p>

        <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">agent</span><span class="pi">:</span> <span class="s">agent</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">undefined_publisher.copilot-extension/jokeFileCreator</span><span class="pi">]</span>
<span class="nn">---</span>

You are Harley Quinn, the girlfriend of the Joker, who is the arch enemy of Batman.
To attack Batman, you tell a joke about ${input:target} that is so funny, it distracts him from his mission.

To keep the distraction going on, write the joke to a file by using #tool:undefined_publisher.copilot-extension/jokeFileCreator
If the user does not provide a path, create a new folder "bat-jokes" in the current workspace folder and store the file in that folder.
</code></pre></div>        </div>
      </li>
      <li>Execute the prompt by using it in the chat via slash command and set the <em>target</em> input variable, e.g. <code class="language-plaintext highlighter-rouge">/harley target=batgirl</code></li>
    </ul>
  </li>
</ul>

<h3 id="custom-agents">Custom Agents</h3>

<ul>
  <li>Create a new <em>Custom Agent</em>
    <ul>
      <li>In the Copilot chat window, click the gear icon in the upper right corner (<em>Open Customizations</em>)
        <figure class="">
<img src="/blog/assets/images/copilot_chat_menu.png" alt="" /></figure>
      </li>
      <li>Select <em>Agents</em> in the <em>Chat Customizations</em> dialog side menu</li>
      <li>Open the dropdown for generating a new agent and select <em>New Agent (Workspace)</em>
        <figure class="">
<img src="/blog/assets/images/copilot_customizations_agents.png" alt="" /></figure>
      </li>
      <li>Select <em>.github</em> as location for the new agent</li>
      <li>Enter the name <em>joker</em></li>
    </ul>

    <p>This creates the file <em>.github/agents/joker.agent.md</em>, which is directly opened in the <em>Chat Customizations</em> dialog.</p>
    <ul>
      <li>
        <p>Add the following content to the file</p>

        <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Creates</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">file</span><span class="nv"> </span><span class="s">with</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">joke</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">distract</span><span class="nv"> </span><span class="s">Batman."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">undefined_publisher.copilot-extension/jokeFileCreator</span><span class="pi">]</span>
<span class="nn">---</span>

You are the Joker, the arch enemy of Batman.
To attack Batman, you tell a joke that is so funny, it distracts him from his mission.
To keep the distraction going on, write the joke to a file.
If the user does not provide a path, create a new folder "bat-jokes" in the current workspace folder and store the file in that folder.
</code></pre></div>        </div>
      </li>
      <li>
        <p>Use the custom agent by selecting it in the agents dropdown in the chat view and enter a prompt, e.g. <code class="language-plaintext highlighter-rouge">joke about alfred</code></p>

        <figure class="">
<img src="/blog/assets/images/copilot_select_custom_agent.png" alt="" /></figure>
      </li>
    </ul>
  </li>
</ul>

<p>As you might notice, the results from the implemented <em>Chat Participant</em> are quite similar to those from the <em>Prompt File</em> or the <em>Custom Agent</em>. For this simple example that uses a <em>Language Model Tool</em> for file creation, there is no significant advantage to creating a <em>Chat Participant</em>, except for the ability to distribute it via a Visual Studio Code Extension. The real benefits of a <em>Chat Participant</em> become apparent when you need to deeply integrate with Visual Studio Code using the extension APIs.</p>

<p>The following table shows a basic comparison of <em>Prompt Files</em>, <em>Custom Agents</em> and <em>Chat Participants</em>:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Prompt Files</th>
      <th>Custom Agents</th>
      <th>Chat Participants</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Format</td>
      <td>.promp.md file</td>
      <td>.agent.md file</td>
      <td>Implementation of <code class="language-plaintext highlighter-rouge">vscode.ChatRequestHandler</code></td>
    </tr>
    <tr>
      <td>Usage</td>
      <td>slash command, e.g. <em>/joker</em></td>
      <td>select in agents dropdown</td>
      <td>select via <code class="language-plaintext highlighter-rouge">@</code> in chat, e.g. <em>@joker</em></td>
    </tr>
    <tr>
      <td>Usage scenarios</td>
      <td>Reusable development tasks</td>
      <td>Specialized workflows with option to delegate task to other agents</td>
      <td>Domain-specific tasks and workflows distributed via extension</td>
    </tr>
  </tbody>
</table>

<h3 id="agent-to-agent-delegation--handoffs">Agent-to-Agent Delegation / Handoffs</h3>

<p><em>Custom Agents</em> can be used to create specialized workflows with multiple agents involved. In this section we will create two new <em>Custom Agents</em> where the first agent is retrieving some information and the second one will write it to a file. This excercise can be done in the development host and does not need to be executed in the debugging instance.</p>

<ul>
  <li>Ensure to have the GitHub MCP server configured in your workspace with gists tools enabled:
    <ul>
      <li>Open the file <em>.vscode/mcp.json</em></li>
      <li>Check that the following entry is available</li>
    </ul>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"X-MCP-Toolsets"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gists"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>Create a new <em>Custom Agent</em> that is able to write content to a file in the workspace by using the <code class="language-plaintext highlighter-rouge">edit/createFile</code> built-in tool of Visual Studio Code.
    <ul>
      <li>In the Copilot chat window, click the gear icon in the upper right corner (<em>Open Customizations</em>)
        <figure class="">
<img src="/blog/assets/images/copilot_chat_menu.png" alt="" /></figure>
      </li>
      <li>Select <em>Agents</em> in the <em>Chat Customizations</em> dialog side menu</li>
      <li>Open the dropdown for generating a new agent and select <em>New Agent (Workspace)</em>
        <figure class="">
<img src="/blog/assets/images/copilot_customizations_agents.png" alt="" /></figure>
      </li>
      <li>Select <em>.github</em> as location for the new agent</li>
      <li>Enter the name <em>filewriter</em></li>
    </ul>

    <p>This creates the file <em>.github/agents/filewriter.agent.md</em>, which is directly opened in the <em>Chat Customizations</em> dialog.</p>
    <ul>
      <li>
        <p>Add the following content to the file</p>

        <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">an</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">that</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">able</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">persist</span><span class="nv"> </span><span class="s">content</span><span class="nv"> </span><span class="s">into</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">file</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">workspace."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">edit/createFile</span><span class="pi">]</span>
<span class="nn">---</span>

You are an agent that operates in the current workspace of Visual Studio Code. You are able to persist the provided content into a file.
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Create a new <em>Custom Agent</em> that is able to retrieve information from a <em>gist</em>.
    <ul>
      <li>Switch back to the <em>Agents</em> overview page in the <em>Chat Customizations</em> dialog (e.g. by using the back arrow if the previous agent editor is still open or selecting the <em>Agents</em> entry in the side menu again)</li>
      <li>Open the dropdown for generating a new agent and select <em>New Agent (Workspace)</em>
        <figure class="">
<img src="/blog/assets/images/copilot_customizations_agents.png" alt="" /></figure>
      </li>
      <li>Select <em>.github</em> as location for the new agent</li>
      <li>Enter the name <em>blog</em></li>
    </ul>

    <p>This creates the file <em>.github/agents/blog.agent.md</em>, which is directly opened in the <em>Chat Customizations</em> dialog.</p>
    <ul>
      <li>Use the GitHub MCP tool <code class="language-plaintext highlighter-rouge">github/list_gists</code> to list the gists (remember to enable the <em>gists</em> toolset)</li>
      <li>Use the built-in <code class="language-plaintext highlighter-rouge">web/fetch</code> tool to fetch the content</li>
      <li>Use <code class="language-plaintext highlighter-rouge">handoffs</code> field in the frontmatter header to configure that the processing should be <em>hand off</em> to the <code class="language-plaintext highlighter-rouge">filewriter</code> agent we created before</li>
      <li>
        <p>Add the following content to the file</p>

        <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">list</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">blog</span><span class="nv"> </span><span class="s">posts</span><span class="nv"> </span><span class="s">related</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">VS</span><span class="nv"> </span><span class="s">Code</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">Theia</span><span class="nv"> </span><span class="s">written</span><span class="nv"> </span><span class="s">by</span><span class="nv"> </span><span class="s">Dirk</span><span class="nv"> </span><span class="s">Fauth."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">web/fetch</span><span class="pi">,</span> <span class="nv">github/list_gists</span><span class="pi">]</span>
<span class="na">handoffs</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">label</span><span class="pi">:</span> <span class="s">Persist Blog Links</span>
    <span class="na">agent</span><span class="pi">:</span> <span class="s">filewriter</span>
    <span class="na">prompt</span><span class="pi">:</span> <span class="s">Persist the given list of links by writing to the links folder in a file named fauth.html</span>
    <span class="na">send</span><span class="pi">:</span> <span class="kc">false</span>
<span class="nn">---</span>

You are an agent that helps the developer by providing links to blog posts about VS Code and Theia written by Dirk Fauth.

To provide the necessary links execute the following steps:
<span class="p">
1.</span> Fetch the publications of Dirk Fauth in the gists of fipro78. Use #tool:github/list_gists to find the correct gist.
<span class="p">2.</span> Use #tool:web/fetch to fetch the content of the gists with a max-length parameter of 15000.
<span class="p">3.</span> Filter the found links for information about VS Code or Theia
<span class="p">4.</span> Provide a list of links to the blog posts about VS Code or Theia
</code></pre></div>        </div>

        <p><em><strong>Note:</strong></em><br />
The tools are mentioned explicitly via the <code class="language-plaintext highlighter-rouge">#tool:</code> syntax to ensure that the correct tools are used without the need to determine which tool to use.</p>
      </li>
    </ul>
  </li>
  <li>
    <p>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">blog</code> by selecting it in the agents dropdown in the chat view and enter the prompt, e.g. <code class="language-plaintext highlighter-rouge">show me the list</code></p>

    <figure class="">
<img src="/blog/assets/images/copilot_custom_agent_blog.png" alt="" /></figure>

    <ul>
      <li>You will be prompted to allow the execution of the <code class="language-plaintext highlighter-rouge">fetch</code> tool to ensure that there is no malicious code fetched</li>
    </ul>

    <figure class="">
<img src="/blog/assets/images/copilot_allow_and_review.png" alt="" /></figure>

    <ul>
      <li>Once the <code class="language-plaintext highlighter-rouge">blog</code> agent is done, you will be asked if you want to proceed with the next step</li>
    </ul>

    <figure class="">
<img src="/blog/assets/images/copilot_handoff_proceed.png" alt="" /></figure>

    <ul>
      <li>If the next step is approved, the chat will contain the <code class="language-plaintext highlighter-rouge">prompt</code> defined in the <code class="language-plaintext highlighter-rouge">handoffs</code> section, and the <code class="language-plaintext highlighter-rouge">filewriter</code> agent will be selected in the agent dropdown</li>
    </ul>

    <figure class="">
<img src="/blog/assets/images/copilot_custom_agent_filewriter.png" alt="" /></figure>

    <ul>
      <li>After sending the pre-filled chat entry, the <code class="language-plaintext highlighter-rouge">filewriter</code> will execute its task and create the folder <em>links</em> with a file <em>fauth.html</em> in it</li>
      <li>You will then be asked whether to <em>Keep</em> or to <em>Undo</em> the file changes. Select <em>Keep</em> and check what the content of the file.</li>
    </ul>

    <figure class="">
<img src="/blog/assets/images/copilot_keep_undo.png" alt="" /></figure>
  </li>
</ul>

<p>By setting the <code class="language-plaintext highlighter-rouge">send</code> field in the <code class="language-plaintext highlighter-rouge">handoffs</code> section to <code class="language-plaintext highlighter-rouge">true</code> you can avoid that you need to send the hand-off prompt manually and instead auto-submit the prompt.
But you still will need to approve the next step.</p>

<h3 id="agent-skills">Agent Skills</h3>

<ul>
  <li>Create a new <em>Agent Skill</em>
    <ul>
      <li>In the Copilot chat window, click the gear icon in the upper right corner (<em>Open Customizations</em>)
        <figure class="">
<img src="/blog/assets/images/copilot_chat_menu.png" alt="" /></figure>
      </li>
      <li>Select <em>Skills</em> in the <em>Chat Customizations</em> dialog side menu</li>
      <li>Open the dropdown for generating a new skill and select <em>New Skill (Workspace)</em>
        <figure class="">
<img src="/blog/assets/images/copilot_customizations_skills.png" alt="" /></figure>
      </li>
      <li>Select <em>.github</em> as location for the new skill</li>
      <li>Enter the name <em>blog-link-extraction</em></li>
    </ul>

    <p>This creates the folder <em>.github/skills/blog-link-extraction</em> that contains a <em>SKILL.md</em> file, which is directly opened in the <em>Chat Customizations</em> dialog.</p>
    <ul>
      <li>
        <p>Add the following content to the <em>SKILL.md</em> file</p>

        <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">blog-link-extraction</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">This skill provides a collection of links about VSCode or Theia from blog posts written by Dirk Fauth. Use this when asked for resources about VSCode or Theia, or when asked to find blog posts by Dirk Fauth.</span>
<span class="nn">---</span>

<span class="gh"># Blog Link Extraction Skill</span>

This skill is designed to extract links from blog posts written by Dirk Fauth about VSCode or Theia. It first collects relevant blog posts from Dirk Fauth's gists and then extracts and filters the links contained within those blog posts to provide a structured list of resources related to VSCode or Theia.

<span class="gu">## Process Overview</span>
<span class="p">
1.</span> <span class="gs">**Define the Extraction Goal**</span>: Identify the specific information to be extracted (e.g., links to blog posts about VSCode or Theia).
<span class="p">2.</span> <span class="gs">**Blog Collection**</span>: Fetch a list of relevant blog posts from a specified data source (e.g., GitHub gists).
<span class="p">3.</span> <span class="gs">**Link Extraction**</span>: For each blog post, extract outbound links and filter them based on relevance to the topic. Perform this step without user interaction to provide a complete result set.
<span class="p">4.</span> <span class="gs">**Output**</span>: Provide a structured list grouped by source blog post, with deterministic ordering.

<span class="gu">## Execution Rules</span>
<span class="p">
1.</span> Run this workflow end-to-end without asking the user for intermediate confirmations.
<span class="p">2.</span> Preferred tools are <span class="sb">`github/list_gists`</span> and <span class="sb">`web/fetch`</span>. If those exact names are unavailable, use equivalent tools that provide the same capability.
<span class="p">3.</span> On fetch failures, retry once. If the second attempt fails, continue with remaining items and report the skipped URL in the final output.
<span class="p">4.</span> If fetched content appears truncated, fetch additional chunks (for example via start index or pagination) until no additional content is returned.

<span class="gu">## Blog Collection</span>
<span class="p">
1.</span> List gists for user <span class="sb">`fipro78`</span>.
<span class="p">2.</span> Select gist files whose filename contains <span class="sb">`publications`</span> (case-insensitive).
<span class="p">3.</span> If multiple matches exist, choose files from the most recently updated gist first.
<span class="p">4.</span> Fetch the selected gist content with max length <span class="sb">`25000`</span>; if needed, fetch additional chunks until complete.
<span class="p">5.</span> Extract candidate blog post URLs.
<span class="p">6.</span> Keep only blog posts relevant to the requested topic (default topic: VSCode or Theia).
<span class="p">7.</span> De-duplicate URLs and produce the final blog post list.

<span class="gu">## Link Extraction</span>
<span class="p">
1.</span> Fetch each blog post with max length <span class="sb">`25000`</span>; if needed, continue fetching additional chunks until complete.
<span class="p">2.</span> Extract outbound links from the blog post.
<span class="p">3.</span> Exclude non-http(s) links and non-content protocols (<span class="sb">`mailto:`</span>, <span class="sb">`javascript:`</span>, <span class="sb">`tel:`</span>).
<span class="p">4.</span> Filter links for relevance using topic keywords from surrounding context. For VSCode/Theia, use keywords such as: <span class="sb">`vscode`</span>, <span class="sb">`visual studio code`</span>, <span class="sb">`theia`</span>, <span class="sb">`eclipse theia`</span>, <span class="sb">`extension`</span>, <span class="sb">`webview`</span>, <span class="sb">`copilot`</span>.
<span class="p">5.</span> De-duplicate links per blog post.
<span class="p">6.</span> Determine display name for each link:
<span class="p">
-</span> Use anchor text when available.
<span class="p">-</span> Otherwise use the URL.
<span class="p">
7.</span> Sort links alphabetically by display name within each blog post.

<span class="gu">## Example Workflow</span>
<span class="p">
1.</span> A user asks for resources about VSCode or Theia.
<span class="p">2.</span> The blog collection process fetches the relevant blog posts from Dirk Fauth's gists.
<span class="p">3.</span> For each relevant blog post, the link extraction process reads the post, extracts outbound links, filters for relevance, and removes duplicates without user interaction.
<span class="p">4.</span> The final output is grouped by blog post, with links presented using anchor text when available, and sorted alphabetically by link name within each blog post.

<span class="gu">## Result</span>

The final output is an aggregated collection grouped by blog post. Blog posts are ordered alphabetically by title (or URL if no title is available). Links inside each blog post are ordered alphabetically by link display name.

<span class="gu">### Example Result</span>
<span class="p">
-</span> <span class="p">[</span><span class="nv">Blog Post 1</span><span class="p">](</span><span class="sx">http://example.com/blog1</span><span class="p">)</span>:
<span class="p">  -</span> <span class="p">[</span><span class="nv">Link 1</span><span class="p">](</span><span class="sx">http://example.com/link1</span><span class="p">)</span> - Anchor Text 1
<span class="p">  -</span> <span class="p">[</span><span class="nv">Link 2</span><span class="p">](</span><span class="sx">http://example.com/link2</span><span class="p">)</span> - Anchor Text 2
<span class="p">-</span> <span class="p">[</span><span class="nv">Blog Post 2</span><span class="p">](</span><span class="sx">http://example.com/blog2</span><span class="p">)</span>:
<span class="p">  -</span> <span class="p">[</span><span class="nv">Link 3</span><span class="p">](</span><span class="sx">http://example.com/link3</span><span class="p">)</span> - Anchor Text 3
<span class="p">  -</span> <span class="p">[</span><span class="nv">Link 4</span><span class="p">](</span><span class="sx">http://example.com/link4</span><span class="p">)</span> - Anchor Text 4

<span class="gu">## Guidelines</span>
<span class="p">
-</span> Ensure that the links are relevant to the topic of VSCode or Theia.
<span class="p">-</span> Avoid including duplicate links or links that are not relevant to the topic.
<span class="p">-</span> Provide clear and concise output that is easy to understand and navigate.
<span class="p">-</span> Use the anchor text as the name of the link when available, and use the URL as the name of the link when anchor text is not available.
<span class="p">-</span> Order links alphabetically by name within each blog post section.
<span class="p">-</span> Ensure that the output is structured in a way that clearly indicates which links are associated with which blog posts.
<span class="p">-</span> Include a short <span class="sb">`Skipped URLs`</span> section when any blog post could not be fetched after retry.
</code></pre></div>        </div>
      </li>
      <li>Use the <em>Agent Skill</em> by selecting the <code class="language-plaintext highlighter-rouge">Agent</code> mode in the agents dropdown in the chat view and either
        <ul>
          <li>enter a prompt, e.g. <code class="language-plaintext highlighter-rouge">find links about vscode</code>, which let Copilot discover and load the instructions by matching the user prompt with the <code class="language-plaintext highlighter-rouge">name</code> and the <code class="language-plaintext highlighter-rouge">description</code> of the skill</li>
          <li>use the slash command to call it directly, e.g. <code class="language-plaintext highlighter-rouge">/blog-link-extraction links about vscode</code></li>
        </ul>
      </li>
      <li>
        <p>Following the execution you will notice that the first step in processing is to read the <em>blog-link-extraction</em> skill file. After that it starts processing as described in the skill.</p>

        <figure class="">
<img src="/blog/assets/images/copilot_read_skill.png" alt="" /></figure>
      </li>
    </ul>
  </li>
</ul>

<p>Further information about <em>Agent Skills</em>:</p>

<ul>
  <li><a href="https://agentskills.io/home">agentskills.io</a></li>
  <li><a href="https://code.visualstudio.com/docs/copilot/customization/agent-skills">Use Agent Skills in VS Code</a></li>
  <li><a href="https://github.com/heilcheng/awesome-agent-skills">awesome-agent-skills</a></li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>In this comprehensive tutorial, we explored the powerful extensibility options available for integrating AI capabilities into Visual Studio Code through Copilot extensions. We covered three main approaches to extend Copilot’s functionality:</p>

<p><strong>Language Model Tools</strong> provide deep integration with VS Code APIs, allowing you to create domain-specific tools that can interact directly with the workspace and file system. While VS Code already includes many built-in tools, custom Language Model Tools enable specialized functionality tailored to your specific development needs.</p>

<p><strong>MCP (Model Context Protocol) Servers</strong> offer a standardized way to connect AI applications with external tools and data sources. We demonstrated both manual configuration via <code class="language-plaintext highlighter-rouge">mcp.json</code> files and programmatic registration through VS Code extensions, covering local servers, remote servers, and authentication scenarios.</p>

<p><strong>Chat Participants</strong> create specialized AI assistants that can leverage both Language Model Tools and MCP servers while providing a customized conversational interface. They excel when deep VS Code integration is required and can be easily distributed through the VS Code marketplace.</p>

<p>We also explored alternative customization approaches like <em>Instructions</em>, <em>Prompt Files</em> and <em>Custom Agents</em>, which can help to improve the Copilot experience according to your needs.</p>

<p>The key takeaway is that the choice between these approaches depends on your specific requirements: use Language Model Tools for VS Code-specific integrations, MCP servers for external tool access, and Chat Participants when you need a complete custom AI assistant experience that can be distributed as an extension.</p>

<p>The complete source code and examples from this tutorial are available in my <a href="https://github.com/fipro78/vscode_theia_cookbook">GitHub repository</a>, providing a practical foundation for building your own AI-powered VS Code extensions.</p>

<p>Whether you’re looking to enhance developer productivity with domain-specific AI tools or create entirely new AI-powered development workflows, this tutorial provides the essential building blocks to get started with extending Copilot in Visual Studio Code.</p>]]></content><author><name>Dirk Fauth</name></author><category term="fipro" /><category term="vscode" /><category term="typescript" /><category term="ai" /><category term="copilot" /><summary type="html"><![CDATA[In this tutorial I will explain how to extend Visual Studio Code with a customized AI experience. By creating a Visual Studio Code Copilot Extension, we will contribute tools, MCP servers and chat participants, to provide AI features with domain expert know-how.]]></summary></entry><entry><title type="html">Getting Started with Eclipse Theia</title><link href="https://vogella.com/blog/theia_getting_started/" rel="alternate" type="text/html" title="Getting Started with Eclipse Theia" /><published>2025-07-08T00:00:00+00:00</published><updated>2025-07-08T00:00:00+00:00</updated><id>https://vogella.com/blog/theia_getting_started</id><content type="html" xml:base="https://vogella.com/blog/theia_getting_started/"><![CDATA[<p>In my blog post <a href="https://vogella.com/blog/vscode-extension-webview-getting-started/">Getting Started with Visual Studio Code Extension Development</a> I explained how to implement a Visual Studio Code Extension that contributes a custom editor using the Webviews API and different Javascript frameworks.
Implementing a Visual Studio Code Extension means to buy-in to the Microsoft Visual Studio Code product world. At least at first sight.
By creating an application based on the <a href="https://theia-ide.org/theia-platform/">Eclipse Theia Platform</a> it is possible to build up tools that are similar to Visual Studio Code, but with some differences regarding features, extensibility and customization, privacy and deployment. It reuses components from the <a href="https://github.com/microsoft/vscode">VS Code Open Source project</a> and provides additional modules that add features that are otherwise only available in Visual Studio Code because of license restrictions.
It is also possible to integrate Visual Studio Code Extensions to a Theia Application, and therefore it is an alternative for the deployment of the developed Visual Studio Code Extension.</p>

<p>If you are interested in a more detailed comparison between Visual Studio Code and Eclipse Theia, have a look at the following articles:</p>

<ul>
  <li><a href="https://eclipsesource.com/blogs/2023/09/08/eclipse-theia-vs-code-oss/">Eclipse Theia vs. VS Code OSS</a></li>
  <li><a href="https://eclipsesource.com/blogs/2024/07/12/vs-code-vs-theia-ide/">The Theia IDE vs VS Code</a></li>
  <li><a href="https://eclipsesource.com/blogs/2024/12/17/is-it-a-good-idea-to-fork-vs-code/">Is Forking VS Code a Good Idea?</a></li>
</ul>

<p>In this tutorial I will show how to:</p>

<ul>
  <li>Create an Eclipse Theia Application</li>
  <li>Integrate a Visual Studio Code Extensions to a Theia Application</li>
  <li>Extend/Customize the Theia Application via a Theia Extension</li>
  <li>Containerize a Theia Application</li>
</ul>

<h2 id="dev-container">Dev Container</h2>

<p>To be able to build a Theia application, several tools and libraries need to be available.
As I created a Dev Container for the development of Visual Studio Code Extensions in my previous blog post, let’s follow this path further and extend the existing Dev Container.</p>

<ul>
  <li>
    <p>Open the file <em>.devcontainer/postCreateCommand.sh</em></p>

    <ul>
      <li>Add the Theia code generator <code class="language-plaintext highlighter-rouge">generator-theia-extension</code> to the initial global <code class="language-plaintext highlighter-rouge">npm install</code> instruction</li>
      <li>Install the necessary Theia dependencies</li>
    </ul>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="c"># Install a new version of npm, the Visual Studio Code Extension code generator, the extension manager,</span>
<span class="c"># the Angular CLI and the Theia code generator</span>
npm <span class="nb">install</span> <span class="nt">-g</span> npm yo generator-code @vscode/vsce @angular/cli generator-theia-extension

<span class="c"># Install Theia dependencies</span>
<span class="nb">sudo </span>apt-get update <span class="o">&amp;&amp;</span> <span class="nb">export </span><span class="nv">DEBIAN_FRONTEND</span><span class="o">=</span>noninteractive <span class="se">\</span>
    <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>apt-get <span class="nt">-y</span> <span class="nb">install</span> <span class="nt">--no-install-recommends</span> <span class="se">\</span>
    make <span class="se">\</span>
    gcc <span class="se">\</span>
    pkg-config <span class="se">\</span>
    build-essential <span class="se">\</span>
    python3.11 <span class="se">\</span>
    software-properties-common <span class="se">\</span>
    libx11-dev <span class="se">\</span>
    libxkbfile-dev <span class="se">\</span>
    libsecret-1-dev <span class="se">\</span>
    libssl-dev
</code></pre></div>    </div>

    <p><em><strong>Note:</strong></em><br />
If you have chosen to use a <em>Dockerfile</em> instead of a <em>postCreateCommand.sh</em> script file, the installation of the dependencies and the Theia code generator need to be added to the <em>Dockerfile</em> by using <code class="language-plaintext highlighter-rouge">RUN</code> commands of course.</p>

    <div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Install Theia dependencies</span>
<span class="k">RUN </span>apt-get update <span class="o">&amp;&amp;</span> <span class="nb">export </span><span class="nv">DEBIAN_FRONTEND</span><span class="o">=</span>noninteractive <span class="se">\
</span>    <span class="o">&amp;&amp;</span> apt-get <span class="nt">-y</span> <span class="nb">install</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>    make <span class="se">\
</span>    gcc <span class="se">\
</span>    dos2unix <span class="se">\
</span>    pkg-config <span class="se">\
</span>    build-essential <span class="se">\
</span>    python3.11 <span class="se">\
</span>    software-properties-common <span class="se">\
</span>    libx11-dev <span class="se">\
</span>    libxkbfile-dev <span class="se">\
</span>    libsecret-1-dev <span class="se">\
</span>    libssl-dev

<span class="c"># Install a new version of npm, the Visual Studio Code Extension code generator, the extension manager,</span>
<span class="c"># the Angular CLI and the Theia code generator</span>
<span class="k">RUN </span>npm <span class="nb">install</span> <span class="nt">-g</span> npm yo generator-code @vscode/vsce @angular/cli generator-theia-extension
</code></pre></div>    </div>
  </li>
  <li>
    <p>Rebuild the Dev Container to get the additional dependencies installed</p>
  </li>
</ul>

<p>Further details for building a Theia application are described in <a href="https://github.com/eclipse-theia/theia/blob/master/doc/Developing.md">How to build Theia and the example applications</a>.</p>

<h4 id="alternative-local-installation-on-windows">Alternative: Local installation on Windows</h4>

<p>If you prefer to develop on Windows instead of using the Dev Container, you need to prepare your local environment.
This is also necessary if you want to build the Theia Electron Desktop app, as you typically build for the OS you are running the build on.
That means, you need to build on Windows if you want to create a Windows Desktop application.</p>

<ul>
  <li>Install <a href="https://github.com/lukesampson/scoop#installation"><code class="language-plaintext highlighter-rouge">scoop</code></a>.</li>
  <li>Install <a href="https://github.com/coreybutler/nvm-windows"><code class="language-plaintext highlighter-rouge">nvm</code></a> with scoop: <code class="language-plaintext highlighter-rouge">scoop install nvm</code>.</li>
  <li>Install Node.js with <code class="language-plaintext highlighter-rouge">nvm</code>: <code class="language-plaintext highlighter-rouge">nvm install lts</code>, then use it: <code class="language-plaintext highlighter-rouge">nvm use lts</code>. You can list all available Node.js versions with <code class="language-plaintext highlighter-rouge">nvm list available</code> if you want to pick another version.</li>
  <li>Install <a href="https://www.python.org/downloads/windows/">Python</a> with <code class="language-plaintext highlighter-rouge">pip</code></li>
  <li>Set the environment variable <code class="language-plaintext highlighter-rouge">PYTHON</code> to the <em>python.exe</em></li>
  <li>Add the Python installation folder and the <em>Scripts</em> folder to the <code class="language-plaintext highlighter-rouge">PATH</code></li>
  <li>Install <code class="language-plaintext highlighter-rouge">distutils</code> module: <code class="language-plaintext highlighter-rouge">pip install setuptools</code><br />
Needed to install python packages for node gyp, but was removed in Python v3.12</li>
  <li>Install the Visual Studio <a href="https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022">build tools</a> with <em>Desktop development with C++</em></li>
</ul>

<p>This is also described in (Theia - Developing - Building on Windows)[https://github.com/eclipse-theia/theia/blob/master/doc/Developing.md#building-on-windows]</p>

<p>Additionally you need to configure the project environment, similar to what is done in the Dev Container.</p>

<ul>
  <li>
    <p>Install code generator for Visual Studio Code and Theia Extensions and install the dependencies</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install -g npm yo generator-code @vscode/vsce @angular/cli generator-theia-extension
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="create-the-theia-application-structure">Create the Theia application structure</h2>

<p>A Theia Application project consists of multiple packages.
There are two projects for the application (browser and desktop) and typically at least one extension project.
Of course this is dependent on the use case.
You could also build up the Theia Application with only Theia dependencies and Visual Studio Code Extensions.</p>

<p>As this tutorial is part of my <em>Visual Studio Code Extension - Theia - Cookbook</em>, I will setup a <strong>Multi Module Repository</strong> (monorepo) that contains the Visual Studio Code Extensions sources and the Theia related sources in one place.
For other projects it might be more useful to keep the extension sources and the Theia app sources in separate repositories. Especially if the extension should be consumeable by different applications.</p>

<p>To avoid dependency collisions between the Visual Studio Code Extension projects and the Theia projects
(e.g. different versions of <code class="language-plaintext highlighter-rouge">webpack</code>, different <code class="language-plaintext highlighter-rouge">copy-webpack-plugin</code> versions, <code class="language-plaintext highlighter-rouge">mocha</code> vs <code class="language-plaintext highlighter-rouge">jasmine</code>), the projects should be separated and not combined with a common workspace setting.</p>

<ul>
  <li>Open a <strong>Terminal</strong></li>
  <li>Create a new subfolder <em>theia</em> in the project root</li>
  <li>Switch to that folder</li>
  <li>
    <p>Create the Theia App Modules by using the <code class="language-plaintext highlighter-rouge">theia-extension</code> generator</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yo theia-extension
</code></pre></div>    </div>
  </li>
  <li>Select the type <strong>No Extension (just a Theia application)</strong></li>
</ul>

<p><em><strong>Note:</strong></em><br />
If you generated the Theia application structure with a <code class="language-plaintext highlighter-rouge">generator-theia-extension</code> Version &lt; 0.1.45 and <code class="language-plaintext highlighter-rouge">yarn</code> is not installed in your system, the code generation will fail with an error when trying to spawn <code class="language-plaintext highlighter-rouge">yarn</code>.
In that case you first need to follow the steps described in <a href="#yarn-vs-npm">Yarn vs NPM</a>.<br />
If you use <code class="language-plaintext highlighter-rouge">generator-theia-extension</code> Version &gt;= 0.1.45 there should be no <code class="language-plaintext highlighter-rouge">yarn</code> dependencies anymore in the generated sources.</p>

<p>The <code class="language-plaintext highlighter-rouge">generator-theia-extension</code> creates two new modules <em>browser-app</em> and <em>electron-app</em> inside the <em>theia</em> folder.
Additionally there are some project files</p>

<ul>
  <li><em>theia/lerna.json</em><br />
<a href="https://lerna.js.org/">Lerna</a> is a fast, modern build system for managing and publishing multiple JavaScript/TypeScript packages from the same repository. There is no need to change anything here.</li>
  <li><em>theia/.gitignore</em><br />
The <em>.gitignore</em> file for the <em>theia</em> folder. Dependent on your strategy to handle <em>.gitignore</em> you can simply keep the file or move the content to the <em>.gitignore</em> file in the root folder. For the later you need of course to adapt the entries with the <em>theia</em> folder prefix.</li>
  <li><em>theia/package.json</em><br />
The <em>package.json</em> for the Theia projects. It makes use of the <code class="language-plaintext highlighter-rouge">workspaces</code> feature of the package manager (<a href="https://docs.npmjs.com/cli/using-npm/workspaces">npm workspaces</a> / <a href="https://yarnpkg.com/features/workspaces">yarn workspaces</a>)</li>
  <li><em>theia/README.md</em><br />
The generated README file that contains some basic information about building and running the Theia application.</li>
  <li>
    <p><em>theia/.vscode/launch.json</em> that contains some launch scripts. They need to be moved to the main <em>.vscode/launch.json</em> and modified to work correctly.</p>

    <ul>
      <li>Copy the two configurations to <em>.vscode/launch.json</em></li>
      <li>Replace <code class="language-plaintext highlighter-rouge">${workspaceRoot}</code> with <code class="language-plaintext highlighter-rouge">${workspaceRoot}/theia</code> to have the correct file links in the configurations</li>
      <li>Delete the folder <em>theia/.vscode</em></li>
    </ul>
  </li>
</ul>

<h3 id="yarn-vs-npm">Yarn vs NPM</h3>

<p>Since Theia 1.58.0 <code class="language-plaintext highlighter-rouge">npm</code> is used to build Theia instead of <code class="language-plaintext highlighter-rouge">yarn</code>, which was used before see (<a href="https://github.com/eclipse-theia/theia/pull/14481">Use npm instead of yarn to build Theia</a>).
The <a href="https://www.npmjs.com/package/generator-theia-extension">Eclipse Theia Generator</a> recently was updated to use <code class="language-plaintext highlighter-rouge">npm</code> instead of <code class="language-plaintext highlighter-rouge">yarn</code>. You need at least version 0.1.45 to get the Theia sources generated that use <code class="language-plaintext highlighter-rouge">npm</code>. If you are unsure which version of the Eclipse Theia Generator you have globally installed, you can check it via</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm ls --depth=0 -global
</code></pre></div></div>

<p>If you have an older version (e.g. because you are using a Dev Container that was created some time ago) you can update the Eclipse Theia Generator via</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install -g generator-theia-extension@latest
</code></pre></div></div>

<h4 id="update-an-existing-project">Update an existing project</h4>

<p>In case you have an existing Theia project that still uses <code class="language-plaintext highlighter-rouge">yarn</code> and you want to use only one package manager in our setup, perform the following updates to use <code class="language-plaintext highlighter-rouge">npm</code> instead of <code class="language-plaintext highlighter-rouge">yarn</code>:</p>

<ul>
  <li>Update <em>theia/lerna.json</em><br />
Change <code class="language-plaintext highlighter-rouge">npmClient</code> to <code class="language-plaintext highlighter-rouge">npm</code></li>
  <li><em>theia/package.json</em>
    <ul>
      <li>Update the <code class="language-plaintext highlighter-rouge">engines</code>
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"engines"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"npm"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&gt;=10.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"node"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&gt;=18"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>Update the <code class="language-plaintext highlighter-rouge">scripts</code>
        <ul>
          <li>Replace <code class="language-plaintext highlighter-rouge">yarn --cwd</code> with <code class="language-plaintext highlighter-rouge">npm --prefix</code></li>
          <li>Add <code class="language-plaintext highlighter-rouge">run</code> between the folder and the script
            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"build:browser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix browser-app run bundle"</span><span class="p">,</span><span class="w">
</span><span class="nl">"build:electron"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix electron-app run bundle"</span><span class="p">,</span><span class="w">
</span><span class="nl">"prepare"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lerna run prepare"</span><span class="p">,</span><span class="w">
</span><span class="nl">"postinstall"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia check:theia-version"</span><span class="p">,</span><span class="w">
</span><span class="nl">"start:browser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix browser-app run start"</span><span class="p">,</span><span class="w">
</span><span class="nl">"start:electron"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix electron-app run start"</span><span class="p">,</span><span class="w">
</span><span class="nl">"watch:browser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lerna run --parallel watch --ignore electron-app"</span><span class="p">,</span><span class="w">
</span><span class="nl">"watch:electron"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lerna run --parallel watch --ignore browser-app"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>            </div>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li><em>theia/browser-app/package.json</em>
    <ul>
      <li>Replace <code class="language-plaintext highlighter-rouge">yarn rebuild</code> with <code class="language-plaintext highlighter-rouge">npm run rebuild</code></li>
      <li>Add prepare scripts
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"prepare"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run clean &amp;&amp; npm run build"</span><span class="err">,</span><span class="w">
</span><span class="nl">"clean"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia clean"</span><span class="err">,</span><span class="w">
</span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia build --mode development"</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li><em>theia/electron-app/package.json</em>
    <ul>
      <li>Replace <code class="language-plaintext highlighter-rouge">yarn rebuild</code> with <code class="language-plaintext highlighter-rouge">npm run rebuild</code></li>
      <li>Add prepare scripts
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"prepare"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run clean &amp;&amp; npm run build"</span><span class="err">,</span><span class="w">
</span><span class="nl">"clean"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia clean"</span><span class="err">,</span><span class="w">
</span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia build --mode development"</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Delete the file <em>theia/yarn.lock</em></li>
</ul>

<h3 id="optional-define-tasks">Optional: Define Tasks</h3>

<ul>
  <li>Open the file <em>.vscode/tasks.json</em></li>
  <li>
    <p>Add the following task definitions</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Build Theia Browser"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build:browser"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/theia"</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Start Theia Browser"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"start:browser"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/theia"</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Watch Theia Browser"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"watch:browser"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"owner"</span><span class="p">:</span><span class="w"> </span><span class="s2">"typescript"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"pattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
            </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(.*?)"</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"browser-app: webpack (.*?) compiled successfully"</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/theia"</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Watch and Start Theia Browser"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"dependsOrder"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sequence"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"dependsOn"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"Watch Theia Browser"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"Start Theia Browser"</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Build Theia Electron"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build:electron"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/theia"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Start Theia Electron"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"start:electron"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/theia"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Watch Theia Electron"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"watch:electron"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"owner"</span><span class="p">:</span><span class="w"> </span><span class="s2">"typescript"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"pattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(.*?)"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"electron-app: webpack (.*?) compiled successfully"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/theia"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Watch and Start Theia Electron"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dependsOrder"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sequence"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dependsOn"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Watch Theia Electron"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Start Theia Electron"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<p>This step is optional, because you can perform those steps also from a <strong>Terminal</strong> window.
I personally like to work with the <a href="https://marketplace.visualstudio.com/items?itemName=spmeesseman.vscode-taskexplorer">Task Explorer</a> Visual Studio Code Extension, which is added to the Dev Container configuration in my example.
Using this extension you can trigger the task <em>Build Theia Browser</em> and <em>Start Theia Browser</em> from the <em>Task Explorer</em> tree view.</p>

<h4 id="interlude-dependency-hell">Interlude: Dependency Hell</h4>

<p>From my experience over the last months, the dependency management with npm is a nightmare! I often followed my own notes to create a new Theia application from scratch, and suddenly came across some issues because of version updates in dependent modules.
One was for example an incompatible <code class="language-plaintext highlighter-rouge">node-abi</code> version. Somehow the newest version was pulled on creating a new Theia application stub via the generator, which then failed because the current <code class="language-plaintext highlighter-rouge">node-abi</code> version required <code class="language-plaintext highlighter-rouge">node@&gt;=22.12.0</code> while the Dev Container was using <code class="language-plaintext highlighter-rouge">node@20.18.1</code>. Another example was a breaking change in <code class="language-plaintext highlighter-rouge">inversify</code>, and creating a new application and pulling the latest version caused compile errors on the way.</p>

<p>Of course the correct solution is that the downstream projects fix their dependencies, which for example happened in Theia via <a href="https://github.com/eclipse-theia/theia/pull/14435">PR 14435</a> or <a href="https://github.com/eclipse-theia/theia/issues/15139">Issue 15139</a>.
As an intermediate solution you can pin the version of a dependency that breaks in its newest version by configuring a <a href="https://github.com/yarnpkg/rfcs/blob/master/implemented/0000-selective-versions-resolutions.md">Selective Version Resolution</a>.
This means to add a <code class="language-plaintext highlighter-rouge">resolutions</code> field to the <em>package.json</em> and define the version override.
As an example, to pin the version of <code class="language-plaintext highlighter-rouge">node-abi</code> to 3.74.0, which was used in the <code class="language-plaintext highlighter-rouge">electron-rebuild</code> version used by Theia, you could add the following snippet to the <em>theia/package.json</em>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"resolutions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"node-abi"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.74.0"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="verify">Verify</h3>

<p>To verify that the intial Theia application setup works, let’s build and start the Theia browser application.</p>

<p>If you created the tasks as explained in <a href="#optional-define-tasks">Optional: Define Tasks</a> you can either</p>

<ul>
  <li>Press <strong>F1</strong>
    <ul>
      <li>Select <code class="language-plaintext highlighter-rouge">Tasks: Run Task</code></li>
      <li>Select <code class="language-plaintext highlighter-rouge">Build Theia Browser</code> to build the Theia browser application</li>
      <li>Select <code class="language-plaintext highlighter-rouge">Start Theia Browser</code> to start the Theia browser application</li>
    </ul>
  </li>
  <li>Use the <a href="https://marketplace.visualstudio.com/items?itemName=spmeesseman.vscode-taskexplorer">Task Explorer</a> Visual Studio Code Extension
    <ul>
      <li>Expand <em>vscode_theia_cookbook - vscode</em> in the <em>Task Explorer</em> view</li>
      <li>Run the <code class="language-plaintext highlighter-rouge">Build Theia Browser</code> task from the tree to build the Theia browser application</li>
      <li>Run the <code class="language-plaintext highlighter-rouge">Start Theia Browser</code> task from the tree to start the Theia browser application</li>
    </ul>
  </li>
</ul>

<p>If you want to work with the Terminal and execute the <code class="language-plaintext highlighter-rouge">npm</code> scripts manually</p>

<ul>
  <li>Open a <strong>Terminal</strong></li>
  <li>Switch to the <em>theia</em> folder</li>
  <li>Run <code class="language-plaintext highlighter-rouge">npm run build:browser</code> to build the Theia browser application</li>
  <li>Run <code class="language-plaintext highlighter-rouge">npm run start:browser</code> to start the Theia browser application</li>
</ul>

<p><em><strong>Note:</strong></em><br />
You can also use the <em>Launch Configuration</em> from the <em>.vscode/launch.json</em> via <em>Run and Debug</em>. This will start the application and enable debugging. But you first need to build the application via commandline or Task. The debugging topic will be covered at a later time in this blog post.</p>

<p>Once started, you can open a browser on http://localhost:3000 and see the started Theia application.</p>

<h4 id="interlude-javascript-heap-out-of-memory">Interlude: JavaScript heap out of memory</h4>

<p>If you phase a <strong>JavaScript heap out of memory</strong> error on building the browser-app, you probably need to increase the memory limit in Node.js.
This can be done via the <a href="https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib"><code class="language-plaintext highlighter-rouge">--max-old-space-size</code></a> command line option.</p>

<ul>
  <li>Open <em>.devcontainer/devcontainer.json</em></li>
  <li>Add the following <code class="language-plaintext highlighter-rouge">NODE_OPTIONS</code> via the <code class="language-plaintext highlighter-rouge">containerEnv</code> property</li>
</ul>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"containerEnv"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"NODE_OPTIONS"</span><span class="p">:</span><span class="w"> </span><span class="s2">"--max-old-space-size=8192"</span><span class="w">
  </span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>Rebuild the dev container and verify if the memory issue is resolved.</li>
</ul>

<h3 id="script-updates">Script Updates</h3>

<p>To get the Theia dependencies installed when creating a new Dev Container, update the <code class="language-plaintext highlighter-rouge">install:all</code> script.</p>

<ul>
  <li>Update the <em>package.json</em> in the repository root</li>
</ul>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vscode-theia-cookbook"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"private"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"install:all"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cd vscode-extension &amp;&amp; npm install &amp;&amp; cd ../angular-extension &amp;&amp; npm run install:all &amp;&amp; cd ../react-extension &amp;&amp; npm run install:all &amp;&amp; cd ../theia &amp;&amp; npm install"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="theia---visual-studio-code-extension">Theia - Visual Studio Code Extension</h2>

<p>As already mentioned, Theia is compatible with Visual Studio Code Extensions.
This means it is possible to build a custom tool based on Eclipse Theia that integrates the Visual Studio Code Extensions that were created in the previous blog post.</p>

<p>In this section we will extend the Theia Application that we just created with the Visual Studio Code Extensions created in the previous blog post.</p>

<ul>
  <li>
    <p>Add support for Visual Studio Code Extensions to the Theia application.</p>

    <ul>
      <li>
        <p>Update the <em>package.json</em> of the <em>browser-app</em> and the <em>electron-app</em></p>

        <ul>
          <li>Open a <strong>Terminal</strong></li>
          <li>
            <p>Switch to the Theia Application directory (<em>theia/browser-app</em> and <em>theia/electron-app</em>)</p>
          </li>
          <li>
            <p>Add <code class="language-plaintext highlighter-rouge">@theia/plugin-ext</code> as a dependency for the webview support</p>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @theia/plugin-ext
</code></pre></div>            </div>

            <p><em><strong>Note:</strong></em><br />
By including <code class="language-plaintext highlighter-rouge">@theia/plugin-ext</code> all Theia packages and their dependencies to which plugins/extensions can contribute are added. This can be quite a lot compared to the initially created application and might even introduce dependencies you would not directly have thought of (e.g. <code class="language-plaintext highlighter-rouge">@theia/ai-mcp</code> and <code class="language-plaintext highlighter-rouge">@theia/ai-core</code>).</p>
          </li>
          <li>
            <p>Add <code class="language-plaintext highlighter-rouge">@theia/plugin-ext-vscode</code> as a dependency</p>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @theia/plugin-ext-vscode
</code></pre></div>            </div>
          </li>
          <li>
            <p>Open the <em>package.json</em> of the application project</p>

            <ul>
              <li>
                <p>Specify the location of the plugins that should be loaded on initialization via the <code class="language-plaintext highlighter-rouge">--plugins</code> option of the <code class="language-plaintext highlighter-rouge">start</code> script. We add them on the same level as the application projects, to have a single location for our workspace.</p>
              </li>
              <li>Add a script <code class="language-plaintext highlighter-rouge">download:plugins</code> to download the plugins</li>
              <li>
                <p>Add scripts to perform a clean build in a <code class="language-plaintext highlighter-rouge">prepare</code> script and additionally download the plugins</p>
              </li>
              <li>
                <p>Configure the options to pre-install Visual Studio Code Extensions</p>

                <ul>
                  <li><code class="language-plaintext highlighter-rouge">theiaPluginsDir</code> - the folder in which the pre-installed extensions are located</li>
                  <li><code class="language-plaintext highlighter-rouge">theiaPlugins</code>- list of published vs code extension urls, can be empty here, because we will pre-install our extension manually</li>
                </ul>
              </li>
            </ul>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"prepare"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run clean &amp;&amp; npm run build &amp;&amp; npm run download:plugins"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"clean"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia clean"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia build --mode development"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"download:plugins"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia download:plugins"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"bundle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run rebuild &amp;&amp; theia build --mode development"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"rebuild"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia rebuild:browser --cacheRoot .."</span><span class="p">,</span><span class="w">
  </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia start --plugins=local-dir:../plugins"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"watch"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run rebuild &amp;&amp; theia build --watch --mode development"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="nl">"theiaPluginsDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"../plugins"</span><span class="err">,</span><span class="w">
</span><span class="nl">"theiaPlugins"</span><span class="p">:</span><span class="w"> </span><span class="p">{}</span><span class="err">,</span><span class="w">
</span></code></pre></div>            </div>
          </li>
          <li>
            <p>Build the Theia applications to take up the updated dependencies</p>

            <ul>
              <li>Open a <strong>Terminal</strong></li>
              <li>Switch to the <em>theia</em> directory</li>
              <li>
                <p>Execute the following commands to trigger the application builds.</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run build:browser
npm run build:electron
</code></pre></div>                </div>
              </li>
            </ul>

            <p><em><strong>Note:</strong></em><br />
If you created the tasks as described before, you can of course use the the tasks to perform the application build.</p>
          </li>
        </ul>
      </li>
      <li>
        <p>Open the file <em>.vscode/launch.json</em></p>
        <ul>
          <li>Locate the <em>Start Browser Backend</em> configuration
            <ul>
              <li>Add <code class="language-plaintext highlighter-rouge">"--plugins=local-dir:${workspaceRoot}/theia/plugins"</code> to the <code class="language-plaintext highlighter-rouge">args</code></li>
            </ul>
          </li>
          <li>Locate the <em>Start Electron Backend</em> configuration
            <ul>
              <li>Add <code class="language-plaintext highlighter-rouge">"--plugins=local-dir:${workspaceRoot}/theia/plugins"</code> to the <code class="language-plaintext highlighter-rouge">args</code></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <p>Pre-install the Visual Studio Code extensions<br />
You have basically two options:</p>

    <ul>
      <li>
        <p>Option 1: Symbolic Links<br />
Use <a href="https://www.npmjs.com/package/symlink-dir"><code class="language-plaintext highlighter-rouge">symlink-dir</code></a> to link the extension to the <em>plugin</em> folder location of the Theia application as described in <a href="https://theia-ide.org/docs/authoring_vscode_extensions/#developing-vs-code-extensions-in-a-theia-project">Developing VS Code Extensions in a Theia Project</a></p>

        <ul>
          <li>Open a <strong>Terminal</strong></li>
          <li>Switch to the <em>vscode-extension</em> directory</li>
          <li>
            <p>Add the following packages as <code class="language-plaintext highlighter-rouge">devDependencies</code></p>

            <ul>
              <li>
                <p><a href="https://www.npmjs.com/package/rimraf"><code class="language-plaintext highlighter-rouge">rimraf</code></a> to delete the directory</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D rimraf
</code></pre></div>                </div>
              </li>
              <li>
                <p><a href="https://www.npmjs.com/package/symlink-dir"><code class="language-plaintext highlighter-rouge">symlink-dir</code></a> to create a symbolic link</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D symlink-dir
</code></pre></div>                </div>

                <p><em><strong>Note:</strong></em><br />
With a <code class="language-plaintext highlighter-rouge">symlink-dir</code> version &gt; 7.1.0 you might face <a href="https://github.com/pnpm/symlink-dir/issues/68">Endless self reference to target parent folder</a>.
In that case force an earlier version via</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D symlink-dir@7.1.0
</code></pre></div>                </div>
              </li>
            </ul>
          </li>
          <li>
            <p>Open the file <em>vscode-extension/package.json</em> and add the following <code class="language-plaintext highlighter-rouge">scripts</code></p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"prepare"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run clean &amp;&amp; npm run vscode:prepublish &amp;&amp; npm run symlink"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"clean"</span><span class="p">:</span><span class="w"> </span><span class="s2">"rimraf ../theia/plugins/vscode-extension"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"symlink"</span><span class="p">:</span><span class="w"> </span><span class="s2">"symlink-dir . ../theia/plugins/vscode-extension"</span><span class="p">,</span><span class="w">
  </span><span class="err">...</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>            </div>
          </li>
          <li>
            <p>Execute the <code class="language-plaintext highlighter-rouge">prepare</code> script</p>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run prepare
</code></pre></div>            </div>
          </li>
          <li>Verify that the symbolic link to the <em>vscode-extension</em> folder is created in <em>theia/plugins</em></li>
          <li>Repeat the above steps for <em>angular-extension</em> and <em>react-extension</em>. Remember to update the folder references in the <code class="language-plaintext highlighter-rouge">clean</code> and the <code class="language-plaintext highlighter-rouge">symlink</code> scripts to match the extension.</li>
        </ul>

        <p><em><strong>Note:</strong></em><br />
The <code class="language-plaintext highlighter-rouge">prepare</code> script is a life cycle script that is automatically executed at various points which is described in <a href="https://docs.npmjs.com/cli/v11/using-npm/scripts#life-cycle-scripts">Life Cycle Scripts</a>. By adding the <code class="language-plaintext highlighter-rouge">prepare</code> script, the <code class="language-plaintext highlighter-rouge">install:all</code> scripts in <em>angular-extension</em> and <em>react-extension</em> will fail because of the execution order. There are two solutions for this issue:</p>

        <ol>
          <li>
            <p>Change the order in the <code class="language-plaintext highlighter-rouge">install:all</code> scripts to first call <code class="language-plaintext highlighter-rouge">npm install</code> in the <em>webview-ui</em> folder</p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"install:all"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cd webview-ui &amp;&amp; npm install &amp;&amp; cd .. &amp;&amp; npm install"</span><span class="p">,</span><span class="w">
  </span><span class="err">...</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>            </div>
          </li>
          <li>
            <p>Call <code class="language-plaintext highlighter-rouge">npm install</code> with the <code class="language-plaintext highlighter-rouge">--ignore-scripts</code> flag</p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"install:all"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm install --ignore-scripts &amp;&amp; cd webview-ui &amp;&amp; npm install"</span><span class="p">,</span><span class="w">
  </span><span class="err">...</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>            </div>
          </li>
        </ol>
      </li>
      <li>
        <p>Option 2: VSIX<br />
This means to create the <em>.vsix</em> extension package file and extract it to the <em>plugins</em> folder manually. This has the advantage that the result is the same as if you install a Visual Studio Code extension from a marketplace. So it is a real integration test and makes it easier to create a container image as I will show later.</p>

        <ul>
          <li>Switch to the <em>vscode-extension</em> folder in a Terminal</li>
          <li>
            <p>Add the following packages as <code class="language-plaintext highlighter-rouge">devDependencies</code></p>

            <ul>
              <li>
                <p><a href="https://www.npmjs.com/package/mkdirp"><code class="language-plaintext highlighter-rouge">mkdirp</code></a> to create the directory</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D mkdirp
</code></pre></div>                </div>
              </li>
              <li>
                <p><a href="https://www.npmjs.com/package/rimraf"><code class="language-plaintext highlighter-rouge">rimraf</code></a> to delete the directory</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D rimraf
</code></pre></div>                </div>
              </li>
              <li>
                <p><a href="https://www.npmjs.com/package/run-script-os"><code class="language-plaintext highlighter-rouge">run-script-os</code></a> to execute the unzip operation in an OS dependent way</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D run-script-os
</code></pre></div>                </div>
              </li>
            </ul>
          </li>
          <li>
            <p>Open the file <em>vscode-extension/package.json</em> and add the following <code class="language-plaintext highlighter-rouge">scripts</code></p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="err">...</span><span class="w">
  </span><span class="nl">"clean"</span><span class="p">:</span><span class="w"> </span><span class="s2">"rimraf ../theia/plugins/vscode-extension"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"package"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vsce package --allow-missing-repository"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"theia:prepare-app"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run package &amp;&amp; npm run theia:extract-vsix"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"theia:extract-vsix"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run clean &amp;&amp; npm run theia:prepare &amp;&amp; npm run unzip"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"theia:prepare"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mkdirp ../theia/plugins/vscode-extension"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"unzip"</span><span class="p">:</span><span class="w"> </span><span class="s2">"run-script-os"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"unzip:windows"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tar -xf vscode-extension-0.0.1.vsix -C ../theia/plugins/vscode-extension"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"unzip:nix"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unzip vscode-extension-0.0.1.vsix -d ../theia/plugins/vscode-extension"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>            </div>
          </li>
          <li>
            <p>Execute <code class="language-plaintext highlighter-rouge">npm run theia:prepare-app</code> on the commandline of the <em>vscode-extension</em> project folder<br />
This will build the project, package it to a <em>.vsix</em> file and extract the file to the <em>theia/plugins</em> folder.</p>

            <p><em><strong>Note:</strong></em><br />
If you get the following error on running the script:</p>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>It seems the README.md still contains template text. Make sure to edit the README.md file before you package or publish your extension.
</code></pre></div>            </div>

            <p>Open the <em>vscode-extension/README.md</em> of the extension project that shows the error and delete the following sentence <code class="language-plaintext highlighter-rouge">This is the README for your extension "vscode-extension".</code><br />
Of course the real solution is to provide a meaningful README instead of the template. But for this tutorial, we can simply fix the error this way. Or simply delete the <em>README.md</em> of the extension for this tutorial.</p>

            <p>To avoid the question about the license on packaging, add a LICENSE file to <em>vscode-extension</em> folder with the information about the extension’s license.</p>
          </li>
          <li>Verify that the folder <em>theia/plugins/vscode-extension</em> is created and contains the content of the <em>.vsix</em> file</li>
          <li>Repeat the above steps for <em>angular-extension</em> and <em>react-extension</em>. Remember to update the folder references according to the extension.</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <p>Verify the installation of the extensions</p>

    <ul>
      <li>Option 1: Via task <em>Start Theia Browser</em></li>
      <li>Option 2: Via Terminal
        <ul>
          <li>Run the Theia browser application</li>
          <li>Open a <strong>Terminal</strong></li>
          <li>Switch to the <em>theia</em> directory</li>
          <li>Start the Theia browser application
            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run start:browser
</code></pre></div>            </div>
          </li>
        </ul>
      </li>
      <li>Open a browser and go to http://localhost:3000</li>
      <li>Open the <em>Plugins</em> view via <em>Menu - View - Plugins</em>
        <ul>
          <li>Verify that the <em>angular-extension</em>, the <em>react-extension</em> and the <em>vscode-extension</em> are listed in the <em>PLUGINS</em> view.</li>
        </ul>
      </li>
      <li>Open a folder somewhere<br />
For example, create a folder <em>example</em> in the home directory of the <em>node</em> user via <strong>Terminal</strong> of the Theia browser application.</li>
      <li>Create a new file named <em>homer.person</em>
        <ul>
          <li>The file should open with a custom editor from the installed Visual Studio Code extensions.</li>
          <li>Try to open the file with another editor via right click on the file, <em>Open With…</em> and then selecting one of the provided editors<br />
If you do not see the contributed custom editors in the list, try to rebuild the Theia browser application and start it again.</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <p>Open the file <em>theia/.gitignore</em></p>
    <ul>
      <li>Add the <em>plugins</em> folder to avoid that the <em>plugins</em> are added to the repository.</li>
    </ul>
  </li>
</ul>

<h3 id="possible-issues-with-the-visual-studio-code-webview-integration-in-theia">Possible issues with the Visual Studio Code webview integration in Theia</h3>

<p>The following chapter contains some issues I came across when integrating a Visual Studio Code Extension in Theia.</p>

<h4 id="webview-file-uris">Webview file URIs</h4>

<p>If you use <code class="language-plaintext highlighter-rouge">path</code> or <code class="language-plaintext highlighter-rouge">fsPath</code> on the URI object of a file resource obtained via <code class="language-plaintext highlighter-rouge">webview.asWebviewUri</code>, you will get an incorrect value, because Theia prefixes the URL with <code class="language-plaintext highlighter-rouge">\webview\theia-resource\file\\\</code>.</p>

<p>This can be worked around with code like this:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fileUri</span> <span class="o">=</span> <span class="nf">getUri</span><span class="p">(</span><span class="nx">webview</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="p">[</span>
  <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">assets</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">myFile.yaml</span><span class="dl">"</span><span class="p">,</span>
<span class="p">]);</span>

<span class="c1">// workaround to fix file path issue with Theia</span>
<span class="kd">var</span> <span class="nx">filePath</span> <span class="o">=</span> <span class="nx">fileUri</span><span class="p">.</span><span class="nx">fsPath</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="nx">fileUri</span><span class="p">.</span><span class="nx">fsPath</span><span class="p">.</span><span class="nf">startsWith</span><span class="p">(</span><span class="dl">"</span><span class="se">\\</span><span class="s2">webview</span><span class="se">\\</span><span class="s2">theia-resource</span><span class="se">\\</span><span class="s2">file</span><span class="se">\\\\\\</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
  <span class="nx">filePath</span> <span class="o">=</span> <span class="nx">fileUri</span><span class="p">.</span><span class="nx">fsPath</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span>
    <span class="dl">"</span><span class="se">\\</span><span class="s2">webview</span><span class="se">\\</span><span class="s2">theia-resource</span><span class="se">\\</span><span class="s2">file</span><span class="se">\\\\\\</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">""</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This issue was already reported and will likely be fixed in an upcoming Theia release. <a href="https://github.com/eclipse-theia/theia/issues/14727">Issue #14727</a></p>

<h4 id="webviews-not-showing-up-in-theia">Webviews not showing up in Theia</h4>

<p>If the webviews do not show up on opening them, it could be an issue with the security settings related to the webview origin pattern.
The reason is that webviews in the browser app are shown in an <code class="language-plaintext highlighter-rouge">iframe</code>.
You can find further information in the <a href="https://www.npmjs.com/package/@theia/plugin-ext?activeTab=readme">@theia/plugin-ext README</a>.</p>

<p>Set <code class="language-plaintext highlighter-rouge">THEIA_WEBVIEW_EXTERNAL_ENDPOINT=""</code> to switch to the unsecure mode for testing as described in the <a href="https://projects.eclipse.org/projects/ecd.theia/releases/0.13.0/plan">Theia 0.13.0 Release Plan</a>. For the productive deployment hosting the webview handlers on a sub-domain is more secure.</p>

<ul>
  <li>Open the file <em>.devcontainer/devcontainer.json</em></li>
  <li>Add the <code class="language-plaintext highlighter-rouge">containerEnv</code> variable
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"containerEnv"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"THEIA_WEBVIEW_EXTERNAL_ENDPOINT"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<p><em><strong>Note:</strong></em><br />
On opening the Theia app with the changed <code class="language-plaintext highlighter-rouge">THEIA_WEBVIEW_EXTERNAL_ENDPOINT</code>, you will see a notification at the bottom right, mentioning the potential security issue. This can be disabled by setting <code class="language-plaintext highlighter-rouge">"warnOnPotentiallyInsecureHostPattern": false</code>.</p>

<ul>
  <li>Open <em>theia/browser-app/package.json</em></li>
  <li>
    <p>Add a <code class="language-plaintext highlighter-rouge">frontend</code> configuration to <code class="language-plaintext highlighter-rouge">theia</code></p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"theia"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"browser"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"frontend"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"config"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"warnOnPotentiallyInsecureHostPattern"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<h4 id="welcome-views-do-not-show-content">Welcome views do not show content</h4>

<p>This seems to be an issue in Theia and was already reported via <a href="https://github.com/eclipse-theia/theia/issues/9361">Issue #9361</a>.</p>

<p>A possible workaround is to register a <code class="language-plaintext highlighter-rouge">TreeDataProvider</code> which returns an empty tree.</p>

<h2 id="consume-third-party-visual-studio-code-extensions-in-theia">Consume third-party Visual Studio Code Extensions in Theia</h2>

<p>In the previous section we integrated our Visual Studio Code Extension to the Theia application. Additionally you can consume third-party Visual Studio Code Extensions from the <a href="https://open-vsx.org/">Open VSX Registry</a>.</p>

<p>The first option to consume a Visual Studio Code Extension is to pre-install it at build-time. We have already prepared the <em>package.json</em> files for this in the previous section by adding <code class="language-plaintext highlighter-rouge">theiaPlugins</code> and <code class="language-plaintext highlighter-rouge">theiaPluginsDir</code> and the <code class="language-plaintext highlighter-rouge">download:plugins</code> script.
Let’s for example pre-install the <a href="https://open-vsx.org/extension/alphabotsec/vscode-eclipse-keybindings">Eclipse Keymap</a> to help people like me that are coming from Eclipse and are used to the Eclipse keybindings:</p>

<p><em><strong>Note:</strong></em><br />
Only Microsoft products can use and connect to Microsoft’s Extension Marketplace. So it is not allowed to install extensions from there to a Theia application.
Further information can be found in this <a href="https://www.eclipse.org/community/eclipse_newsletter/2020/march/1.php">article</a>.
Also note that there are also some Extensions that are only allowed to be installed in Microsoft products,
e.g. <a href="https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance">Pylance</a> the language server for Python from Microsoft can not be installed in a Theia application.</p>

<ul>
  <li>
    <p>Open the file <em>theia/browser-app/package.json</em></p>

    <ul>
      <li>
        <p>Add the following entry in the <code class="language-plaintext highlighter-rouge">theiaPlugins</code> section</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"theiaPlugins"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"eclipse-keybindings"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://open-vsx.org/api/alphabotsec/vscode-eclipse-keybindings/0.16.1/file/alphabotsec.vscode-eclipse-keybindings-0.16.1.vsix"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>

        <p><em><strong>Note:</strong></em><br />
You find the link to the <em>.vsix</em> file on the extension page with the <strong>Download</strong> button.</p>
      </li>
    </ul>
  </li>
  <li>
    <p>Open the file <em>theia/electron-app/package.json</em></p>

    <ul>
      <li>
        <p>Add the following entry in the <code class="language-plaintext highlighter-rouge">theiaPlugins</code> section</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"theiaPlugins"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"eclipse-keybindings"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://open-vsx.org/api/alphabotsec/vscode-eclipse-keybindings/0.16.1/file/alphabotsec.vscode-eclipse-keybindings-0.16.1.vsix"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>

        <p><em><strong>Note:</strong></em><br />
You find the link to the <em>.vsix</em> file on the extension page with the <strong>Download</strong> button.</p>
      </li>
    </ul>
  </li>
  <li>Open a Terminal</li>
  <li>Switch to the folder <em>theia</em></li>
  <li>Execute <code class="language-plaintext highlighter-rouge">npm run prepare</code><br />
You could also run <code class="language-plaintext highlighter-rouge">npm install</code> which triggers the <code class="language-plaintext highlighter-rouge">prepare</code> script while processing, or switch to the <em>browser-app</em> and <em>electron-app</em> folders directly and only call <code class="language-plaintext highlighter-rouge">npm run download:plugins</code>.</li>
</ul>

<p>If you now start the Theia application and open the <em>Plugins</em> view via <em>Menu - View - Plugins</em> you should see the <em>angular-extension</em>, the <em>react-extension</em>, the <em>vscode-extension</em> and the <em>vscode-eclipse-keybindings</em> listed in the <em>PLUGINS</em> view.</p>

<p>At this time we <strong>pre-installed</strong> extensions. A user is not able to change this.
And users are still not able to install and uninstall additional extensions at runtime to customize their own instance of the application.
To enable this feature you need to add <code class="language-plaintext highlighter-rouge">@theia/vsx-registry</code> as a dependency.</p>

<ul>
  <li>Open a Terminal</li>
  <li>
    <p>Switch to the folder <em>theia/browser-app</em></p>

    <ul>
      <li>
        <p>Add the dependency to <code class="language-plaintext highlighter-rouge">@theia/vsx-registry</code> via</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i @theia/vsx-registry
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Switch to the folder <em>theia/electron-app</em></p>

    <ul>
      <li>
        <p>Add the dependency to <code class="language-plaintext highlighter-rouge">@theia/vsx-registry</code> via</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i @theia/vsx-registry
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Rebuild the Theia applications</li>
</ul>

<p>If you now start the Theia application and open the <em>Extensions</em> view via <em>Menu - View - Extensions</em> you you are able to install additional extensions to your Theia instance. You might need to refresh the browser window.</p>

<p><em><strong>Note:</strong></em><br />
The pre-installed extensions still can not be uninstalled. This is intended, as you as a developer decided that the pre-installed extensions are required to make the application work as designed.
This should not be modifiable by a customer.</p>

<p>The above steps and further information are also described in</p>

<ul>
  <li><a href="https://theia-ide.org/docs/authoring_vscode_extensions/#consuming-vs-code-extensions">Consuming VS Code Extensions</a></li>
  <li><a href="https://theia-ide.org/docs/user_install_vscode_extensions/">Installing VS Code Extensions in Theia</a></li>
</ul>

<h3 id="built-in-visual-studio-code-extensions">Built-in Visual Studio Code Extensions</h3>

<p>Visual Studio Code contains multiple pre-integrated extensions that make up several base functionalities of Visual Studio Code. To get an idea about those <em>builtin</em> extension, you can open the <em>Extensions</em> view in Visual Studio Code and type <code class="language-plaintext highlighter-rouge">@builtin</code> in the search field.</p>

<p>Those extensions are part of the <a href="https://github.com/microsoft/vscode/tree/main/extensions">vscode repository</a>.
To make them usable in Theia, there is the <a href="https://github.com/eclipse-theia/vscode-builtin-extensions">vscode-builtin-extensions repository</a>.
It packages those <em>builtins</em> as <em>.vsix</em> and publishes them. In the past the publishing was done via Open VSX. But Open VSX changed its ownership behavior, which lead to the current temporary decision by the Theia team to publish the <em>builtin</em> extensions via <a href="https://github.com/eclipse-theia/vscode-builtin-extensions/releases">GitHub releases</a>.</p>

<p>You can find further information about the temporary decision via:</p>

<ul>
  <li><a href="https://github.com/eclipse-theia/theia/pull/16774">eclipse-theia/theia#16774</a></li>
  <li><a href="https://github.com/eclipse-theia/vscode-builtin-extensions/issues/139">eclipse-theia/vscode-builtin-extensions#139</a></li>
</ul>

<p><em><strong>Note:</strong></em><br />
Since the built-in extensions in version 1.108.2, the release contains the single built-in extensions as <em>.vsix</em> files and the archive of the built-in extension pack. The latest version of single built-in extensions in Open VSX is currently 1.95.3.</p>

<p>For example, to extend the Theia application for Typescript support, the following two built-in extensions need to be added:</p>

<ul>
  <li><a href="https://open-vsx.org/extension/vscode/typescript">TypeScript Language Basics (built-in)</a></li>
  <li>
    <p><a href="https://open-vsx.org/extension/vscode/typescript-language-features">TypeScript and JavaScript Language Features (built-in)</a></p>
  </li>
  <li>
    <p>Open the file <em>theia/browser-app/package.json</em></p>

    <ul>
      <li>
        <p>Add the following entries in the <code class="language-plaintext highlighter-rouge">theiaPlugins</code> section</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"theiaPlugins"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"eclipse-keybindings"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://open-vsx.org/api/alphabotsec/vscode-eclipse-keybindings/0.16.1/file/alphabotsec.vscode-eclipse-keybindings-0.16.1.vsix"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"vscode-typescript"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.typescript.vsix"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"vscode-typescript-language-features"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.typescript-language-features.vsix"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Open the file <em>theia/electron-app/package.json</em></p>

    <ul>
      <li>
        <p>Add the following entries in the <code class="language-plaintext highlighter-rouge">theiaPlugins</code> section</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"theiaPlugins"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"eclipse-keybindings"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://open-vsx.org/api/alphabotsec/vscode-eclipse-keybindings/0.16.1/file/alphabotsec.vscode-eclipse-keybindings-0.16.1.vsix"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"vscode-typescript"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.typescript.vsix"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"vscode-typescript-language-features"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.typescript-language-features.vsix"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Open a Terminal</li>
  <li>Switch to the folder <em>theia/browser-app</em></li>
  <li>Execute <code class="language-plaintext highlighter-rouge">npm run download:plugins</code><br />
As the <em>browser-app</em> and the <em>electron-app</em> share the same <em>plugins</em> folder in our setup, we only need to execute this for one app.</li>
</ul>

<p>Start the Theia application and verify that syntax highlighting and code completion works for typescript files.
This can be done for example by opening the workspace folder you are currently working on in the Theia Application.</p>

<h4 id="built-in-extension-pack">Built-in Extension-Pack</h4>

<p>To create a Theia application that provides the same functionality as vanilla Visual Studio Code, you can also install all built-in extensions by using the <a href="https://open-vsx.org/extension/eclipse-theia/builtin-extension-pack">builtin-extension-pack</a>.
In this case you should exclude the extensions that are not working in Theia via the <code class="language-plaintext highlighter-rouge">theiaPluginsExcludeIds</code> setting the the <em>package.json</em>.</p>

<p>The following snippet is quite the same as in the <a href="https://github.com/eclipse-theia/theia-ide/blob/941edd0e03bc6459a7d526d802bb0aac9d38e349/package.json#L66-L78"><em>package.json</em> of the Theia IDE</a> (without the Java Visual Studio Code Extensions):</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"theiaPlugins"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"vscode-builtin-extensions"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode-builtin-extensions-1.108.2.tar.gz"</span><span class="w">
  </span><span class="p">}</span><span class="err">,</span><span class="w">
  </span><span class="nl">"theiaPluginsExcludeIds"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"ms-vscode.js-debug-companion"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"VisualStudioExptTeam.vscodeintellicode"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"vscode.extension-editing"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"vscode.github"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"vscode.github-authentication"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"vscode.microsoft-authentication"</span><span class="w">
  </span><span class="p">]</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h2 id="theia-customization">Theia Customization</h2>

<p>When you create a Visual Studio Code Extension, the intention is to add a new functionality to Visual Studio Code. Apart from adding new functionality, there is not much you can do to customize Visual Studio Code. Of course you can customize the color theme, which I will show later, and you can change some settings like key bindings etc. But you can for example not remove some basic functionality in Visual Studio Code or replace it with a customized variant. In Theia it is possible to remove or replace existing features to create a custom Theia Application.</p>

<p>In the following sections I will describe the different ways to customize a Theia Application. It is not intended as a recommendation what to do, more a description on how to customize it by example.</p>

<h3 id="configuration">Configuration</h3>

<p>One first step in customizing a Theia Application is to configure it via <em>Application Properties</em> and <em>Default Preferences</em>. Further information can be found in <a href="https://github.com/eclipse-theia/theia/tree/master/dev-packages/cli#configure">ECLIPSE THEIA - CLI - Configure</a>.</p>

<p>Let`s configure a custom application name for the Theia Application.</p>

<ul>
  <li>Open <em>theia/browser-app/package.json</em></li>
  <li>
    <p>Add a <code class="language-plaintext highlighter-rouge">frontend</code> configuration to <code class="language-plaintext highlighter-rouge">theia</code> if it does not exist yet</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"theia"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"browser"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"frontend"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"config"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"applicationName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Custom Theia Application"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"warnOnPotentiallyInsecureHostPattern"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<p>After rebuilding and starting the browser application, you will see the application name in the browser tab.</p>

<h3 id="theia-extension-for-customization">Theia Extension for customization</h3>

<p>If your goal is to create a Theia Application with custom functionality, the recommended way is to create a <em>Theia Extension</em>.
This gives you full access to Theia internals with almost no limitations in terms of accessible API.
Theia Extensions are installed at compile time and not intended to be installed at runtime in an existing Theia application.
And of course a Theia Extension can not be installed into Visual Studio Code, unlike the Visual Studio Code Extension that we created in the previous blog post.</p>

<p>A more detailed comparison of the different extension mechanisms that are possible with Theia is described in <a href="https://theia-ide.org/docs/extensions/">Theia - Extensions and Plugins</a>.</p>

<p>In the following section we create a Theia Extension that is used to customize the app.</p>

<ul>
  <li>Open a Terminal</li>
  <li>Switch to the <em>theia</em> folder</li>
  <li>
    <p>Create a new Theia extension by using the <code class="language-plaintext highlighter-rouge">theia-extension</code> generator</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yo theia-extension
? The extension's type Empty
? The extension's name theia-customization
</code></pre></div>    </div>
  </li>
  <li>Select <code class="language-plaintext highlighter-rouge">do not overwrite</code> (n) for every file that comes up with a conflict. It would otherwise remove all changes we applied before.</li>
</ul>

<p><em><strong>Note:</strong></em><br />
If you have already committed the previous work, you could also let the generator overwrite those files and merge the changes manually of course.</p>

<ul>
  <li>
    <p>Make the necessary modifications manually</p>

    <ul>
      <li>
        <p>Update the <em>theia/package.json</em> and add the new <em>theia-customization</em> module to the <code class="language-plaintext highlighter-rouge">workspaces</code> section</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"workspaces"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"theia-customization"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"browser-app"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"electron-app"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p>Add the <em>theia-customization</em> module as a dependency to the <em>browser-app</em> and the <em>electron-app</em></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="err">...</span><span class="p">,</span><span class="w">
    </span><span class="nl">"theia-customization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.0.0"</span><span class="w">
  </span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>Optional: Update the <em>theia-customization/package.json</em>
        <ul>
          <li>Replace <code class="language-plaintext highlighter-rouge">yarn</code> in the <code class="language-plaintext highlighter-rouge">scripts</code> section with <code class="language-plaintext highlighter-rouge">npm</code><br />
This is only necessary if you use a <code class="language-plaintext highlighter-rouge">generator-theia-extension</code> &lt; 0.1.45</li>
        </ul>
      </li>
      <li>Run <code class="language-plaintext highlighter-rouge">npm install</code> to update the project after the manual modifications</li>
    </ul>
  </li>
</ul>

<p>Now you can create a customization, like adding a banner on top of the application shell or removing things from the application, like the Terminal.</p>

<h3 id="replace-an-existing-theia-implementation">Replace an existing Theia implementation</h3>

<p>The Theia framework uses dependency injection to wire up services and contribution points. This is described in more detail in <a href="https://theia-ide.org/docs/services_and_contributions/">Services and Contributions</a>. Because of this architectural design it is easy to contribute customizations that replace existing Theia implementations.</p>

<p>In the following section we will replace the Theia <code class="language-plaintext highlighter-rouge">ApplicationShell</code> with a custom implementation that adds a banner on top.</p>

<ul>
  <li>Create a new folder <em>theia/theia-customization/src/browser/style</em></li>
  <li>
    <p>Create a new file <em>theia/theia-customization/src/browser/style/index.css</em></p>

    <div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="py">--brand-color1</span><span class="p">:</span> <span class="nf">rgb</span><span class="p">(</span><span class="m">53</span><span class="p">,</span> <span class="m">52</span><span class="p">,</span> <span class="m">52</span><span class="p">);</span>
  <span class="py">--brand-color2</span><span class="p">:</span> <span class="nf">rgb</span><span class="p">(</span><span class="m">218</span><span class="p">,</span> <span class="m">122</span><span class="p">,</span> <span class="m">8</span><span class="p">);</span>
<span class="p">}</span>

<span class="nf">#theia-app-shell</span> <span class="p">{</span>
  <span class="nl">top</span><span class="p">:</span> <span class="m">2.8rem</span><span class="p">;</span>
<span class="p">}</span>

<span class="nf">#example-header</span> <span class="p">{</span>
  <span class="nl">position</span><span class="p">:</span> <span class="nb">fixed</span><span class="p">;</span>
  <span class="nl">top</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">left</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">right</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">height</span><span class="p">:</span> <span class="m">2.8rem</span><span class="p">;</span>
  <span class="nl">display</span><span class="p">:</span> <span class="nb">flex</span><span class="p">;</span>
  <span class="nl">justify-content</span><span class="p">:</span> <span class="nb">space-between</span><span class="p">;</span>
  <span class="nl">align-items</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span>
  <span class="nl">background</span><span class="p">:</span> <span class="nf">linear-gradient</span><span class="p">(</span>
    <span class="n">to</span> <span class="nb">right</span><span class="p">,</span>
    <span class="nf">var</span><span class="p">(</span><span class="l">--brand-color1</span><span class="p">),</span>
    <span class="nf">var</span><span class="p">(</span><span class="l">--brand-color2</span><span class="p">)</span>
  <span class="p">);</span>
  <span class="nl">color</span><span class="p">:</span> <span class="nx">white</span><span class="p">;</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">0</span> <span class="m">1rem</span><span class="p">;</span>
  <span class="nl">-webkit-app-region</span><span class="p">:</span> <span class="n">drag</span><span class="p">;</span>
<span class="p">}</span>

<span class="nf">#example-app-title</span> <span class="p">{</span>
  <span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">font-size</span><span class="p">:</span> <span class="m">1.33rem</span><span class="p">;</span>
  <span class="nl">font-weight</span><span class="p">:</span> <span class="m">400</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>Open the file <em>theia/theia-customization/src/theia-customization-contribution.ts</em></li>
  <li>
    <p>Replace the content of the file with the following content:</p>

    <ul>
      <li>Import the <em>index.css</em> file</li>
      <li>Extend <code class="language-plaintext highlighter-rouge">ApplicationShell</code></li>
      <li>Add a banner on top of the Theia Application Shell:</li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">ApplicationShell</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/browser</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">injectable</span><span class="p">,</span> <span class="nx">postConstruct</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>

<span class="k">import</span> <span class="dl">"</span><span class="s2">../../src/browser/style/index.css</span><span class="dl">"</span><span class="p">;</span>

<span class="p">@</span><span class="nd">injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">TheiaCustomizationContribution</span> <span class="kd">extends</span> <span class="nc">ApplicationShell</span> <span class="p">{</span>
  <span class="p">@</span><span class="nd">postConstruct</span><span class="p">()</span>
  <span class="k">protected</span> <span class="nf">init</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">super</span><span class="p">.</span><span class="nf">init</span><span class="p">();</span>

    <span class="kd">const</span> <span class="nx">div</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">div</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">div</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">example-header</span><span class="dl">"</span><span class="p">;</span>

    <span class="kd">const</span> <span class="nx">h1</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">h1</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">h1</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">example-app-title</span><span class="dl">"</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">appTitle</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createTextNode</span><span class="p">(</span><span class="dl">"</span><span class="s2">My Custom Theia Application</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">h1</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nx">appTitle</span><span class="p">);</span>
    <span class="nx">div</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nx">h1</span><span class="p">);</span>

    <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">prepend</span><span class="p">(</span><span class="nx">div</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Open the file <em>theia/theia-customization/src/theia-customization-frontend-module.ts</em></p>

    <ul>
      <li><strong>Bind</strong> the <code class="language-plaintext highlighter-rouge">TheiaCustomizationContribution</code> in the <code class="language-plaintext highlighter-rouge">ContainerModule</code> instance</li>
      <li><strong>Rebind</strong> the <code class="language-plaintext highlighter-rouge">ApplicationShell</code> with the <code class="language-plaintext highlighter-rouge">TheiaCustomizationContribution</code><br />
in the <code class="language-plaintext highlighter-rouge">ContainerModule</code> instance</li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">ContainerModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TheiaCustomizationContribution</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./theia-customization-contribution</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ApplicationShell</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/browser</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="k">new</span> <span class="nc">ContainerModule</span><span class="p">((</span><span class="nx">bind</span><span class="p">,</span> <span class="nx">unbind</span><span class="p">,</span> <span class="nx">isBound</span><span class="p">,</span> <span class="nx">rebind</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">bind</span><span class="p">(</span><span class="nx">TheiaCustomizationContribution</span><span class="p">).</span><span class="nf">toSelf</span><span class="p">().</span><span class="nf">inSingletonScope</span><span class="p">();</span>
  <span class="nf">rebind</span><span class="p">(</span><span class="nx">ApplicationShell</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">to</span><span class="p">(</span><span class="nx">TheiaCustomizationContribution</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">inSingletonScope</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>To make the integration of the new package more convenient in development, we add and modify some scripts in the theia top level <em>package.json</em>:</p>

<ul>
  <li>Open the file <em>theia/package.json</em></li>
  <li>Add a new <code class="language-plaintext highlighter-rouge">script</code> <code class="language-plaintext highlighter-rouge">build:customization</code> to trigger the build of the new Theia extension from the top level folder</li>
  <li>
    <p>Call that script in the build scripts of browser and electron</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"build:customization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix theia-customization run build"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"build:browser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run build:customization &amp;&amp; npm --prefix browser-app run bundle"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"build:electron"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run build:customization &amp;&amp; npm --prefix electron-app run bundle"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"prepare"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lerna run prepare"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"postinstall"</span><span class="p">:</span><span class="w"> </span><span class="s2">"theia check:theia-version"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"start:browser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix browser-app start"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"start:electron"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix electron-app start"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"watch:browser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lerna run --parallel watch --ignore electron-app"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"watch:electron"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lerna run --parallel watch --ignore browser-app"</span><span class="w">
  </span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>Open a <strong>Terminal</strong></li>
  <li>Switch to the <em>theia</em> folder</li>
  <li>Run <code class="language-plaintext highlighter-rouge">npm i</code> to get the dependencies correctly resolved.<br />
This also triggers a clean build via the <code class="language-plaintext highlighter-rouge">prepare</code> script.</li>
</ul>

<p>If you now start the Theia application, the Theia Application will show a banner on top.</p>

<p>Further information on that topic:</p>

<ul>
  <li><a href="https://theia-ide.org/docs/authoring_extensions/">Authoring Theia Extensions</a></li>
  <li><a href="https://theia-ide.org/docs/blueprint_documentation/">Extending/Adopting the Theia IDE</a></li>
  <li><a href="https://theia-ide.org/docs/services_and_contributions/">Services and Contributions</a></li>
  <li><a href="https://theia-ide.org/docs/frontend_application_contribution/">Frontend Application Contributions</a></li>
  <li><a href="https://theia-ide.org/docs/backend_application_contribution/">Backend Application Contributions</a></li>
</ul>

<h3 id="remove-default-theia-functionality">Remove Default Theia Functionality</h3>

<p>The most obvious way to remove a default Theia functionality is of course to remove the Theia package from the <code class="language-plaintext highlighter-rouge">dependencies</code> of the application project. But sometimes a dependency sneaks in as a transitive dependency, which you can not simply remove. For example <code class="language-plaintext highlighter-rouge">@theia/plugin-ext</code> has a lot of dependencies to ensure that installed Visual Studio Code extensions work correctly in Theia.</p>

<p>Let’s try to remove the Terminal view from the application, which comes in as a transitive dependency of <code class="language-plaintext highlighter-rouge">@theia/plugin-ext</code> for example.</p>

<ul>
  <li>
    <p>Create a new file <em>theia/theia-customization/src/browser/theia-customization-filter-contribution.ts</em></p>

    <ul>
      <li>Create a new class <code class="language-plaintext highlighter-rouge">TheiaCustomizationFilterContribution</code> that extends <code class="language-plaintext highlighter-rouge">FilterContribution</code></li>
      <li>Implement the method <code class="language-plaintext highlighter-rouge">registerContributionFilters(registry: ContributionFilterRegistry)</code> and register a filter that filters out the <code class="language-plaintext highlighter-rouge">TerminalFrontendContribution</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">FilterContribution</span><span class="p">,</span>
  <span class="nx">ContributionFilterRegistry</span><span class="p">,</span>
  <span class="nx">Filter</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/common</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>

<span class="p">@</span><span class="nd">injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">TheiaCustomizationFilterContribution</span>
  <span class="k">implements</span> <span class="nx">FilterContribution</span>
<span class="p">{</span>
  <span class="nf">registerContributionFilters</span><span class="p">(</span><span class="nx">registry</span><span class="p">:</span> <span class="nx">ContributionFilterRegistry</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="nx">registry</span><span class="p">.</span><span class="nf">addFilters</span><span class="p">(</span><span class="dl">"</span><span class="s2">*</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span>
      <span class="c1">// Filter out the main outline contribution at:</span>
      <span class="c1">// https://github.com/eclipse-theia/theia/blob/master/packages/terminal/src/browser/terminal-frontend-contribution.ts</span>
      <span class="nf">filterClassName</span><span class="p">((</span><span class="nx">name</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">name</span> <span class="o">!==</span> <span class="dl">"</span><span class="s2">TerminalFrontendContribution</span><span class="dl">"</span><span class="p">),</span>
    <span class="p">]);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">filterClassName</span><span class="p">(</span><span class="nx">filter</span><span class="p">:</span> <span class="nx">Filter</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span><span class="p">):</span> <span class="nx">Filter</span><span class="o">&lt;</span><span class="nb">Object</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="k">return </span><span class="p">(</span><span class="nx">object</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">className</span> <span class="o">=</span> <span class="nx">object</span><span class="p">?.</span><span class="kd">constructor</span><span class="p">?.</span><span class="nx">name</span><span class="p">;</span>
    <span class="k">return</span> <span class="nx">className</span> <span class="p">?</span> <span class="nf">filter</span><span class="p">(</span><span class="nx">className</span><span class="p">)</span> <span class="p">:</span> <span class="kc">false</span><span class="p">;</span>
  <span class="p">};</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Create a new file <em>theia/theia-customization/src/browser/theia-customization-cleanup-contribution.ts</em></p>

    <ul>
      <li>Create a new class <code class="language-plaintext highlighter-rouge">CleanupFrontendContribution</code> that implements <code class="language-plaintext highlighter-rouge">FrontendApplicationContribution</code></li>
      <li>Implement the lifecycle method <code class="language-plaintext highlighter-rouge">onDidInitializeLayout(app: FrontendApplication)</code> and dispose any terminal widget<br />
This is a way to ensure that a widget that is opened by default somehow is closed before the user sees anything.</li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">inversify</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
  <span class="nx">FrontendApplicationContribution</span><span class="p">,</span>
  <span class="nx">FrontendApplication</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/browser</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">MaybePromise</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/common/types</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Widget</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/browser/widgets</span><span class="dl">"</span><span class="p">;</span>

<span class="p">@</span><span class="nd">injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">CleanupFrontendContribution</span>
  <span class="k">implements</span> <span class="nx">FrontendApplicationContribution</span>
<span class="p">{</span>
  <span class="cm">/**
   * Called after the application shell has been attached in case there is no previous workbench layout state.
   * Should return a promise if it runs asynchronously.
   */</span>
  <span class="nf">onDidInitializeLayout</span><span class="p">(</span><span class="nx">app</span><span class="p">:</span> <span class="nx">FrontendApplication</span><span class="p">):</span> <span class="nx">MaybePromise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// Remove unused widgets</span>
    <span class="nx">app</span><span class="p">.</span><span class="nx">shell</span><span class="p">.</span><span class="nx">widgets</span><span class="p">.</span><span class="nf">forEach</span><span class="p">((</span><span class="na">widget</span><span class="p">:</span> <span class="nx">Widget</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">widget</span><span class="p">.</span><span class="nx">id</span><span class="p">.</span><span class="nf">startsWith</span><span class="p">(</span><span class="dl">"</span><span class="s2">terminal</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
        <span class="nx">widget</span><span class="p">.</span><span class="nf">dispose</span><span class="p">();</span>
      <span class="p">}</span>
    <span class="p">});</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Open the file <em>theia/theia-customization/src/theia-customization-frontend-module.ts</em></p>

    <ul>
      <li><strong>Bind</strong> the <code class="language-plaintext highlighter-rouge">TheiaCustomizationFilterContribution</code> and the <code class="language-plaintext highlighter-rouge">CleanupFrontendContribution</code> in the <code class="language-plaintext highlighter-rouge">ContainerModule</code> instance</li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">ContainerModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/shared/inversify</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TheiaCustomizationContribution</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./theia-customization-contribution</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
  <span class="nx">ApplicationShell</span><span class="p">,</span>
  <span class="nx">FrontendApplicationContribution</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core/lib/browser</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TheiaCustomizationFilterContribution</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./theia-customization-filter-contribution</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">bindContribution</span><span class="p">,</span> <span class="nx">FilterContribution</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@theia/core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CleanupFrontendContribution</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./theia-customization-cleanup-contribution</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="k">new</span> <span class="nc">ContainerModule</span><span class="p">((</span><span class="nx">bind</span><span class="p">,</span> <span class="nx">unbind</span><span class="p">,</span> <span class="nx">isBound</span><span class="p">,</span> <span class="nx">rebind</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">bind</span><span class="p">(</span><span class="nx">TheiaCustomizationContribution</span><span class="p">).</span><span class="nf">toSelf</span><span class="p">().</span><span class="nf">inSingletonScope</span><span class="p">();</span>
  <span class="nf">rebind</span><span class="p">(</span><span class="nx">ApplicationShell</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">to</span><span class="p">(</span><span class="nx">TheiaCustomizationContribution</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">inSingletonScope</span><span class="p">();</span>

  <span class="nf">bind</span><span class="p">(</span><span class="nx">TheiaCustomizationFilterContribution</span><span class="p">).</span><span class="nf">toSelf</span><span class="p">().</span><span class="nf">inSingletonScope</span><span class="p">();</span>
  <span class="nf">bindContribution</span><span class="p">(</span><span class="nx">bind</span><span class="p">,</span> <span class="nx">TheiaCustomizationFilterContribution</span><span class="p">,</span> <span class="p">[</span>
    <span class="nx">FilterContribution</span><span class="p">,</span>
  <span class="p">]);</span>

  <span class="nf">bind</span><span class="p">(</span><span class="nx">CleanupFrontendContribution</span><span class="p">).</span><span class="nf">toSelf</span><span class="p">().</span><span class="nf">inSingletonScope</span><span class="p">();</span>
  <span class="nf">bind</span><span class="p">(</span><span class="nx">FrontendApplicationContribution</span><span class="p">).</span><span class="nf">toService</span><span class="p">(</span>
    <span class="nx">CleanupFrontendContribution</span>
  <span class="p">);</span>
<span class="p">});</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>If you now build and start the Theia application, the Theia Application should not show any previously opened Terminal views and you should not be able to open a new Terminal.</p>

<p>Further information can be found here:</p>

<ul>
  <li><a href="https://theia-ide.org/docs/contribution_filter/">Contribution Filter</a></li>
</ul>

<h3 id="color-themes">Color Themes</h3>

<p>When you create a custom application you typically want to apply your color theme, e.g. according to your corporate style guide. Visual Studio Code offers a way to generate and publish custom color themes, and Theia adopted the usage of such Visual Studio Code themes.</p>

<p>Create a new color theme:</p>

<ul>
  <li>In your Visual Studio Code instance, switch to the theme to use a base for our custom color theme
    <ul>
      <li>Press F1</li>
      <li>Enter <em>theme</em></li>
      <li>Select <em>Preferences: Color Theme</em></li>
      <li>Select for example <em>Light Modern</em></li>
    </ul>
  </li>
  <li>Modify the theme via settings
    <ul>
      <li>Press F1</li>
      <li>Enter <em>settings</em></li>
      <li>Select <em>Preferences: Open User Settings (JSON)</em></li>
      <li>Add the property <code class="language-plaintext highlighter-rouge">workbench.colorCustomizations</code> and some customizations that should be applied, for example the color of the active activity bar icons and the color of the active status bar.
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.colorCustomizations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"statusBar.background"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#da7a08"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"activityBar.foreground"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#da7a08"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"activityBar.activeBorder"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#da7a08"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Generate a theme file
    <ul>
      <li>Press F1</li>
      <li>Enter <em>theme</em></li>
      <li>Select <em>Developer: Generate Color Theme from Current Settings</em><br />
This opens a new editor with the content of the generated color theme.</li>
    </ul>
  </li>
  <li>
    <p>Generate a new Visual Studio Code Theme Extension<br />
As the Theme Extension could generally be used by multiple Theia applications, and even in Visual Studio Code, we create it on the same folder level as the <em>theia</em> folder.</p>

    <ul>
      <li>Open a <strong>Terminal</strong></li>
      <li>Ensure that you are on the top level of the repository.</li>
      <li>Create a new project using <code class="language-plaintext highlighter-rouge">generator-code</code>
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yo code
</code></pre></div>        </div>
      </li>
      <li>
        <p>Answer the questions of the wizard for example like shown below:</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>? What type of extension do you want to create? New Color Theme
? Do you want to import or convert an existing TextMate color theme? No, start fresh
? What's the name of your extension? custom-theme
? What's the identifier of your extension? custom-theme
? What's the description of your extension?
? What's the name of your theme shown to the user? Custom Theme (Light)
? Select a base theme: Light
? Initialize a git repository? No

? Do you want to open the new folder with Visual Studio Code? Skip
</code></pre></div>        </div>
      </li>
      <li>Delete the folder <em>custom-theme/.vscode</em></li>
      <li>Open the file <em>custom-theme/themes/Custom Theme (Light)-color-theme.json</em>
        <ul>
          <li>Copy the content of the previously generated color theme (the <em>Untitled-1</em> file) and replace the content of <em>custom-theme/themes/Custom Theme (Light)-color-theme.json</em> with the copied content.</li>
          <li>Add the <code class="language-plaintext highlighter-rouge">name</code> property, which is missing in the generated color theme
            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Custom Theme (Light)"</span><span class="p">,</span><span class="w">
  </span><span class="err">...</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>            </div>
          </li>
        </ul>
      </li>
      <li>
        <p>Pre-install the theme extension to the Theia application</p>

        <ul>
          <li>Open a <strong>Terminal</strong></li>
          <li>Switch to the <em>custom-theme</em> directory</li>
          <li>
            <p>Add the following packages as <code class="language-plaintext highlighter-rouge">devDependencies</code></p>

            <ul>
              <li>
                <p><a href="https://www.npmjs.com/package/rimraf"><code class="language-plaintext highlighter-rouge">rimraf</code></a> to delete the directory</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D rimraf
</code></pre></div>                </div>
              </li>
              <li>
                <p><a href="https://www.npmjs.com/package/symlink-dir"><code class="language-plaintext highlighter-rouge">symlink-dir</code></a> to create a symbolic link</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D symlink-dir
</code></pre></div>                </div>
              </li>
              <li>
                <p><a href="https://www.npmjs.com/package/mkdirp"><code class="language-plaintext highlighter-rouge">mkdirp</code></a> to create the directory</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D mkdirp
</code></pre></div>                </div>
              </li>
              <li>
                <p><a href="https://www.npmjs.com/package/run-script-os"><code class="language-plaintext highlighter-rouge">run-script-os</code></a> to execute the unzip operation in an OS dependent way</p>

                <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D run-script-os
</code></pre></div>                </div>
              </li>
            </ul>
          </li>
          <li>
            <p>Open the file <em>custom-theme/package.json</em> and add the following <code class="language-plaintext highlighter-rouge">scripts</code></p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"prepare"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run clean &amp;&amp; npm run symlink"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"clean"</span><span class="p">:</span><span class="w"> </span><span class="s2">"rimraf ../theia/plugins/custom-theme"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"symlink"</span><span class="p">:</span><span class="w"> </span><span class="s2">"symlink-dir . ../theia/plugins/custom-theme"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"package"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vsce package --allow-missing-repository"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"theia:prepare-app"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run package &amp;&amp; npm run theia:extract-vsix"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"theia:extract-vsix"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run clean &amp;&amp; npm run theia:prepare &amp;&amp; npm run unzip"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"theia:prepare"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mkdirp ../theia/plugins/custom-theme"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"unzip"</span><span class="p">:</span><span class="w"> </span><span class="s2">"run-script-os"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"unzip:windows"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tar -xf custom-theme-0.0.1.vsix -C ../theia/plugins/custom-theme"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"unzip:nix"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unzip custom-theme-0.0.1.vsix -d ../theia/plugins/custom-theme"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>            </div>
          </li>
          <li>
            <p>Execute the <code class="language-plaintext highlighter-rouge">prepare</code> script</p>

            <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run prepare
</code></pre></div>            </div>
          </li>
          <li>
            <p>Verify that the symbolic link to the <em>custom-theme</em> folder is created in <em>theia/plugins</em></p>

            <p><em><strong>Note:</strong></em><br />
If you execute <code class="language-plaintext highlighter-rouge">npm run theia:prepare-app</code> on the commandline of the <em>custom-theme</em> project folder the project is built, packaged to a <em>.vsix</em> file which is then extracted to the <em>theia/plugins</em> folder. This mechanism will be used for the containerization of the Theia Application.</p>
          </li>
        </ul>
      </li>
      <li>Enable the custom theme on startup via predefined preferences
        <ul>
          <li>Open the file <em>theia/browser-app/package.json</em></li>
          <li>Add the <code class="language-plaintext highlighter-rouge">theia/frontend/config/preferences</code> property to set the <code class="language-plaintext highlighter-rouge">workbench.colorTheme</code>
            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"theia"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"browser"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"frontend"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"config"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"applicationName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Custom Theia Application"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"warnOnPotentiallyInsecureHostPattern"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
      </span><span class="nl">"preferences"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"workbench.colorTheme"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Custom Theme (Light)"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>            </div>
          </li>
          <li>Open the file <em>theia/electron-app/package.json</em></li>
          <li>Add the <code class="language-plaintext highlighter-rouge">theia/frontend/config/preferences</code> property to set the <code class="language-plaintext highlighter-rouge">workbench.colorTheme</code>
            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"theia"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"electron"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"frontend"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"config"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"applicationName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Custom Theia Application"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"preferences"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"workbench.colorTheme"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Custom Theme (Light)"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>            </div>
          </li>
        </ul>
      </li>
      <li>Build and start the Theia Application and verify that the custom theme is applied on startup</li>
    </ul>
  </li>
</ul>

<p>Further information about Visual Studio Code Color Themes can be found here:</p>

<ul>
  <li><a href="https://code.visualstudio.com/api/extension-guides/color-theme">Color Theme</a></li>
  <li><a href="https://code.visualstudio.com/api/references/theme-color">Theme Color Reference</a></li>
</ul>

<h2 id="containerizing-the-theia-browser-application">Containerizing the Theia Browser Application</h2>

<p>To containerize the Theia application, you need to setup a Dockerfile with the necessary dependencies.
These are described in <a href="https://github.com/eclipse-theia/theia/blob/master/doc/Developing.md">How to build Theia and the example applications</a>.</p>

<p>It is also recommended to setup a multi-stage build, where the first step is the build of the application and the second step is the image creation with only the build result.
This keeps the final image as small as possible.</p>

<p>For this task we need to ensure that the Visual Studio Code Extensions are added to the <em>plugins</em> folder in the extracted <em>.vsix</em> way, as the symbolic links would not work in the Theia Application container if you don’t copy the extension projects folders also to the final container image.</p>

<ul>
  <li>
    <p>Open the <em>package.json</em> in the repository root</p>

    <ul>
      <li>Extend the <code class="language-plaintext highlighter-rouge">install:all</code> script to run <code class="language-plaintext highlighter-rouge">npm install</code> on the <em>custom-theme</em> project</li>
      <li>Add a new script <code class="language-plaintext highlighter-rouge">build:all:browser</code> that calls <code class="language-plaintext highlighter-rouge">theia:prepare-app</code> for each Visual Studio Code Extension and then builds the Theia Browser Application.</li>
    </ul>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vscode-theia-cookbook"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"private"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"install:all"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cd vscode-extension &amp;&amp; npm install &amp;&amp; cd ../angular-extension &amp;&amp; npm run install:all &amp;&amp; cd ../react-extension &amp;&amp; npm run install:all &amp;&amp; cd ../custom-theme &amp;&amp; npm install &amp;&amp; cd ../theia &amp;&amp; npm install"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"build:all:browser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cd vscode-extension &amp;&amp; npm run theia:prepare-app &amp;&amp; cd ../angular-extension &amp;&amp; npm run theia:prepare-app &amp;&amp; cd ../react-extension &amp;&amp; npm run theia:prepare-app &amp;&amp; cd ../custom-theme &amp;&amp; npm run theia:prepare-app &amp;&amp; cd ../theia &amp;&amp; npm run build:browser"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Create a <em>Dockerfile</em> in the repository root</p>

    <div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Builder stage</span>
<span class="k">ARG</span><span class="s"> NODE_VERSION=20</span>
<span class="k">FROM</span><span class="w"> </span><span class="s">node:${NODE_VERSION}-bullseye</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">build</span>

<span class="c"># install required tools to build the application</span>
<span class="k">RUN </span>apt-get update <span class="o">&amp;&amp;</span> <span class="nb">export </span><span class="nv">DEBIAN_FRONTEND</span><span class="o">=</span>noninteractive <span class="se">\
</span>    <span class="o">&amp;&amp;</span> apt-get <span class="nt">-y</span> <span class="nb">install</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>    make <span class="se">\
</span>    gcc <span class="se">\
</span>    pkg-config <span class="se">\
</span>    build-essential <span class="se">\
</span>    python3 <span class="se">\
</span>    software-properties-common <span class="se">\
</span>    libx11-dev <span class="se">\
</span>    libxkbfile-dev <span class="se">\
</span>    libsecret-1-dev <span class="se">\
</span>    libssl-dev

<span class="k">RUN </span>npm <span class="nb">install</span> <span class="nt">-g</span> @vscode/vsce @angular/cli

<span class="k">WORKDIR</span><span class="s"> /home</span>

<span class="c"># copy sources to the container</span>
<span class="k">COPY</span><span class="s"> ./angular-extension ./angular-extension</span>
<span class="k">COPY</span><span class="s"> ./react-extension ./react-extension</span>
<span class="k">COPY</span><span class="s"> ./vscode-extension ./vscode-extension</span>
<span class="k">COPY</span><span class="s"> ./custom-theme ./custom-theme</span>
<span class="k">COPY</span><span class="s"> ./theia ./theia</span>
<span class="k">COPY</span><span class="s"> package.json ./package.json</span>

<span class="c"># set the GITHUB_TOKEN environment variable</span>
<span class="c"># needed to avoid API rate limit when the build tries to download vscode-ripgrep</span>
<span class="c"># see https://github.com/microsoft/vscode/issues/28434</span>
<span class="k">ARG</span><span class="s"> GITHUB_TOKEN</span>
<span class="k">ENV</span><span class="s"> GITHUB_TOKEN=$GITHUB_TOKEN</span>

<span class="c"># run the build</span>
<span class="k">RUN </span>npm run <span class="nb">install</span>:all <span class="o">&amp;&amp;</span> <span class="se">\
</span>    npm run build:all:browser <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">cd </span>theia <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">rm </span>package-lock.json <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">rm</span> <span class="nt">-rf</span> node_modules <span class="k">**</span>/node_modules

<span class="c"># create the execution image</span>
<span class="k">FROM</span><span class="s"> node:${NODE_VERSION}-bullseye-slim</span>

<span class="c"># Create theia user and directories</span>
<span class="c"># Application will be copied to /home/theia</span>
<span class="c"># Default workspace is located at /home/project</span>
<span class="k">RUN </span>adduser <span class="nt">--system</span> <span class="nt">--group</span> theia
<span class="k">RUN </span><span class="nb">chmod </span>g+rw /home <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">mkdir</span> <span class="nt">-p</span> /home/project <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">chown</span> <span class="nt">-R</span> theia:theia /home/theia <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">chown</span> <span class="nt">-R</span> theia:theia /home/project<span class="p">;</span>

<span class="c"># Install required tools for application</span>
<span class="k">RUN </span>apt-get update <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="se">\
</span>    openssh-client <span class="se">\
</span>    openssh-server <span class="se">\
</span>    libsecret-1-0 <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get clean

<span class="k">ENV</span><span class="s"> HOME=/home/theia \</span>
    THEIA_DEFAULT_PLUGINS=local-dir:/home/theia/plugins \
    THEIA_WEBVIEW_EXTERNAL_ENDPOINT=""

<span class="k">WORKDIR</span><span class="s"> /home/theia</span>

<span class="c"># Copy application from build stage</span>
<span class="k">COPY</span><span class="s"> --from=build --chown=theia:theia /home/theia/browser-app /home/theia/browser-app</span>
<span class="k">COPY</span><span class="s"> --from=build --chown=theia:theia /home/theia/plugins /home/theia/plugins</span>

<span class="k">EXPOSE</span><span class="s"> 3000</span>

<span class="c"># Switch to Theia user</span>
<span class="k">USER</span><span class="s"> theia</span>

<span class="c"># Launch the backend application via node</span>
<span class="k">ENTRYPOINT</span><span class="s"> [ "node", "/home/theia/browser-app/lib/backend/main.js" ]</span>

<span class="c"># Arguments passed to the application</span>
<span class="k">CMD</span><span class="s"> [ "/home/project", "--hostname=0.0.0.0" ]</span>
</code></pre></div>    </div>

    <p>You can also have a look at the <a href="https://github.com/eclipse-theia/theia-ide/blob/master/browser.Dockerfile">Theia IDE Browser Dockerfile</a> for an example.</p>

    <p><em><strong>Note:</strong></em><br />
The configuration of the <code class="language-plaintext highlighter-rouge">GITHUB_TOKEN</code> is only necessary if you are hitting the GitHub API rate limit. This could happen because of the <code class="language-plaintext highlighter-rouge">vscode-ripgrep</code> dependency, which uses the GitHub API to download the native binary part of the library. See <a href="#possible-issues">Possible Issues</a> for further information.</p>
  </li>
  <li>
    <p>Create a <em>.dockerignore</em> file in the repository root to ensure that only the relevant parts for the build are copied to the image.<br />
This is especially necessary because of the native artifacts like GLIBC in the local build results, which does not need to be the same on the host and in the resulting container image.</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>**/dist/
**/build/
**/node_modules/
theia/**/lib
theia/plugins
theia/**/src-gen/
theia/electron-app
**/target/
yarn.lock
package-lock.json
gen-webpack*.*
**/*.vsix
</code></pre></div>    </div>
  </li>
  <li>
    <p>Ensure that you have Docker or Podman installed. In case you are using the Dev Container</p>

    <ul>
      <li>Open the file <em>.devcontainer/devcontainer.json</em></li>
      <li>Add the <em>docker-in-docker</em> feature</li>
    </ul>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"features"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="err">https://github.com/devcontainers/features/tree/main/src/docker-in-docker</span><span class="w">
  </span><span class="nl">"ghcr.io/devcontainers/features/docker-in-docker:2"</span><span class="p">:</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>

    <ul>
      <li>Rebuild the Dev Container to get the <em>docker-in-docker</em> feature installed</li>
    </ul>
  </li>
  <li>
    <p>Build the container image</p>

    <ul>
      <li>Open a <strong>Terminal</strong></li>
      <li>Execute the following command in the repository root folder</li>
    </ul>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker build -t theia_custom_application .
</code></pre></div>    </div>
  </li>
</ul>

<p>If a previous attempt failed and you want to a clean rebuild, use the <code class="language-plaintext highlighter-rouge">--no-cache</code> option.<br />
If the console output of the build process does not show the necessary detailed output, use the option <code class="language-plaintext highlighter-rouge">--progress=plain</code> to get the container output.</p>

<ul>
  <li>
    <p>Run the container either via:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -d --rm -p 3000:3000 --name theia_custom_application theia_custom_application
</code></pre></div>    </div>

    <p>or if you configured your WSL via <code class="language-plaintext highlighter-rouge">networkingMode=mirrored</code></p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -d --rm --network=host --name theia_custom_application theia_custom_application
</code></pre></div>    </div>
  </li>
  <li>
    <p>Connect to the running container in case of issues:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker exec -ti theia_custom_application /bin/bash
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="possible-issues">Possible issues</h3>

<p>There could be multiple issues in building the container image:</p>

<ul>
  <li>Build fails with error <code class="language-plaintext highlighter-rouge">Downloading ripgrep failed: Error: API rate limit exceeded</code><br />
The error is reported in https://github.com/microsoft/vscode/issues/28434. The explained workaround is to create and configure a <code class="language-plaintext highlighter-rouge">GITHUB_TOKEN</code> environment variable.<br />
This can be done via <em>GitHub - Settings - Developer Settings - Personal access tokens - Tokens (classic) - Generate Token</em>.
To avoid that the token is hardcoded in the Dockerfile, configure it as an environment variable.
To forward an environment variable from the Windows Host to the WSL, you can additionally configure the special environment variable <code class="language-plaintext highlighter-rouge">WSLENV</code> like this: <code class="language-plaintext highlighter-rouge">WSLENV=GITHUB_TOKEN/u</code>.
Have a look at <a href="https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/">Share Environment Vars between WSL and Windows</a>.
Once the environment variable is set and available, update the <em>.devcontainer/devcontainer.json</em> file and add the following configuration as described in <a href="https://code.visualstudio.com/remote/advancedcontainers/environment-variables">Visual Studio Code - Environment variables</a>
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"remoteEnv"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"GITHUB_TOKEN"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${localEnv:GITHUB_TOKEN}"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
    <p>Then run the build using the <code class="language-plaintext highlighter-rouge">--build-arg GITHUB_TOKEN=${GITHUB_TOKEN}</code> argument:</p>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker build -t theia_custom_application --build-arg GITHUB_TOKEN=${GITHUB_TOKEN} .
</code></pre></div>    </div>
  </li>
</ul>

<p>Alternatively you can manually download the native executable for your operating system from <a href="https://github.com/microsoft/ripgrep-prebuilt/releases">ripgrep-prebuilt/releases</a> and extract it to <em>theia/node_modules/@vscode/ripgrep/bin</em>. For the container build, copy the binary in the image creation process to avoid the download at image creation time.</p>

<h2 id="conclusion">Conclusion</h2>

<p>In this blog post I collected the information I gathered over the last months related to getting started with Eclipse Theia. I hope the information helps others who also want to start with Eclipse Theia. There is of course a lot more to learn about it, so this post is just the beginning.</p>

<p>At this point I want to thank <a href="https://www.linkedin.com/in/stefan-dirix/">Stefan Dirix</a> and <a href="https://www.linkedin.com/in/philip-langer/">Philip Langer</a> and the Eclipse Theia Community for the support. Whenever I had questions, opened issues or even contributed something, I got a friendly and helpful response.</p>

<p>The sources of this and the previous blog posts can be found in my <a href="https://github.com/fipro78/vscode_theia_cookbook">Github repository</a>.</p>

<p>I hope you again enjoyed my tutorial and I could share some information via my <em>“external memory”</em>.</p>]]></content><author><name>Dirk Fauth</name></author><category term="fipro" /><category term="vscode" /><category term="typescript" /><category term="theia" /><summary type="html"><![CDATA[In my blog post Getting Started with Visual Studio Code Extension Development I explained how to implement a Visual Studio Code Extension that contributes a custom editor using the Webviews API and different Javascript frameworks. Implementing a Visual Studio Code Extension means to buy-in to the Microsoft Visual Studio Code product world. At least at first sight. By creating an application based on the Eclipse Theia Platform it is possible to build up tools that are similar to Visual Studio Code, but with some differences regarding features, extensibility and customization, privacy and deployment. It reuses components from the VS Code Open Source project and provides additional modules that add features that are otherwise only available in Visual Studio Code because of license restrictions. It is also possible to integrate Visual Studio Code Extensions to a Theia Application, and therefore it is an alternative for the deployment of the developed Visual Studio Code Extension.]]></summary></entry><entry><title type="html">Multiple webviews in a single Visual Studio Code Extension</title><link href="https://vogella.com/blog/multiple-webviews-single-extension/" rel="alternate" type="text/html" title="Multiple webviews in a single Visual Studio Code Extension" /><published>2025-05-07T00:00:00+00:00</published><updated>2025-05-07T00:00:00+00:00</updated><id>https://vogella.com/blog/multiple-webviews-single-extension</id><content type="html" xml:base="https://vogella.com/blog/multiple-webviews-single-extension/"><![CDATA[<p>In my previous blog post <a href="https://vogella.com/blog/vscode-extension-webview-getting-started/">Getting Started with Visual Studio Code Extension Development</a> I described how to create a Visual Studio Code Extension that contributes a custom editor that is implemented using a webview. And I compared how this can be done using vanilla HTML and Javascript, using Angular and using React. Only a single webview is contributed by each Visual Studio Code Extension in the examples of that blog post, which is sufficient for a lot of use cases. But after the publishing of that blog post I was asked several times how to contribute multiple editors with a single extension. It turned out that this is not as intuitive as initially thought, so I decided to write a new blog post dedicated to this topic.</p>

<p>To demonstrate the solutions for the different variants I will add a simple pet editor to the extensions.</p>

<p><strong><em>Note:</em></strong><br />
The following content is based on the <a href="https://vogella.com/blog/vscode-extension-webview-getting-started/">Getting Started with Visual Studio Code Extension Development</a> blog post. If you want to follow the steps described in this post, work through the previous blog post or get the sources from the <a href="https://github.com/fipro78/vscode_theia_cookbook/tree/getting_started">getting_started branch</a>.</p>

<h2 id="visual-studio-code-extension-project">Visual Studio Code Extension Project</h2>

<p>Let’s start by adding a second custom editor to the Vanilla HTML and Javascript Visual Studio Code Extension.</p>

<ul>
  <li>
    <p>Open the <em>vscode-extension/package.json</em></p>

    <ul>
      <li>Extend the <code class="language-plaintext highlighter-rouge">contributes</code> section to add a Pet editor for the <strong>.pet</strong> file extension:</li>
    </ul>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"contributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"customEditors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"viewType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vscode-extension.personEditor"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Visual Studio Code Person Editor"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"filenamePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*.person"</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="p">],</span><span class="w">
        </span><span class="nl">"priority"</span><span class="p">:</span><span class="w"> </span><span class="s2">"default"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"viewType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vscode-extension.petEditor"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Visual Studio Code Pet Editor"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"filenamePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*.pet"</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="p">],</span><span class="w">
        </span><span class="nl">"priority"</span><span class="p">:</span><span class="w"> </span><span class="s2">"default"</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Create a new file <em>vscode-extension/src/abstractEditor.ts</em><br />
To avoid that we need to copy a lot of code for the new editor, we create an abstract base class <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code> that will be used by both editor implementations.</p>

    <ul>
      <li>Copy the content of <em>vscode-extension/src/personEditor.ts</em> to <em>vscode-extension/src/abstractEditor.ts</em></li>
      <li>Rename <code class="language-plaintext highlighter-rouge">PersonEditorProvider</code> to <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code> and make it <code class="language-plaintext highlighter-rouge">abstract</code></li>
      <li>Remove the <code class="language-plaintext highlighter-rouge">viewType</code> constant, the <code class="language-plaintext highlighter-rouge">constructor</code> and the <code class="language-plaintext highlighter-rouge">register</code> method</li>
      <li>Change the visibility of <code class="language-plaintext highlighter-rouge">getWebviewHtml()</code> method to <code class="language-plaintext highlighter-rouge">protected</code> and make it <code class="language-plaintext highlighter-rouge">abstract</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractEditorProvider</span>
  <span class="k">implements</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CustomTextEditorProvider</span>
<span class="p">{</span>
  <span class="k">public</span> <span class="k">async</span> <span class="nf">resolveCustomTextEditor</span><span class="p">(</span>
    <span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span><span class="p">,</span>
    <span class="nx">webviewPanel</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">WebviewPanel</span><span class="p">,</span>
    <span class="nx">_token</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CancellationToken</span>
  <span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// Setup initial content for the webview</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
      <span class="c1">// Enable scripts in the webview</span>
      <span class="na">enableScripts</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="p">};</span>

    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nx">html</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">);</span>

    <span class="c1">// Hook up event handlers so that we can synchronize the webview with the text document.</span>
    <span class="c1">//</span>
    <span class="c1">// The text document acts as our model, so we have to sync change in the document to our</span>
    <span class="c1">// editor and sync changes in the editor back to the document.</span>
    <span class="c1">//</span>
    <span class="c1">// Remember that a single text document can also be shared between multiple custom</span>
    <span class="c1">// editors (this happens for example when you split a custom editor)</span>

    <span class="kd">const</span> <span class="nx">changeDocumentSubscription</span> <span class="o">=</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nf">onDidChangeTextDocument</span><span class="p">((</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nf">toString</span><span class="p">()</span> <span class="o">===</span> <span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nf">toString</span><span class="p">())</span> <span class="p">{</span>
          <span class="k">this</span><span class="p">.</span><span class="nf">updateWebview</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">,</span> <span class="nb">document</span><span class="p">);</span>
        <span class="p">}</span>
      <span class="p">});</span>

    <span class="c1">// Make sure we get rid of the listener when our editor is closed.</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nf">onDidDispose</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">changeDocumentSubscription</span><span class="p">.</span><span class="nf">dispose</span><span class="p">();</span>
    <span class="p">});</span>

    <span class="c1">// Receive message from the webview.</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nf">onDidReceiveMessage</span><span class="p">((</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">switch </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">case</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">:</span>
          <span class="k">this</span><span class="p">.</span><span class="nf">updateDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">text</span><span class="p">);</span>
          <span class="k">return</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">});</span>

    <span class="k">this</span><span class="p">.</span><span class="nf">updateWebview</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">,</span> <span class="nb">document</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Updates the webview with the content of the current document.
   * @param webviewPanel The webview panel to post the message to.
   * @param document The current document.
   */</span>
  <span class="k">private</span> <span class="nf">updateWebview</span><span class="p">(</span>
    <span class="nx">webviewPanel</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">WebviewPanel</span><span class="p">,</span>
    <span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Post message with text of the document to the webview</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
      <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">text</span><span class="p">:</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getText</span><span class="p">(),</span>
    <span class="p">});</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Applies a set of text edits to a document.
   * @param document The current document.
   * @param text The text to set to the document.
   * @returns A thenable that resolves when the edit could be applied.
   */</span>
  <span class="k">private</span> <span class="nf">updateDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span><span class="p">,</span> <span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">edit</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">WorkspaceEdit</span><span class="p">();</span>
    <span class="nx">edit</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span>
      <span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">,</span>
      <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">Range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">document</span><span class="p">.</span><span class="nx">lineCount</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
      <span class="nx">text</span>
    <span class="p">);</span>
    <span class="k">return</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nf">applyEdit</span><span class="p">(</span><span class="nx">edit</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Get the static html used for the editor webviews.
   */</span>
  <span class="k">protected</span> <span class="kd">abstract</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span><span class="p">;</span>

  <span class="cm">/**
   * A helper function that returns a unique alphanumeric identifier called a nonce.
   *
   * @remarks This function is primarily used to help enforce content security
   * policies for resources/scripts being executed in a webview context.
   *
   * @returns A nonce
   */</span>
  <span class="nf">getNonce</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">text</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">possible</span> <span class="o">=</span>
      <span class="dl">"</span><span class="s2">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789</span><span class="dl">"</span><span class="p">;</span>
    <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">32</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">text</span> <span class="o">+=</span> <span class="nx">possible</span><span class="p">.</span><span class="nf">charAt</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nf">random</span><span class="p">()</span> <span class="o">*</span> <span class="nx">possible</span><span class="p">.</span><span class="nx">length</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">text</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update the file <em>vscode-extension/src/personEditor.ts</em></p>

    <ul>
      <li>Extend <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code></li>
      <li>Call <code class="language-plaintext highlighter-rouge">super()</code> in the <code class="language-plaintext highlighter-rouge">constructor</code></li>
      <li>Change the visibility of <code class="language-plaintext highlighter-rouge">getWebviewHtml()</code> to <code class="language-plaintext highlighter-rouge">protected</code></li>
      <li>Delete the other methods that are now defined in <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AbstractEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./abstractEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nc">PersonEditorProvider</span> <span class="kd">extends</span> <span class="nc">AbstractEditorProvider</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="nx">viewType</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">vscode-extension.personEditor</span><span class="dl">"</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">super</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="k">public</span> <span class="k">static</span> <span class="nf">register</span><span class="p">(</span>
    <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span>
  <span class="p">):</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Disposable</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PersonEditorProvider</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">providerRegistration</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nb">window</span><span class="p">.</span><span class="nf">registerCustomEditorProvider</span><span class="p">(</span>
      <span class="nx">PersonEditorProvider</span><span class="p">.</span><span class="nx">viewType</span><span class="p">,</span>
      <span class="nx">provider</span>
    <span class="p">);</span>
    <span class="k">return</span> <span class="nx">providerRegistration</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Get the static html used for the editor webviews.
   */</span>
  <span class="k">protected</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="c1">// Local path to script and css for the webview</span>
    <span class="kd">const</span> <span class="nx">stylesUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">styles.css</span><span class="dl">"</span><span class="p">)</span>
    <span class="p">);</span>
    <span class="kd">const</span> <span class="nx">scriptUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">main.js</span><span class="dl">"</span><span class="p">)</span>
    <span class="p">);</span>

    <span class="kd">const</span> <span class="nx">elementsUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">node_modules</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">@vscode-elements/elements</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">bundled.js</span><span class="dl">"</span>
      <span class="p">)</span>
    <span class="p">);</span>

    <span class="kd">const</span> <span class="nx">nonce</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getNonce</span><span class="p">();</span>

    <span class="c1">// Tip: Install the es6-string-html Visual Studio Code extension to enable code highlighting below</span>
    <span class="k">return</span> <span class="cm">/* html */</span> <span class="s2">`
      &lt;!DOCTYPE html&gt;
      &lt;html lang="en"&gt;
      &lt;head&gt;
          &lt;meta charset="UTF-8"&gt;
  
          &lt;!--
          Use a content security policy to only allow loading images, styles and fonts from https or from our extension directory,
          and only allow scripts that have a specific nonce.
          --&gt;
          &lt;meta
              http-equiv="Content-Security-Policy"
              content="default-src 'none'; img-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; font-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; style-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; script-src 'nonce-</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">';"&gt;
  
          &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  
          &lt;link href="</span><span class="p">${</span><span class="nx">stylesUri</span><span class="p">}</span><span class="s2">" rel="stylesheet" /&gt;
  
          &lt;title&gt;Person Editor&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
          &lt;h1&gt;Visual Studio Code Person Editor&lt;/h1&gt;
          &lt;div class="person"&gt;
              &lt;div class="row"&gt;
                  &lt;vscode-label for="firstname"&gt;Firstname:&lt;/vscode-label&gt;
                  &lt;div class="value"&gt;
                      &lt;vscode-textfield type="text" id="firstname"/&gt;
                  &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="row"&gt;
                  &lt;vscode-label for="lastname"&gt;Lastname:&lt;/vscode-label&gt;
                  &lt;div class="value"&gt;
                      &lt;vscode-textfield type="text" id="lastname"/&gt;
                  &lt;/div&gt;
              &lt;/div&gt;
          &lt;/div&gt;
  
          &lt;script nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">scriptUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
          &lt;script nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">elementsUri</span><span class="p">}</span><span class="s2">" type="module"&gt;&lt;/script&gt;
      &lt;/body&gt;
      &lt;/html&gt;`</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Create a new file <em>vscode-extension/src/petEditor.ts</em></p>

    <ul>
      <li>Copy the content of <em>vscode-extension/src/personEditor.ts</em></li>
      <li>Rename the class to <code class="language-plaintext highlighter-rouge">PetEditorProvider</code></li>
      <li>Update the value of <code class="language-plaintext highlighter-rouge">viewType</code> to <code class="language-plaintext highlighter-rouge">"vscode-extension.petEditor"</code></li>
      <li>Update the <code class="language-plaintext highlighter-rouge">register()</code> implementation to register the <code class="language-plaintext highlighter-rouge">PetEditorProvider</code></li>
      <li>Update the <code class="language-plaintext highlighter-rouge">getWebviewHtml()</code> implementation to return the HTML for a pet editor</li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AbstractEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./abstractEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nc">PetEditorProvider</span> <span class="kd">extends</span> <span class="nc">AbstractEditorProvider</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="nx">viewType</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">vscode-extension.petEditor</span><span class="dl">"</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">super</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="k">public</span> <span class="k">static</span> <span class="nf">register</span><span class="p">(</span>
    <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span>
  <span class="p">):</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Disposable</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PetEditorProvider</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">providerRegistration</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nb">window</span><span class="p">.</span><span class="nf">registerCustomEditorProvider</span><span class="p">(</span>
      <span class="nx">PetEditorProvider</span><span class="p">.</span><span class="nx">viewType</span><span class="p">,</span>
      <span class="nx">provider</span>
    <span class="p">);</span>
    <span class="k">return</span> <span class="nx">providerRegistration</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Get the static html used for the editor webviews.
   */</span>
  <span class="k">protected</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="c1">// Local path to script and css for the webview</span>
    <span class="kd">const</span> <span class="nx">stylesUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">styles.css</span><span class="dl">"</span><span class="p">)</span>
    <span class="p">);</span>
    <span class="kd">const</span> <span class="nx">scriptUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">main.js</span><span class="dl">"</span><span class="p">)</span>
    <span class="p">);</span>

    <span class="kd">const</span> <span class="nx">elementsUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">node_modules</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">@vscode-elements/elements</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">bundled.js</span><span class="dl">"</span>
      <span class="p">)</span>
    <span class="p">);</span>

    <span class="kd">const</span> <span class="nx">nonce</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getNonce</span><span class="p">();</span>

    <span class="c1">// Tip: Install the es6-string-html Visual Studio Code extension to enable code highlighting below</span>
    <span class="k">return</span> <span class="cm">/* html */</span> <span class="s2">`
      &lt;!DOCTYPE html&gt;
      &lt;html lang="en"&gt;
      &lt;head&gt;
          &lt;meta charset="UTF-8"&gt;
  
          &lt;!--
          Use a content security policy to only allow loading images, styles and fonts from https or from our extension directory,
          and only allow scripts that have a specific nonce.
          --&gt;
          &lt;meta
              http-equiv="Content-Security-Policy"
              content="default-src 'none'; img-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; font-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; style-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; script-src 'nonce-</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">';"&gt;
  
          &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  
          &lt;link href="</span><span class="p">${</span><span class="nx">stylesUri</span><span class="p">}</span><span class="s2">" rel="stylesheet" /&gt;
  
          &lt;title&gt;Pet Editor&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
          &lt;h1&gt;Visual Studio Code Pet Editor&lt;/h1&gt;
          &lt;div class="pet"&gt;
              &lt;div class="row"&gt;
                  &lt;vscode-label for="name"&gt;Name:&lt;/vscode-label&gt;
                  &lt;div class="value"&gt;
                      &lt;vscode-textfield type="text" id="name"/&gt;
                  &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="row"&gt;
                  &lt;vscode-label for="species"&gt;Species:&lt;/vscode-label&gt;
                  &lt;div class="value"&gt;
                      &lt;vscode-single-select id="species"&gt;
                          &lt;vscode-option&gt;-&lt;/vscode-option&gt;
                          &lt;vscode-option description="bird"&gt;Bird&lt;/vscode-option&gt;
                          &lt;vscode-option description="cat"&gt;Cat&lt;/vscode-option&gt;
                          &lt;vscode-option description="dog"&gt;Dog&lt;/vscode-option&gt;
                      &lt;/vscode-single-select&gt;
                  &lt;/div&gt;
              &lt;/div&gt;
          &lt;/div&gt;
  
          &lt;script nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">scriptUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
          &lt;script nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">elementsUri</span><span class="p">}</span><span class="s2">" type="module"&gt;&lt;/script&gt;
      &lt;/body&gt;
      &lt;/html&gt;`</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update the file <em>vscode-extension/media/main.js</em> so the code is able to handle <em>persons</em> and <em>pets</em></p>

    <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Script run within the webview itself.</span>
<span class="p">(</span><span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// Get a reference to the Visual Studio Code webview api.</span>
  <span class="c1">// We use this API to post messages back to our extension.</span>

  <span class="kd">const</span> <span class="nx">vscode</span> <span class="o">=</span> <span class="nf">acquireVsCodeApi</span><span class="p">();</span>

  <span class="kd">const</span> <span class="nx">personContainer</span> <span class="o">=</span> <span class="cm">/** @type {HTMLElement} */</span> <span class="p">(</span>
    <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">.person</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">);</span>
  <span class="kd">const</span> <span class="nx">petContainer</span> <span class="o">=</span> <span class="cm">/** @type {HTMLElement} */</span> <span class="p">(</span>
    <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">.pet</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">errorContainer</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">div</span><span class="dl">"</span><span class="p">);</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">errorContainer</span><span class="p">);</span>
  <span class="nx">errorContainer</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">;</span>
  <span class="nx">errorContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">;</span>

  <span class="cm">/**
   * Render the document in the webview.
   */</span>
  <span class="kd">function</span> <span class="nf">updateContent</span><span class="p">(</span><span class="cm">/** @type {string} */</span> <span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">json</span><span class="p">;</span>
    <span class="k">try</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">text</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">{}</span><span class="dl">"</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="nx">json</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">personContainer</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">personContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">petContainer</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">petContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="nx">errorContainer</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Error: Document is not valid json</span><span class="dl">"</span><span class="p">;</span>
      <span class="nx">errorContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">personContainer</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">personContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">petContainer</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">petContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nx">errorContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">;</span>

    <span class="kd">const</span> <span class="nx">firstname</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">firstname</span><span class="dl">"</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">lastname</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">lastname</span><span class="dl">"</span><span class="p">);</span>

    <span class="kd">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">species</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">species</span><span class="dl">"</span><span class="p">);</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">firstname</span> <span class="o">&amp;&amp;</span> <span class="nx">lastname</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">firstname</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">firstname</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">lastname</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">lastname</span><span class="p">;</span>
      <span class="p">}</span>

      <span class="nx">firstname</span><span class="p">.</span><span class="nx">oninput</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
        <span class="c1">// wait 500 ms before updating the document</span>
        <span class="c1">// only update if in the meantime no other input was given</span>
        <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">json</span><span class="p">.</span><span class="nx">firstname</span> <span class="o">=</span> <span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
            <span class="nx">vscode</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
              <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">,</span>
              <span class="na">text</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">json</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
            <span class="p">});</span>
          <span class="p">}</span>
        <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
      <span class="p">};</span>

      <span class="nx">lastname</span><span class="p">.</span><span class="nx">oninput</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
        <span class="c1">// wait 500 ms before updating the document</span>
        <span class="c1">// only update if in the meantime no other input was given</span>
        <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">json</span><span class="p">.</span><span class="nx">lastname</span> <span class="o">=</span> <span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
            <span class="nx">vscode</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
              <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">,</span>
              <span class="na">text</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">json</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
            <span class="p">});</span>
          <span class="p">}</span>
        <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
      <span class="p">};</span>
    <span class="p">}</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">name</span> <span class="o">&amp;&amp;</span> <span class="nx">species</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">name</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">species</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">species</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">species</span><span class="p">;</span>
      <span class="p">}</span>

      <span class="nx">name</span><span class="p">.</span><span class="nx">oninput</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">name</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
        <span class="c1">// wait 500 ms before updating the document</span>
        <span class="c1">// only update if in the meantime no other input was given</span>
        <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">name</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">json</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
            <span class="nx">vscode</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
              <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">,</span>
              <span class="na">text</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">json</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
            <span class="p">});</span>
          <span class="p">}</span>
        <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
      <span class="p">};</span>

      <span class="nx">species</span><span class="p">.</span><span class="nx">oninput</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">species</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
        <span class="c1">// wait 500 ms before updating the document</span>
        <span class="c1">// only update if in the meantime no other input was given</span>
        <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">species</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">json</span><span class="p">.</span><span class="nx">species</span> <span class="o">=</span> <span class="nx">species</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
            <span class="nx">vscode</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
              <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">,</span>
              <span class="na">text</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">json</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
            <span class="p">});</span>
          <span class="p">}</span>
        <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
      <span class="p">};</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="c1">// Handle messages sent from the extension to the webview</span>
  <span class="nb">window</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// The json data that the extension sent</span>
    <span class="k">switch </span><span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">case</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">:</span>
        <span class="kd">const</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">message</span><span class="p">.</span><span class="nx">text</span><span class="p">;</span>

        <span class="c1">// Update our webview's content</span>
        <span class="nf">updateContent</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>

        <span class="c1">// Then persist state information.</span>
        <span class="c1">// This state is returned in the call to `vscode.getState` below when a webview is reloaded.</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nf">setState</span><span class="p">({</span> <span class="nx">text</span> <span class="p">});</span>

        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">});</span>

  <span class="c1">// Webviews are normally torn down when not visible and re-created when they become visible again.</span>
  <span class="c1">// State lets us save information across these re-loads</span>
  <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nf">getState</span><span class="p">();</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">updateContent</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">text</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">})();</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update <em>vscode-extension/src/extension.ts</em></p>

    <ul>
      <li>Register the <code class="language-plaintext highlighter-rouge">PetEditorProvider</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The module 'vscode' contains the Visual Studio Code extensibility API</span>
<span class="c1">// Import the module and reference it with the alias vscode in your code below</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PersonEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./personEditor</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PetEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./petEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// This method is called when your extension is activated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">activate</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Register our custom editor provider</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">PersonEditorProvider</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">context</span><span class="p">));</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">PetEditorProvider</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">context</span><span class="p">));</span>
<span class="p">}</span>

<span class="c1">// This method is called when your extension is deactivated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">deactivate</span><span class="p">()</span> <span class="p">{}</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>If everything is correctly in place, you can verify the editor by</p>

<ul>
  <li>pressing F5 to start a new Visual Studio Code instance with the extension</li>
  <li>opening a folder somewhere<br />
Create a folder <em>example</em> in the home directory of the <em>node</em> user in the Dev Container for example.</li>
  <li>creating a new file named <em>santas.pet</em></li>
</ul>

<p>Now a webview with two input fields should be visible.</p>

<ul>
  <li>enter values for <em>Name</em> and <em>Species</em>,<br />
<em>e.g. Name: Santa’s little helper, Species: Dog</em></li>
  <li>Save via <em>CTRL + S</em></li>
  <li>Right click on the created file in the <em>Explorer</em></li>
  <li>Select <em>Open With…</em> - <em>Text Editor</em></li>
</ul>

<p>The default text editor should open with the JSON content of the created file.</p>

<h2 id="angular-webview-implementation">Angular Webview Implementation</h2>

<p>To provide multiple webviews in an extension that uses Angular as webframework, there is some more work to do.
In the <a href="https://vogella.com/blog/vscode-extension-webview-getting-started/#angular-webview-implementation">Getting Started with Visual Studio Code Extension Development - Angular Webview Implementation</a> I implemented the webview as a <a href="https://blog.angular-university.io/angular-standalone-components/">Angular Standalone Component</a> and I removed the <code class="language-plaintext highlighter-rouge">RouterOutlet</code> to make the usage of assets from third-party modules work correctly.</p>

<p>The following section describes how to add a second custom editor to the Angular Visual Studio Code Extension.</p>

<ul>
  <li>
    <p>Open the <em>angular-extension/package.json</em></p>

    <ul>
      <li>Extend the <code class="language-plaintext highlighter-rouge">contributes</code> section to add a Pet editor for the <strong>.pet</strong> file extension:</li>
    </ul>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"contributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"customEditors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"viewType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"angular-extension.personEditor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Angular Person Editor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"filenamePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*.person"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"priority"</span><span class="p">:</span><span class="w"> </span><span class="s2">"option"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"viewType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"angular-extension.petEditor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Angular Pet Editor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"filenamePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*.pet"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"priority"</span><span class="p">:</span><span class="w"> </span><span class="s2">"option"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Create a new file <em>angular-extension/src/abstractEditor.ts</em><br />
To avoid that we need to copy a lot of code for the new editor, we create an abstract base class <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code> that will be used by both editor implementations.</p>

    <ul>
      <li>Copy the content of <em>angular-extension/src/personEditor.ts</em> to <em>angular-extension/src/abstractEditor.ts</em></li>
      <li>Rename <code class="language-plaintext highlighter-rouge">PersonEditorProvider</code> to <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code> and make it <code class="language-plaintext highlighter-rouge">abstract</code></li>
      <li>Remove the <code class="language-plaintext highlighter-rouge">viewType</code> constant and the <code class="language-plaintext highlighter-rouge">register</code> method</li>
      <li>Add an abstract method <code class="language-plaintext highlighter-rouge">getRootComponentSelector()</code></li>
      <li>Update <code class="language-plaintext highlighter-rouge">getWebviewHtml()</code> to use <code class="language-plaintext highlighter-rouge">getRootComponentSelector()</code> as the app root component and as an attribute <code class="language-plaintext highlighter-rouge">data-root</code> in the <code class="language-plaintext highlighter-rouge">html</code> tag</li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractEditorProvider</span>
  <span class="k">implements</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CustomTextEditorProvider</span>
<span class="p">{</span>
  <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{}</span>

  <span class="k">public</span> <span class="k">async</span> <span class="nf">resolveCustomTextEditor</span><span class="p">(</span>
    <span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span><span class="p">,</span>
    <span class="nx">webviewPanel</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">WebviewPanel</span><span class="p">,</span>
    <span class="nx">_token</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CancellationToken</span>
  <span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// Setup initial content for the webview</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
      <span class="c1">// Enable scripts in the webview</span>
      <span class="na">enableScripts</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>

      <span class="c1">// Restrict the webview to only load resources from the `dist` and `webview-ui/build` directories</span>
      <span class="na">localResourceRoots</span><span class="p">:</span> <span class="p">[</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">),</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">webview-ui/build</span><span class="dl">"</span><span class="p">),</span>
      <span class="p">],</span>
    <span class="p">};</span>

    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nx">html</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">);</span>

    <span class="c1">// Hook up event handlers so that we can synchronize the webview with the text document.</span>
    <span class="c1">//</span>
    <span class="c1">// The text document acts as our model, so we have to sync change in the document to our</span>
    <span class="c1">// editor and sync changes in the editor back to the document.</span>
    <span class="c1">//</span>
    <span class="c1">// Remember that a single text document can also be shared between multiple custom</span>
    <span class="c1">// editors (this happens for example when you split a custom editor)</span>

    <span class="kd">const</span> <span class="nx">changeDocumentSubscription</span> <span class="o">=</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nf">onDidChangeTextDocument</span><span class="p">((</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nf">toString</span><span class="p">()</span> <span class="o">===</span> <span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nf">toString</span><span class="p">())</span> <span class="p">{</span>
          <span class="k">this</span><span class="p">.</span><span class="nf">updateWebview</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">,</span> <span class="nb">document</span><span class="p">);</span>
        <span class="p">}</span>
      <span class="p">});</span>

    <span class="c1">// Make sure we get rid of the listener when our editor is closed.</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nf">onDidDispose</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">changeDocumentSubscription</span><span class="p">.</span><span class="nf">dispose</span><span class="p">();</span>
    <span class="p">});</span>

    <span class="c1">// Receive message from the webview.</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nf">onDidReceiveMessage</span><span class="p">((</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">switch </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">case</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">:</span>
          <span class="k">this</span><span class="p">.</span><span class="nf">updateDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">text</span><span class="p">);</span>
          <span class="k">return</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">});</span>

    <span class="k">this</span><span class="p">.</span><span class="nf">updateWebview</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">,</span> <span class="nb">document</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Updates the webview with the content of the current document.
   * @param webviewPanel The webview panel to post the message to.
   * @param document The current document.
   */</span>
  <span class="k">private</span> <span class="nf">updateWebview</span><span class="p">(</span>
    <span class="nx">webviewPanel</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">WebviewPanel</span><span class="p">,</span>
    <span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Post message with text of the document to the webview</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
      <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">text</span><span class="p">:</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getText</span><span class="p">(),</span>
    <span class="p">});</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Applies a set of text edits to a document.
   * @param document The current document.
   * @param text The text to set to the document.
   * @returns A thenable that resolves when the edit could be applied.
   */</span>
  <span class="k">private</span> <span class="nf">updateDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span><span class="p">,</span> <span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">edit</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">WorkspaceEdit</span><span class="p">();</span>
    <span class="nx">edit</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span>
      <span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">,</span>
      <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">Range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">document</span><span class="p">.</span><span class="nx">lineCount</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
      <span class="nx">text</span>
    <span class="p">);</span>
    <span class="k">return</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nf">applyEdit</span><span class="p">(</span><span class="nx">edit</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Get the static html used for the editor webviews.
   */</span>
  <span class="k">private</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="c1">// The CSS file from the Angular build output</span>
    <span class="kd">const</span> <span class="nx">stylesUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">styles.css</span><span class="dl">"</span>
      <span class="p">)</span>
    <span class="p">);</span>
    <span class="c1">// The JS files from the Angular build output</span>
    <span class="kd">const</span> <span class="nx">polyfillsUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">polyfills.js</span><span class="dl">"</span>
      <span class="p">)</span>
    <span class="p">);</span>
    <span class="kd">const</span> <span class="nx">scriptUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">main.js</span><span class="dl">"</span>
      <span class="p">)</span>
    <span class="p">);</span>

    <span class="kd">const</span> <span class="nx">rootSelector</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getRootComponentSelector</span><span class="p">();</span>
    <span class="kd">const</span> <span class="nx">nonce</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getNonce</span><span class="p">();</span>

    <span class="c1">// Tip: Install the es6-string-html VS Code extension to enable code highlighting below</span>
    <span class="k">return</span> <span class="cm">/*html*/</span> <span class="s2">`
      &lt;!DOCTYPE html&gt;
      &lt;html lang="en" data-root="</span><span class="p">${</span><span class="nx">rootSelector</span><span class="p">}</span><span class="s2">"&gt;
      &lt;head&gt;
          &lt;meta charset="UTF-8" /&gt;
          &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
          &lt;meta
              http-equiv="Content-Security-Policy"
              content="default-src 'none'; style-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2"> 'unsafe-inline'; font-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; img-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; script-src 'nonce-</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">';"&gt;
          &lt;link rel="stylesheet" type="text/css" href="</span><span class="p">${</span><span class="nx">stylesUri</span><span class="p">}</span><span class="s2">"&gt;
      &lt;/head&gt;
      &lt;body&gt;
          &lt;</span><span class="p">${</span><span class="nx">rootSelector</span><span class="p">}</span><span class="s2">&gt;&lt;/</span><span class="p">${</span><span class="nx">rootSelector</span><span class="p">}</span><span class="s2">&gt;
          &lt;script type="module" nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">polyfillsUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
          &lt;script type="module" nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">scriptUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
      &lt;/body&gt;
      &lt;/html&gt;
    `</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Return the selector of the root component to use.
   */</span>
  <span class="kd">abstract</span> <span class="nf">getRootComponentSelector</span><span class="p">():</span> <span class="kr">string</span><span class="p">;</span>

  <span class="cm">/**
   * A helper function that returns a unique alphanumeric identifier called a nonce.
   *
   * @remarks This function is primarily used to help enforce content security
   * policies for resources/scripts being executed in a webview context.
   *
   * @returns A nonce
   */</span>
  <span class="nf">getNonce</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">text</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">possible</span> <span class="o">=</span>
      <span class="dl">"</span><span class="s2">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789</span><span class="dl">"</span><span class="p">;</span>
    <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">32</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">text</span> <span class="o">+=</span> <span class="nx">possible</span><span class="p">.</span><span class="nf">charAt</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nf">random</span><span class="p">()</span> <span class="o">*</span> <span class="nx">possible</span><span class="p">.</span><span class="nx">length</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">text</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update the file <em>angular-extension/src/personEditor.ts</em></p>

    <ul>
      <li>Extend <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code></li>
      <li>Call <code class="language-plaintext highlighter-rouge">super(context)</code> in the <code class="language-plaintext highlighter-rouge">constructor</code></li>
      <li>Add the <code class="language-plaintext highlighter-rouge">getRootComponentSelector()</code> method which returns <code class="language-plaintext highlighter-rouge">person-root</code></li>
      <li>Delete the other methods that are now defined in <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AbstractEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./abstractEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nc">PersonEditorProvider</span> <span class="kd">extends</span> <span class="nc">AbstractEditorProvider</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="nx">viewType</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">angular-extension.personEditor</span><span class="dl">"</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">super</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">public</span> <span class="k">static</span> <span class="nf">register</span><span class="p">(</span>
    <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span>
  <span class="p">):</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Disposable</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PersonEditorProvider</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">providerRegistration</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nb">window</span><span class="p">.</span><span class="nf">registerCustomEditorProvider</span><span class="p">(</span>
      <span class="nx">PersonEditorProvider</span><span class="p">.</span><span class="nx">viewType</span><span class="p">,</span>
      <span class="nx">provider</span>
    <span class="p">);</span>
    <span class="k">return</span> <span class="nx">providerRegistration</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">getRootComponentSelector</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="dl">"</span><span class="s2">person-root</span><span class="dl">"</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Create a new file <em>angular-extension/src/petEditor.ts</em></p>

    <ul>
      <li>Copy the content of <em>angular-extension/src/personEditor.ts</em></li>
      <li>Rename the class to <code class="language-plaintext highlighter-rouge">PetEditorProvider</code></li>
      <li>Update the value of <code class="language-plaintext highlighter-rouge">viewType</code> to <code class="language-plaintext highlighter-rouge">"angular-extension.petEditor"</code></li>
      <li>Update the <code class="language-plaintext highlighter-rouge">register()</code> implementation to register the <code class="language-plaintext highlighter-rouge">PetEditorProvider</code></li>
      <li>Change the return value of <code class="language-plaintext highlighter-rouge">getRootComponentSelector()</code> to <code class="language-plaintext highlighter-rouge">pet-root</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AbstractEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./abstractEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nc">PetEditorProvider</span> <span class="kd">extends</span> <span class="nc">AbstractEditorProvider</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="nx">viewType</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">angular-extension.petEditor</span><span class="dl">"</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">super</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">public</span> <span class="k">static</span> <span class="nf">register</span><span class="p">(</span>
    <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span>
  <span class="p">):</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Disposable</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PetEditorProvider</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">providerRegistration</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nb">window</span><span class="p">.</span><span class="nf">registerCustomEditorProvider</span><span class="p">(</span>
      <span class="nx">PetEditorProvider</span><span class="p">.</span><span class="nx">viewType</span><span class="p">,</span>
      <span class="nx">provider</span>
    <span class="p">);</span>
    <span class="k">return</span> <span class="nx">providerRegistration</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">getRootComponentSelector</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="dl">"</span><span class="s2">pet-root</span><span class="dl">"</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update <em>angular-extension/src/extension.ts</em></p>

    <ul>
      <li>Register the <code class="language-plaintext highlighter-rouge">PetEditorProvider</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The module 'vscode' contains the Visual Studio Code extensibility API</span>
<span class="c1">// Import the module and reference it with the alias vscode in your code below</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PersonEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./personEditor</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PetEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./petEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// This method is called when your extension is activated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">activate</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Register our custom editor provider</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">PersonEditorProvider</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">context</span><span class="p">));</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">PetEditorProvider</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">context</span><span class="p">));</span>
<span class="p">}</span>

<span class="c1">// This method is called when your extension is deactivated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">deactivate</span><span class="p">()</span> <span class="p">{}</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>Now that the extension code is prepared for multiple custom editors, the webview needs to be implemented in the Angular <em>webview-ui</em> structure. There is also some refactoring involved</p>

<ul>
  <li>
    <p>Update <em>angular-extension/webview-ui/src/app/vscode-textfield-input.directive.ts</em><br />
I want to use the <a href="https://vscode-elements.github.io/components/single-select/">VSCode Elements - Single Select</a> component in the new pet editor. The VSCode Elements implementation is based on the Lit library. So the usage requires the implementation of a <a href="https://angular.dev/api/forms/ControlValueAccessor">ControlValueAccessor</a> as a <a href="https://angular.dev/guide/directives/directive-composition-api">Directive</a>, which is described in <a href="https://vogella.com/blog/vscode-extension-webview-getting-started/#vscode-elements-1">Getting Started with Visual Studio Code Extension Development</a>.
As the <code class="language-plaintext highlighter-rouge">vscode-single-select</code> component also tracks its state in the <code class="language-plaintext highlighter-rouge">value</code> attribute, we can simply add <code class="language-plaintext highlighter-rouge">vscode-single-select</code> to the <code class="language-plaintext highlighter-rouge">selector</code> of the existing <code class="language-plaintext highlighter-rouge">Directive</code>.</p>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
  <span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">vscode-textfield, vscode-single-select</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nx">VSCODE_TEXTFIELD_INPUT_VALUE_ACCESSOR</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">VscodeTextfieldInputDirective</span> <span class="k">implements</span> <span class="nx">ControlValueAccessor</span> <span class="p">{</span>
</code></pre></div>    </div>

    <p>We could of course also think about renaming it to something more general, but let’s keep the modifications minimal at this point.</p>
  </li>
  <li>Create a new folder <em>angular-extension/webview-ui/src/app/person</em></li>
  <li>Move the following files to <em>angular-extension/webview-ui/src/app/person</em>
    <ul>
      <li><em>angular-extension/webview-ui/src/app/app.component.html</em></li>
      <li><em>angular-extension/webview-ui/src/app/app.component.ts</em></li>
      <li><em>angular-extension/webview-ui/src/app/app.component.spec.ts</em></li>
    </ul>
  </li>
  <li>Rename those files by replacing <em>app</em> with <em>person</em></li>
  <li>Edit the file <em>angular-extension/webview-ui/src/app/person/person.component.ts</em>
    <ul>
      <li>Change the selector to <code class="language-plaintext highlighter-rouge">person-root</code></li>
      <li>Rename the <code class="language-plaintext highlighter-rouge">AppComponent</code> class to <code class="language-plaintext highlighter-rouge">PersonComponent</code></li>
      <li>Correct the references in <code class="language-plaintext highlighter-rouge">templateUrl</code> and <code class="language-plaintext highlighter-rouge">styleUrl</code>
        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">Component</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">person-root</span><span class="dl">'</span><span class="p">,</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">ReactiveFormsModule</span><span class="p">,</span> <span class="nx">VscodeTextfieldInputDirective</span><span class="p">],</span>
<span class="na">templateUrl</span><span class="p">:</span> <span class="dl">'</span><span class="s1">./person.component.html</span><span class="dl">'</span><span class="p">,</span>
<span class="na">styleUrl</span><span class="p">:</span> <span class="dl">'</span><span class="s1">../app.component.css</span><span class="dl">'</span><span class="p">,</span>
<span class="na">schemas</span><span class="p">:</span> <span class="p">[</span><span class="nx">CUSTOM_ELEMENTS_SCHEMA</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">PersonComponent</span> <span class="p">{</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>Now create a <code class="language-plaintext highlighter-rouge">PetComponent</code> as the second custom editor in the Angular extension:</p>

<ul>
  <li>Create a new folder <em>angular-extension/webview-ui/src/app/pet</em></li>
  <li>
    <p>Create a new file <em>angular-extension/webview-ui/src/app/pet/pet.component.html</em></p>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="nx">main</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">main</span><span class="dl">"</span><span class="o">&gt;</span>
  <span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;</span><span class="nx">Angular</span> <span class="nx">Pet</span> <span class="nx">Editor</span><span class="o">&lt;</span><span class="sr">/h1</span><span class="err">&gt;
</span>  <span class="o">&lt;</span><span class="nx">div</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">content</span><span class="dl">"</span><span class="o">&gt;</span>
    <span class="o">&lt;</span><span class="nx">div</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">pet</span><span class="dl">"</span><span class="o">&gt;</span>
      <span class="o">&lt;</span><span class="nx">div</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">row</span><span class="dl">"</span><span class="o">&gt;</span>
        <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">label</span> <span class="k">for</span><span class="o">=</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Name</span><span class="p">:</span><span class="o">&lt;</span><span class="sr">/vscode-label</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="nx">div</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="o">&gt;</span>
          <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">textfield</span>
            <span class="kd">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text</span><span class="dl">"</span>
            <span class="p">[</span><span class="nx">formControl</span><span class="p">]</span><span class="o">=</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span>
            <span class="p">(</span><span class="nx">input</span><span class="p">)</span><span class="o">=</span><span class="dl">"</span><span class="s2">updateDocument()</span><span class="dl">"</span>
          <span class="o">/&gt;</span>
        <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="nx">div</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">row</span><span class="dl">"</span><span class="o">&gt;</span>
        <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">label</span> <span class="k">for</span><span class="o">=</span><span class="dl">"</span><span class="s2">species</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Species</span><span class="p">:</span><span class="o">&lt;</span><span class="sr">/vscode-label</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="nx">div</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="o">&gt;</span>
          <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">single</span><span class="o">-</span><span class="nx">select</span>
            <span class="p">[</span><span class="nx">formControl</span><span class="p">]</span><span class="o">=</span><span class="dl">"</span><span class="s2">species</span><span class="dl">"</span>
            <span class="p">(</span><span class="nx">input</span><span class="p">)</span><span class="o">=</span><span class="dl">"</span><span class="s2">updateDocument()</span><span class="dl">"</span>
          <span class="o">&gt;</span>
            <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">option</span><span class="o">&gt;-&lt;</span><span class="sr">/vscode-option</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">option</span> <span class="nx">description</span><span class="o">=</span><span class="dl">"</span><span class="s2">bird</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Bird</span><span class="o">&lt;</span><span class="sr">/vscode-option</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">option</span> <span class="nx">description</span><span class="o">=</span><span class="dl">"</span><span class="s2">cat</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Cat</span><span class="o">&lt;</span><span class="sr">/vscode-option</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">option</span> <span class="nx">description</span><span class="o">=</span><span class="dl">"</span><span class="s2">dog</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Dog</span><span class="o">&lt;</span><span class="sr">/vscode-option</span><span class="err">&gt;
</span>          <span class="o">&lt;</span><span class="sr">/vscode-single-select</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>    <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>  <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span><span class="o">&lt;</span><span class="sr">/main</span><span class="err">&gt;
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Create a new file <em>angular-extension/webview-ui/src/app/pet/pet.component.ts</em><br />
The content of this file is almost the same as <em>angular-extension/webview-ui/src/app/person/person.component.ts</em>, but of course with changes related to the pet data structure and the selector is changed to <code class="language-plaintext highlighter-rouge">pet-root</code>.</p>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">Component</span><span class="p">,</span>
  <span class="nx">CUSTOM_ELEMENTS_SCHEMA</span><span class="p">,</span>
  <span class="nx">HostListener</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@angular/core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">FormControl</span><span class="p">,</span> <span class="nx">ReactiveFormsModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@angular/forms</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">vscode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utilities/vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">VscodeTextfieldInputDirective</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../vscode-textfield-input.directive</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">../../../node_modules/@vscode-elements/elements/dist/vscode-label</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">../../../node_modules/@vscode-elements/elements/dist/vscode-textfield</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">../../../node_modules/@vscode-elements/elements/dist/vscode-single-select</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">../../../node_modules/@vscode-elements/elements/dist/vscode-option</span><span class="dl">"</span><span class="p">;</span>

<span class="p">@</span><span class="nd">Component</span><span class="p">({</span>
  <span class="na">selector</span><span class="p">:</span> <span class="dl">"</span><span class="s2">pet-root</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">ReactiveFormsModule</span><span class="p">,</span> <span class="nx">VscodeTextfieldInputDirective</span><span class="p">],</span>
  <span class="na">templateUrl</span><span class="p">:</span> <span class="dl">"</span><span class="s2">./pet.component.html</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">styleUrl</span><span class="p">:</span> <span class="dl">"</span><span class="s2">../app.component.css</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">schemas</span><span class="p">:</span> <span class="p">[</span><span class="nx">CUSTOM_ELEMENTS_SCHEMA</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">PetComponent</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FormControl</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
  <span class="nx">species</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FormControl</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
  <span class="nl">petObject</span><span class="p">:</span> <span class="kr">any</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Webviews are normally torn down when not visible and re-created when they become visible again.</span>
    <span class="c1">// State lets us save information across these re-loads</span>
    <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nf">getState</span><span class="p">()</span> <span class="kd">as </span><span class="kr">any</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nf">updateContent</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">text</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="c1">// Handle messages sent from the extension to the webview</span>
  <span class="p">@</span><span class="nd">HostListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">window:message</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="dl">"</span><span class="s2">$event</span><span class="dl">"</span><span class="p">])</span>
  <span class="nf">handleMessage</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="nx">MessageEvent</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// The json data that the extension sent</span>
    <span class="k">switch </span><span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">case</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">:</span>
        <span class="kd">const</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">message</span><span class="p">.</span><span class="nx">text</span><span class="p">;</span>

        <span class="c1">// Update our webview's content</span>
        <span class="k">this</span><span class="p">.</span><span class="nf">updateContent</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>

        <span class="c1">// Then persist state information.</span>
        <span class="c1">// This state is returned in the call to `vscode.getState` below when a webview is reloaded.</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nf">setState</span><span class="p">({</span> <span class="nx">text</span> <span class="p">});</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Update the data shown in the document in the webview.
   */</span>
  <span class="nf">updateContent</span><span class="p">(</span><span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">text</span> <span class="o">!==</span> <span class="dl">""</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">petObject</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nf">setValue</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">petObject</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">species</span><span class="p">.</span><span class="nf">setValue</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">petObject</span><span class="p">.</span><span class="nx">species</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Update the document in the extension.
   */</span>
  <span class="nf">updateDocument</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">name</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
    <span class="kd">let</span> <span class="nx">species</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">species</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>

    <span class="c1">// wait 500 ms before updating the document</span>
    <span class="c1">// only update if in the meantime no other input was given</span>
    <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">name</span> <span class="o">&amp;&amp;</span> <span class="k">this</span><span class="p">.</span><span class="nx">species</span><span class="p">.</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">species</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">petObject</span> <span class="o">=</span> <span class="p">{</span>
          <span class="na">name</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nx">value</span><span class="p">,</span>
          <span class="na">species</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">species</span><span class="p">.</span><span class="nx">value</span><span class="p">,</span>
        <span class="p">};</span>

        <span class="nx">vscode</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
          <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">,</span>
          <span class="na">text</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">petObject</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
        <span class="p">});</span>
      <span class="p">}</span>
    <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update the file <em>angular-extension/webview-ui/src/main.ts</em></p>

    <ul>
      <li>Decide which component to use for bootstrapping based on the <code class="language-plaintext highlighter-rouge">data-root</code> attribute.<br />
This is actually the crucial step if you want to provide multiple webviews in one extension when you use Angular. The bootstrapping of the standalone component needs to be dynamic, and this is implemented by inspecting an attribute added to the webview html in the extension code.</li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">bootstrapApplication</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@angular/platform-browser</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">appConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./app/app.config</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PersonComponent</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./app/person/person.component</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PetComponent</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./app/pet/pet.component</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">currentPath</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nf">getAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">data-root</span><span class="dl">"</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nx">currentPath</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">person-root</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
  <span class="nf">bootstrapApplication</span><span class="p">(</span><span class="nx">PersonComponent</span><span class="p">,</span> <span class="nx">appConfig</span><span class="p">).</span><span class="k">catch</span><span class="p">((</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
  <span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">currentPath</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">pet-root</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
  <span class="nf">bootstrapApplication</span><span class="p">(</span><span class="nx">PetComponent</span><span class="p">,</span> <span class="nx">appConfig</span><span class="p">).</span><span class="k">catch</span><span class="p">((</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>To verify that it works</p>

<ul>
  <li>Launch the Visual Studio Code Extension(s)
    <ul>
      <li>Open <em>Run and Debug</em></li>
      <li>Ensure <em>Run Extension</em> is selected in the dropdown</li>
      <li>Click <em>Start Debugging</em> or press <em>F5</em></li>
    </ul>
  </li>
  <li>Right click on the file created in the previous example in the <em>Explorer</em></li>
  <li>Select <em>Open With…</em> - <em>Angular Pet Editor</em></li>
</ul>

<p>This should open the webview with the content of the Angular <code class="language-plaintext highlighter-rouge">PetComponent</code>.</p>

<h2 id="react-webview-implementation">React Webview Implementation</h2>

<p>To provide multiple webviews in an extension that uses React as webframework, we need to perform similar steps like before with Angular as webframework.</p>

<ul>
  <li>
    <p>Open the <em>react-extension/package.json</em></p>

    <ul>
      <li>Extend the <code class="language-plaintext highlighter-rouge">contributes</code> section to add a Pet editor for the <strong>.pet</strong> file extension:</li>
    </ul>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"contributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"customEditors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"viewType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"react-extension.personEditor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"React Person Editor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"filenamePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*.person"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"priority"</span><span class="p">:</span><span class="w"> </span><span class="s2">"option"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"viewType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"react-extension.petEditor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"React Pet Editor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"filenamePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*.pet"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"priority"</span><span class="p">:</span><span class="w"> </span><span class="s2">"option"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Create a new file <em>react-extension/src/abstractEditor.ts</em><br />
To avoid that we need to copy a lot of code for the new editor, we create an abstract base class <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code> that will be used by both editor implementations.</p>

    <ul>
      <li>Copy the content of <em>react-extension/src/personEditor.ts</em> to <em>react-extension/src/abstractEditor.ts</em></li>
      <li>Rename <code class="language-plaintext highlighter-rouge">PersonEditorProvider</code> to <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code> and make it <code class="language-plaintext highlighter-rouge">abstract</code></li>
      <li>Remove the <code class="language-plaintext highlighter-rouge">viewType</code> constant and the <code class="language-plaintext highlighter-rouge">register</code> method</li>
      <li>Add an abstract method <code class="language-plaintext highlighter-rouge">getRootComponentSelector()</code></li>
      <li>Update <code class="language-plaintext highlighter-rouge">getWebviewHtml()</code> to use <code class="language-plaintext highlighter-rouge">getRootComponentSelector()</code> as an attribute <code class="language-plaintext highlighter-rouge">data-root</code> in the <code class="language-plaintext highlighter-rouge">html</code> tag</li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractEditorProvider</span>
  <span class="k">implements</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CustomTextEditorProvider</span>
<span class="p">{</span>
  <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{}</span>

  <span class="k">public</span> <span class="k">async</span> <span class="nf">resolveCustomTextEditor</span><span class="p">(</span>
    <span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span><span class="p">,</span>
    <span class="nx">webviewPanel</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">WebviewPanel</span><span class="p">,</span>
    <span class="nx">_token</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CancellationToken</span>
  <span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// Setup initial content for the webview</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
      <span class="c1">// Enable scripts in the webview</span>
      <span class="na">enableScripts</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>

      <span class="c1">// Restrict the webview to only load resources from the `dist` and `webview-ui/build` directories</span>
      <span class="na">localResourceRoots</span><span class="p">:</span> <span class="p">[</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">),</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">webview-ui/build</span><span class="dl">"</span><span class="p">),</span>
      <span class="p">],</span>
    <span class="p">};</span>

    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nx">html</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">);</span>

    <span class="c1">// Hook up event handlers so that we can synchronize the webview with the text document.</span>
    <span class="c1">//</span>
    <span class="c1">// The text document acts as our model, so we have to sync change in the document to our</span>
    <span class="c1">// editor and sync changes in the editor back to the document.</span>
    <span class="c1">//</span>
    <span class="c1">// Remember that a single text document can also be shared between multiple custom</span>
    <span class="c1">// editors (this happens for example when you split a custom editor)</span>

    <span class="kd">const</span> <span class="nx">changeDocumentSubscription</span> <span class="o">=</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nf">onDidChangeTextDocument</span><span class="p">((</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nf">toString</span><span class="p">()</span> <span class="o">===</span> <span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nf">toString</span><span class="p">())</span> <span class="p">{</span>
          <span class="k">this</span><span class="p">.</span><span class="nf">updateWebview</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">,</span> <span class="nb">document</span><span class="p">);</span>
        <span class="p">}</span>
      <span class="p">});</span>

    <span class="c1">// Make sure we get rid of the listener when our editor is closed.</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nf">onDidDispose</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">changeDocumentSubscription</span><span class="p">.</span><span class="nf">dispose</span><span class="p">();</span>
    <span class="p">});</span>

    <span class="c1">// Receive message from the webview.</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nf">onDidReceiveMessage</span><span class="p">((</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">switch </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">case</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">:</span>
          <span class="k">this</span><span class="p">.</span><span class="nf">updateDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">text</span><span class="p">);</span>
          <span class="k">return</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">});</span>

    <span class="k">this</span><span class="p">.</span><span class="nf">updateWebview</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">,</span> <span class="nb">document</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Updates the webview with the content of the current document.
   * @param webviewPanel The webview panel to post the message to.
   * @param document The current document.
   */</span>
  <span class="k">private</span> <span class="nf">updateWebview</span><span class="p">(</span>
    <span class="nx">webviewPanel</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">WebviewPanel</span><span class="p">,</span>
    <span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Post message with text of the document to the webview</span>
    <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
      <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">text</span><span class="p">:</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getText</span><span class="p">(),</span>
    <span class="p">});</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Applies a set of text edits to a document.
   * @param document The current document.
   * @param text The text to set to the document.
   * @returns A thenable that resolves when the edit could be applied.
   */</span>
  <span class="k">private</span> <span class="nf">updateDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span><span class="p">,</span> <span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">edit</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">WorkspaceEdit</span><span class="p">();</span>
    <span class="nx">edit</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span>
      <span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">,</span>
      <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">Range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">document</span><span class="p">.</span><span class="nx">lineCount</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
      <span class="nx">text</span>
    <span class="p">);</span>
    <span class="k">return</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nf">applyEdit</span><span class="p">(</span><span class="nx">edit</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Get the static html used for the editor webviews.
   */</span>
  <span class="k">private</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="c1">// The CSS file from the React build output</span>
    <span class="kd">const</span> <span class="nx">stylesUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">assets</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">index.css</span><span class="dl">"</span>
      <span class="p">)</span>
    <span class="p">);</span>
    <span class="c1">// The JS file from the React build output</span>
    <span class="kd">const</span> <span class="nx">scriptUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
      <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">assets</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">index.js</span><span class="dl">"</span>
      <span class="p">)</span>
    <span class="p">);</span>

    <span class="kd">const</span> <span class="nx">rootSelector</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getRootComponentSelector</span><span class="p">();</span>
    <span class="kd">const</span> <span class="nx">nonce</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getNonce</span><span class="p">();</span>

    <span class="c1">// Tip: Install the es6-string-html VS Code extension to enable code highlighting below</span>
    <span class="k">return</span> <span class="cm">/*html*/</span> <span class="s2">`
    &lt;!DOCTYPE html&gt;
    &lt;html lang="en" data-root="</span><span class="p">${</span><span class="nx">rootSelector</span><span class="p">}</span><span class="s2">"&gt;
    &lt;head&gt;
      &lt;meta charset="UTF-8" /&gt;
      &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
        &lt;meta
          http-equiv="Content-Security-Policy"
          content="default-src 'none'; img-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; font-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; style-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; script-src 'nonce-</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">';"&gt;
      &lt;link rel="stylesheet" type="text/css" href="</span><span class="p">${</span><span class="nx">stylesUri</span><span class="p">}</span><span class="s2">"&gt;
    &lt;/head&gt;
    &lt;body&gt;
      &lt;div id="root"&gt;&lt;/div&gt;
      &lt;script type="module" nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">scriptUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
    &lt;/body&gt;
    &lt;/html&gt;`</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Return the selector of the root component to use.
   */</span>
  <span class="kd">abstract</span> <span class="nf">getRootComponentSelector</span><span class="p">():</span> <span class="kr">string</span><span class="p">;</span>

  <span class="cm">/**
   * A helper function that returns a unique alphanumeric identifier called a nonce.
   *
   * @remarks This function is primarily used to help enforce content security
   * policies for resources/scripts being executed in a webview context.
   *
   * @returns A nonce
   */</span>
  <span class="nf">getNonce</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">text</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">possible</span> <span class="o">=</span>
      <span class="dl">"</span><span class="s2">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789</span><span class="dl">"</span><span class="p">;</span>
    <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">32</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">text</span> <span class="o">+=</span> <span class="nx">possible</span><span class="p">.</span><span class="nf">charAt</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nf">random</span><span class="p">()</span> <span class="o">*</span> <span class="nx">possible</span><span class="p">.</span><span class="nx">length</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">text</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update the file <em>react-extension/src/personEditor.ts</em></p>

    <ul>
      <li>Extend <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code></li>
      <li>Call <code class="language-plaintext highlighter-rouge">super(context)</code> in the <code class="language-plaintext highlighter-rouge">constructor</code></li>
      <li>Add the <code class="language-plaintext highlighter-rouge">getRootComponentSelector()</code> method which returns <code class="language-plaintext highlighter-rouge">person-root</code></li>
      <li>Delete the other methods that are now defined in <code class="language-plaintext highlighter-rouge">AbstractEditorProvider</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AbstractEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./abstractEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nc">PersonEditorProvider</span> <span class="kd">extends</span> <span class="nc">AbstractEditorProvider</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="nx">viewType</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">react-extension.personEditor</span><span class="dl">"</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">super</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">public</span> <span class="k">static</span> <span class="nf">register</span><span class="p">(</span>
    <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span>
  <span class="p">):</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Disposable</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PersonEditorProvider</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">providerRegistration</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nb">window</span><span class="p">.</span><span class="nf">registerCustomEditorProvider</span><span class="p">(</span>
      <span class="nx">PersonEditorProvider</span><span class="p">.</span><span class="nx">viewType</span><span class="p">,</span>
      <span class="nx">provider</span>
    <span class="p">);</span>
    <span class="k">return</span> <span class="nx">providerRegistration</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">getRootComponentSelector</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="dl">"</span><span class="s2">person-root</span><span class="dl">"</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Create a new file <em>react-extension/src/petEditor.ts</em></p>

    <ul>
      <li>Copy the content of <em>react-extension/src/personEditor.ts</em></li>
      <li>Rename the class to <code class="language-plaintext highlighter-rouge">PetEditorProvider</code></li>
      <li>Update the value of <code class="language-plaintext highlighter-rouge">viewType</code> to <code class="language-plaintext highlighter-rouge">"react-extension.petEditor"</code></li>
      <li>Update the <code class="language-plaintext highlighter-rouge">register()</code> implementation to register the <code class="language-plaintext highlighter-rouge">PetEditorProvider</code></li>
      <li>Change the return value of <code class="language-plaintext highlighter-rouge">getRootComponentSelector()</code> to <code class="language-plaintext highlighter-rouge">pet-root</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AbstractEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./abstractEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nc">PetEditorProvider</span> <span class="kd">extends</span> <span class="nc">AbstractEditorProvider</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="nx">viewType</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">react-extension.petEditor</span><span class="dl">"</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">super</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">public</span> <span class="k">static</span> <span class="nf">register</span><span class="p">(</span>
    <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span>
  <span class="p">):</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Disposable</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PetEditorProvider</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">providerRegistration</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nb">window</span><span class="p">.</span><span class="nf">registerCustomEditorProvider</span><span class="p">(</span>
      <span class="nx">PetEditorProvider</span><span class="p">.</span><span class="nx">viewType</span><span class="p">,</span>
      <span class="nx">provider</span>
    <span class="p">);</span>
    <span class="k">return</span> <span class="nx">providerRegistration</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">getRootComponentSelector</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="dl">"</span><span class="s2">pet-root</span><span class="dl">"</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update <em>react-extension/src/extension.ts</em></p>

    <ul>
      <li>Register the <code class="language-plaintext highlighter-rouge">PetEditorProvider</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The module 'vscode' contains the Visual Studio Code extensibility API</span>
<span class="c1">// Import the module and reference it with the alias vscode in your code below</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PersonEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./personEditor</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PetEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./petEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// This method is called when your extension is activated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">activate</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Register our custom editor provider</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">PersonEditorProvider</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">context</span><span class="p">));</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">PetEditorProvider</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">context</span><span class="p">));</span>
<span class="p">}</span>

<span class="c1">// This method is called when your extension is deactivated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">deactivate</span><span class="p">()</span> <span class="p">{}</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>Now that the extension code is prepared for multiple custom editors, the webview needs to be implemented in the React webview-ui structure.</p>

<ul>
  <li>
    <p>Update <em>react-extension/src/webview-ui/src/global.d.ts</em> and add the <code class="language-plaintext highlighter-rouge">VscodeSingleSelect</code> and the <code class="language-plaintext highlighter-rouge">VscodeOption</code> web component.<br />
To use custom tag names, you must configure the TypeScript parser to recognize the custom elements. This can be done via a TypeScript definition which is described in <a href="https://vogella.com/blog/vscode-extension-webview-getting-started/#vscode-elements-2">Getting Started with Visual Studio Code Extension Development</a>.</p>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">VscodeLabel</span><span class="p">,</span>
  <span class="nx">VscodeTextfield</span><span class="p">,</span>
  <span class="nx">VscodeSingleSelect</span><span class="p">,</span>
  <span class="nx">VscodeOption</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@vscode-elements/elements</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">type</span> <span class="nx">ElementProps</span><span class="o">&lt;</span><span class="nx">I</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nb">Partial</span><span class="o">&lt;</span><span class="nb">Omit</span><span class="o">&lt;</span><span class="nx">I</span><span class="p">,</span> <span class="kr">keyof</span> <span class="nx">HTMLElement</span><span class="o">&gt;&gt;</span><span class="p">;</span>
<span class="kd">type</span> <span class="nx">CustomEventHandler</span><span class="o">&lt;</span><span class="nx">E</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">(</span><span class="nx">e</span><span class="p">:</span> <span class="nx">E</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>

<span class="kd">type</span> <span class="nx">WebComponentProps</span><span class="o">&lt;</span><span class="nx">I</span> <span class="kd">extends</span> <span class="nx">HTMLElement</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">DetailedHTMLProps</span><span class="o">&lt;</span>
  <span class="nx">React</span><span class="p">.</span><span class="nx">HTMLAttributes</span><span class="o">&lt;</span><span class="nx">I</span><span class="o">&gt;</span><span class="p">,</span>
  <span class="nx">I</span>
<span class="o">&gt;</span> <span class="o">&amp;</span>
  <span class="nx">ElementProps</span><span class="o">&lt;</span><span class="nx">I</span><span class="o">&gt;</span><span class="p">;</span>

<span class="kr">declare</span> <span class="kr">module</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span> <span class="p">{</span>
  <span class="k">namespace</span> <span class="nx">JSX</span> <span class="p">{</span>
    <span class="kr">interface</span> <span class="nx">IntrinsicElements</span> <span class="p">{</span>
      <span class="dl">"</span><span class="s2">vscode-label</span><span class="dl">"</span><span class="p">:</span> <span class="nx">WebComponentProps</span><span class="o">&lt;</span><span class="nx">VscodeLabel</span><span class="o">&gt;</span><span class="p">;</span>
      <span class="dl">"</span><span class="s2">vscode-textfield</span><span class="dl">"</span><span class="p">:</span> <span class="nx">WebComponentProps</span><span class="o">&lt;</span><span class="nx">VscodeTextfield</span><span class="o">&gt;</span><span class="p">;</span>
      <span class="dl">"</span><span class="s2">vscode-single-select</span><span class="dl">"</span><span class="p">:</span> <span class="nx">WebComponentProps</span><span class="o">&lt;</span><span class="nx">VscodeSingleSelect</span><span class="o">&gt;</span><span class="p">;</span>
      <span class="dl">"</span><span class="s2">vscode-option</span><span class="dl">"</span><span class="p">:</span> <span class="nx">WebComponentProps</span><span class="o">&lt;</span><span class="nx">VscodeOption</span><span class="o">&gt;</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>Rename <em>react-extension/src/webview-ui/src/App.tsx</em> to <em>react-extension/src/webview-ui/src/Person.tsx</em></li>
  <li>In <em>react-extension/src/webview-ui/src/Person.tsx</em> rename the <code class="language-plaintext highlighter-rouge">App</code> function to <code class="language-plaintext highlighter-rouge">Person</code></li>
  <li>Create a new file <em>react-extension/src/webview-ui/src/Pet.tsx</em></li>
  <li>
    <p>Copy the content of <em>react-extension/src/webview-ui/src/Person.tsx</em> to the new file and adapt the code to handle a pet instead of a person</p>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">vscode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./utilities/vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">./App.css</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">@vscode-elements/elements/dist/vscode-label</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">@vscode-elements/elements/dist/vscode-textfield</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">@vscode-elements/elements/dist/vscode-single-select</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">@vscode-elements/elements/dist/vscode-option</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">function</span> <span class="nf">Pet</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">petObject</span><span class="p">,</span> <span class="nx">setPetObject</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="nx">loadState</span><span class="p">);</span>

  <span class="cm">/**
   * Load the initial state via vscode API or return an empty object as initial state.
   */</span>
  <span class="kd">function</span> <span class="nf">loadState</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// eslint-disable-next-line @typescript-eslint/no-explicit-any</span>
    <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nf">getState</span><span class="p">()</span> <span class="kd">as </span><span class="kr">any</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">text</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="p">{</span>
      <span class="na">name</span><span class="p">:</span> <span class="dl">""</span><span class="p">,</span>
      <span class="na">species</span><span class="p">:</span> <span class="dl">""</span><span class="p">,</span>
    <span class="p">};</span>
  <span class="p">}</span>

  <span class="c1">// Handle messages sent from the extension to the webview</span>
  <span class="nb">window</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// The json data that the extension sent</span>
    <span class="k">switch </span><span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">case</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">message</span><span class="p">.</span><span class="nx">text</span><span class="p">;</span>

        <span class="c1">// Update our webview's content</span>
        <span class="nf">updateContent</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>

        <span class="c1">// Then persist state information.</span>
        <span class="c1">// This state is returned in the call to `vscode.getState` below when a webview is reloaded.</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nf">setState</span><span class="p">({</span> <span class="nx">text</span> <span class="p">});</span>

        <span class="k">return</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">});</span>

  <span class="cm">/**
   * Update the data shown in the document in the webview.
   */</span>
  <span class="kd">function</span> <span class="nf">updateContent</span><span class="p">(</span><span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">text</span> <span class="o">!==</span> <span class="dl">""</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">parsed</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>
      <span class="nf">setPetObject</span><span class="p">({</span>
        <span class="na">name</span><span class="p">:</span> <span class="nx">parsed</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span>
        <span class="na">species</span><span class="p">:</span> <span class="nx">parsed</span><span class="p">.</span><span class="nx">species</span><span class="p">,</span>
      <span class="p">});</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Update the document in the extension.
   */</span>
  <span class="kd">function</span> <span class="nf">updateDocument</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
      <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">text</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">petObject</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
    <span class="p">});</span>
  <span class="p">}</span>

  <span class="k">return </span><span class="p">(</span>
    <span class="o">&lt;&gt;</span>
      <span class="o">&lt;</span><span class="nx">main</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">main</span><span class="dl">"</span><span class="o">&gt;</span>
        <span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;</span><span class="nx">React</span> <span class="nx">Pet</span> <span class="nx">Editor</span><span class="o">&lt;</span><span class="sr">/h1</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">content</span><span class="dl">"</span><span class="o">&gt;</span>
          <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">pet</span><span class="dl">"</span><span class="o">&gt;</span>
            <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">row</span><span class="dl">"</span><span class="o">&gt;</span>
              <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">label</span> <span class="nx">htmlFor</span><span class="o">=</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">vscode-label</span><span class="dl">"</span><span class="o">&gt;</span>
                <span class="nx">Name</span><span class="p">:</span>
              <span class="o">&lt;</span><span class="sr">/vscode-label</span><span class="err">&gt;
</span>              <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="o">&gt;</span>
                <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">textfield</span>
                  <span class="kd">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text</span><span class="dl">"</span>
                  <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span>
                  <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">vscode-textfield</span><span class="dl">"</span>
                  <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">petObject</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span>
                  <span class="nx">onInput</span><span class="o">=</span><span class="p">{(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                    <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
                    <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">;</span>
                    <span class="c1">// wait 500 ms before updating the document</span>
                    <span class="c1">// only update if in the meantime no other input was given</span>
                    <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                      <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                        <span class="nx">petObject</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
                        <span class="nf">updateDocument</span><span class="p">();</span>
                      <span class="p">}</span>
                    <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
                  <span class="p">}}</span>
                <span class="sr">/</span><span class="err">&gt;
</span>              <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">row</span><span class="dl">"</span><span class="o">&gt;</span>
              <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">label</span> <span class="nx">htmlFor</span><span class="o">=</span><span class="dl">"</span><span class="s2">species</span><span class="dl">"</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">vscode-label</span><span class="dl">"</span><span class="o">&gt;</span>
                <span class="nx">Species</span><span class="p">:</span>
              <span class="o">&lt;</span><span class="sr">/vscode-label</span><span class="err">&gt;
</span>              <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="o">&gt;</span>
                <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">single</span><span class="o">-</span><span class="nx">select</span>
                  <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">species</span><span class="dl">"</span>
                  <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">petObject</span><span class="p">.</span><span class="nx">species</span><span class="p">}</span>
                  <span class="nx">onInput</span><span class="o">=</span><span class="p">{(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                    <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
                    <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">;</span>
                    <span class="c1">// wait 500 ms before updating the document</span>
                    <span class="c1">// only update if in the meantime no other input was given</span>
                    <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                      <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                        <span class="nx">petObject</span><span class="p">.</span><span class="nx">species</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
                        <span class="nf">updateDocument</span><span class="p">();</span>
                      <span class="p">}</span>
                    <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
                  <span class="p">}}</span>
                <span class="o">&gt;</span>
                  <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">option</span><span class="o">&gt;-&lt;</span><span class="sr">/vscode-option</span><span class="err">&gt;
</span>                  <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">option</span> <span class="nx">description</span><span class="o">=</span><span class="dl">"</span><span class="s2">bird</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Bird</span><span class="o">&lt;</span><span class="sr">/vscode-option</span><span class="err">&gt;
</span>                  <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">option</span> <span class="nx">description</span><span class="o">=</span><span class="dl">"</span><span class="s2">cat</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Cat</span><span class="o">&lt;</span><span class="sr">/vscode-option</span><span class="err">&gt;
</span>                  <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">option</span> <span class="nx">description</span><span class="o">=</span><span class="dl">"</span><span class="s2">dog</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Dog</span><span class="o">&lt;</span><span class="sr">/vscode-option</span><span class="err">&gt;
</span>                <span class="o">&lt;</span><span class="sr">/vscode-single-select</span><span class="err">&gt;
</span>              <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>          <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="sr">/main</span><span class="err">&gt;
</span>    <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt;
</span>  <span class="p">);</span>
<span class="p">}</span>

<span class="k">export</span> <span class="k">default</span> <span class="nx">Pet</span><span class="p">;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update <em>react-extension/src/webview-ui/src/main.tsx</em></p>

    <ul>
      <li>Decide which component to use as root based on the <code class="language-plaintext highlighter-rouge">data-root</code> attribute<br />
This is actually the crucial step if you want to provide multiple webviews in one extension when you use React. The definition of the root component needs to be dynamic, and this is implemented by inspecting an attribute added to the webview html in the extension code.</li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">StrictMode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createRoot</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-dom/client</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">./index.css</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Person</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Person.tsx</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Pet</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Pet.tsx</span><span class="dl">"</span><span class="p">;</span>

<span class="nf">createRoot</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">root</span><span class="dl">"</span><span class="p">)</span><span class="o">!</span><span class="p">).</span><span class="nf">render</span><span class="p">(</span>
  <span class="o">&lt;</span><span class="nx">StrictMode</span><span class="o">&gt;</span>
    <span class="o">&lt;</span><span class="nx">RootComponent</span> <span class="o">/&gt;</span>
  <span class="o">&lt;</span><span class="sr">/StrictMode</span><span class="err">&gt;
</span><span class="p">);</span>

<span class="c1">// eslint-disable-next-line react-refresh/only-export-components</span>
<span class="kd">function</span> <span class="nf">RootComponent</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">currentPath</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nf">getAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">data-root</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">currentPath</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">person-root</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="o">&lt;</span><span class="nx">Person</span> <span class="o">/&gt;</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">currentPath</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">pet-root</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="o">&lt;</span><span class="nx">Pet</span> <span class="o">/&gt;</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>To verify that it works</p>

<ul>
  <li>Launch the Visual Studio Code Extension(s)
    <ul>
      <li>Open <em>Run and Debug</em></li>
      <li>Ensure <em>Run Extension</em> is selected in the dropdown</li>
      <li>Click <em>Start Debugging</em> or press <em>F5</em></li>
    </ul>
  </li>
  <li>Right click on the file created in the previous example in the <em>Explorer</em></li>
  <li>Select <em>Open With…</em> - <em>React Pet Editor</em></li>
</ul>

<p>This should open the webview with the content of the React <code class="language-plaintext highlighter-rouge">Pet</code> component.</p>

<h2 id="bonus-automatic-termination-of-watch-tasks">Bonus: Automatic Termination of Watch Tasks</h2>

<p>In the setup of this project multiple <strong>watch</strong> tasks are started when you launch the Visual Studio Code Extensions for debugging via <em>Run and Debug</em> or by pressing F5 or F11.
I configured the <strong>watch</strong> tasks as <code class="language-plaintext highlighter-rouge">defaultBuildTask</code> to get code changes directly reflected in the running instance.
This is similar to the <strong>Hot Code Replace</strong> debugging technique in Java.
After a code change in a webview implementation, the changes can be directly seen by either closing and reopening the webview, or by reloading the webview by pressing F1 - <em>Developer: Reload Webviews</em>. To reload the <strong>Extension Development Host</strong> after changes in the extension code, you can either click on the debug restart action or press <code class="language-plaintext highlighter-rouge">Ctrl + R</code> / <code class="language-plaintext highlighter-rouge">Cmd + R</code> in the <strong>Extension Development Host</strong> window. This makes the development flow a bit more comfortable compared to always having to close and restart the debugging launch configuration.</p>

<p>There is a nasty side effect with this setup. If you close the <strong>Extension Development Host</strong>, the watch tasks are not automatically stopped.
So if you want to start a new debugging session afterwards, nothing happens, because the watch scripts are still running and therefore the patterns that are used to match the started state are not matched in a new start. To make the start of a debug instance work again, you first need to stop the running watch tasks, so they are started freshly. As this is quite annoying when it happens often that you kill the <strong>Extension Development Host</strong> instead of reloading it, I was searching for a solution. A colleague of mine found a nice solution for this, and I modified it to be even more convenient. Of course I found the necessary information in the web, but you know, the blog posts are my external memory.</p>

<p>To automatically terminate the watch tasks</p>

<ul>
  <li>Open the file <em>.vscode/tasks.json</em>
    <ul>
      <li>Add the following <code class="language-plaintext highlighter-rouge">Terminate Tasks</code> to the <code class="language-plaintext highlighter-rouge">tasks</code> and the <code class="language-plaintext highlighter-rouge">terminate</code> input to the <code class="language-plaintext highlighter-rouge">inputs</code>
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"tasks"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
  </span><span class="err">...</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Terminate Tasks"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"echo ${input:terminate}"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shell"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"presentation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"reveal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"never"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"close"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"inputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"terminate"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"command"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"workbench.action.tasks.terminate"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="s2">"terminateAll"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>The above <code class="language-plaintext highlighter-rouge">Terminate Tasks</code> configuration triggers the <code class="language-plaintext highlighter-rouge">workbench.action.tasks.terminate</code> command with the argument <code class="language-plaintext highlighter-rouge">terminateAll</code>.
So actually this kills all running tasks.
Via the <code class="language-plaintext highlighter-rouge">close</code> property of the <code class="language-plaintext highlighter-rouge">presentation</code> task property, we configure that the terminal the tasks runs in is closed the task exits.
This way our Terminal list stays clean.</p>

<ul>
  <li>Open the file <em>.vscode/launch.json</em>
    <ul>
      <li>Add <code class="language-plaintext highlighter-rouge">"postDebugTask": "Terminate Tasks"</code> to the <code class="language-plaintext highlighter-rouge">Run Extension</code> configuration
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.2.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Run Extension"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"extensionHost"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/vscode-extension"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/angular-extension"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/react-extension"</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"outFiles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"${workspaceFolder}/vscode-extension/out/**/*.js"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"${workspaceFolder}/angular-extension/dist/**/*.js"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"${workspaceFolder}/react-extension/dist/**/*.js"</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"preLaunchTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${defaultBuildTask}"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"postDebugTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Terminate Tasks"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="err">...</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>If you now start the debugging instance via the <code class="language-plaintext highlighter-rouge">Run Extension</code> launch configuration, you will again see that the three watch scripts are started before the <strong>Extension Development Host</strong> comes up. If you now stop the <strong>Extension Development Host</strong>, the watch scripts will be stopped also.</p>

<p><em><strong>Note:</strong></em>
This will also affect the restart behavior of the <strong>Extension Development Host</strong>. Without the <code class="language-plaintext highlighter-rouge">postDebugTask</code> the <strong>Extension Development Host</strong> will restart while the watch tasks keep running. With the <code class="language-plaintext highlighter-rouge">postDebugTask</code> the watch tasks will be killed and also restarted. If you are working on the extension sources and need to restart the <strong>Extension Development Host</strong> to make the changes visible, it might be annoying that the watch scripts are killed and restarted. But if you are working on the webview sources and don’t need to restart the <strong>Extension Development Host</strong>, this change might not really be noticable.</p>

<h2 id="conclusion">Conclusion</h2>

<p>With a Vanilla Javascript and HTML webview, it is straight forward to provide multiple webviews in one extension. The webview HTML is provided by each custom editor provider implementation. And the special handling in the Javascript needs to be implemented accordingly.</p>

<p>Using a webframework like Angular or React the crucial fact is to determine the root component dynamically. If you know how this can be done, it is quite easy, but finding out how to achieve this was indeed a journey.</p>

<p>If you are annoyed by watch scripts that keep running after closing the <strong>Extension Development Host</strong>, the <code class="language-plaintext highlighter-rouge">postDebugTask</code> of a launch configuration could be helpful. But there are also use cases where the behavior that is introduced by the <code class="language-plaintext highlighter-rouge">postDebugTask</code> could be annoying, e.g. on restarting the <strong>Extension Development Host</strong>. So you need to use that configuration based on your use cases and personal development/testing preferences.</p>

<p>The sources for this and the following tutorials are located in my <a href="https://github.com/fipro78/vscode_theia_cookbook">Github repository</a>.</p>]]></content><author><name>Dirk Fauth</name></author><category term="fipro" /><category term="vscode" /><category term="typescript" /><category term="angular" /><category term="react" /><summary type="html"><![CDATA[In my previous blog post Getting Started with Visual Studio Code Extension Development I described how to create a Visual Studio Code Extension that contributes a custom editor that is implemented using a webview. And I compared how this can be done using vanilla HTML and Javascript, using Angular and using React. Only a single webview is contributed by each Visual Studio Code Extension in the examples of that blog post, which is sufficient for a lot of use cases. But after the publishing of that blog post I was asked several times how to contribute multiple editors with a single extension. It turned out that this is not as intuitive as initially thought, so I decided to write a new blog post dedicated to this topic.]]></summary></entry><entry><title type="html">Getting Started with Visual Studio Code Extension Development</title><link href="https://vogella.com/blog/vscode-extension-webview-getting-started/" rel="alternate" type="text/html" title="Getting Started with Visual Studio Code Extension Development" /><published>2025-03-05T00:00:00+00:00</published><updated>2025-03-05T00:00:00+00:00</updated><id>https://vogella.com/blog/vscode-extension-webview-getting-started</id><content type="html" xml:base="https://vogella.com/blog/vscode-extension-webview-getting-started/"><![CDATA[<p>Over the past years I wrote several blog posts about Java, Eclipse and OSGi. While I still like those technologies very much, I can’t ignore that Visual Studio Code, Eclipse Theia and several web technologies became more important.
The following blog posts will therefore cover these topics, to help developers like me, that need to switch and provide tools based on Visual Studio Code and/or Eclipse Theia.</p>

<p>I will start my new series of blog post with this tutorial about Visual Studio Code Extension development.
This tutorial is a <em>Getting Started</em> for developing Visual Studio Code Extensions that contribute a custom editor using a webview. 
It is intended for developers that are not familiar with Visual Studio Code Extension development and covers the following topics:</p>

<ul>
  <li>Project setup using a Dev Container</li>
  <li>Developing a custom editor using a webview with Vanilla HTML and Javascript</li>
  <li>Developing a custom editor using a webview with Angular</li>
  <li>Developing a custom editor using a webview with React</li>
  <li>Usage of the VSCode Elements web component library to get an almost native Visual Studio Code look and feel</li>
</ul>

<p>Let’s start!</p>

<h2 id="dev-container">Dev Container</h2>

<p>When starting a new project, it is recommended to define a <em>Dev Container</em> to</p>

<ul>
  <li>reduce the time a new developer in the project needs to setup the environment</li>
  <li>encapsulate the development environment for the project</li>
</ul>

<p>This makes it easy to get a working environment, without having to install all required tools locally.
To make this work you of course need to have at least:</p>

<ul>
  <li><a href="https://code.visualstudio.com/">Visual Studio Code</a></li>
  <li><a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack">Visual Studio Code Remote Development Extension Pack</a></li>
  <li>Docker</li>
</ul>

<p>Working on Windows you can either use <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a>, or if you work inside a WSL you can also install Docker in the WSL distribution directly.
Note that on Windows it is recommended to checkout the sources in the WSL and start Visual Studio Code from there.
This improves the performance of the Dev Container, because there is no performance loss caused by disk performance issues when transforming from a Windows file system to a Linux file system in the container.
See <a href="https://code.visualstudio.com/remote/advancedcontainers/improve-performance">Improve disk performance</a> for further information and alternatives.</p>

<p><em><strong>Note:</strong></em><br />
A possible WSL setup is described in the <a href="https://github.com/fipro78/vscode_theia_cookbook/blob/main/tutorials/vscode_extension_webview_getting_started.md">WSL Setup</a> tutorial in this repository.</p>

<p>Prepare the project to provide a Dev Container for the development:</p>

<ul>
  <li>Create a folder <em>.devcontainer</em></li>
  <li>
    <p>Create a file <em>devcontainer.json</em><br />
The simplest form of a <em>devcontainer.json</em> for starting to develop a Visual Studio Code Extension could look like this:</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">For</span><span class="w"> </span><span class="err">format</span><span class="w"> </span><span class="err">details,</span><span class="w"> </span><span class="err">see</span><span class="w"> </span><span class="err">https://aka.ms/devcontainer.json.</span><span class="w"> </span><span class="err">For</span><span class="w"> </span><span class="err">config</span><span class="w"> </span><span class="err">options,</span><span class="w"> </span><span class="err">see</span><span class="w"> </span><span class="err">the</span><span class="w">
</span><span class="err">//</span><span class="w"> </span><span class="err">README</span><span class="w"> </span><span class="err">at:</span><span class="w"> </span><span class="err">https://github.com/devcontainers/templates/tree/main/src/typescript-node</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Node.js &amp; TypeScript"</span><span class="p">,</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="err">Or</span><span class="w"> </span><span class="err">use</span><span class="w"> </span><span class="err">a</span><span class="w"> </span><span class="err">Dockerfile</span><span class="w"> </span><span class="err">or</span><span class="w"> </span><span class="err">Docker</span><span class="w"> </span><span class="err">Compose</span><span class="w"> </span><span class="err">file.</span><span class="w"> </span><span class="err">More</span><span class="w"> </span><span class="err">info:</span><span class="w"> </span><span class="err">https://containers.dev/guide/dockerfile</span><span class="w">
  </span><span class="nl">"image"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mcr.microsoft.com/devcontainers/typescript-node:1-20-bookworm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"customizations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"vscode"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"extensions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"dbaeumer.vscode-eslint"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"amodio.tsl-problem-matcher"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"ms-vscode.extension-test-runner"</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="err">Features</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">add</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">dev</span><span class="w"> </span><span class="err">container.</span><span class="w"> </span><span class="err">More</span><span class="w"> </span><span class="err">info:</span><span class="w"> </span><span class="err">https://containers.dev/features.</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="nl">"features"</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="err">Use</span><span class="w"> </span><span class="err">'forwardPorts'</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">make</span><span class="w"> </span><span class="err">a</span><span class="w"> </span><span class="err">list</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">ports</span><span class="w"> </span><span class="err">inside</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">container</span><span class="w"> </span><span class="err">available</span><span class="w"> </span><span class="err">locally.</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="nl">"forwardPorts"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="err">Enable</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">connect</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">dev</span><span class="w"> </span><span class="err">container</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">host</span><span class="w"> </span><span class="err">network</span><span class="p">,</span><span class="w"> </span><span class="err">needed</span><span class="w"> </span><span class="err">in</span><span class="w"> </span><span class="err">case</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">WSL</span><span class="w"> </span><span class="err">with</span><span class="w"> </span><span class="err">networkingMode=mirrored</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="nl">"runArgs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"--network=host"</span><span class="p">],</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="err">Use</span><span class="w"> </span><span class="err">'postCreateCommand'</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">run</span><span class="w"> </span><span class="err">commands</span><span class="w"> </span><span class="err">after</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">container</span><span class="w"> </span><span class="err">is</span><span class="w"> </span><span class="err">created.</span><span class="w">
  </span><span class="nl">"postCreateCommand"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm install -g npm yo generator-code @vscode/vsce"</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="err">Uncomment</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">connect</span><span class="w"> </span><span class="err">as</span><span class="w"> </span><span class="err">root</span><span class="w"> </span><span class="err">instead.</span><span class="w"> </span><span class="err">More</span><span class="w"> </span><span class="err">info:</span><span class="w"> </span><span class="err">https://aka.ms/dev-containers-non-root.</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="nl">"remoteUser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<p>The above <em>devcontainer.json</em> uses the predefined Dev Container template for Typescript and Node, adds the recommended extensions and installs a new version of <code class="language-plaintext highlighter-rouge">npm</code>, the code generator for Visual Studio Code Extensions and the Visual Studio Code Extension Manager used for packaging.</p>

<p>Dependent on the framework you want to use for implementing the webview, you might want to add additional extensions, e.g. the Angular Language Service <a href="https://marketplace.visualstudio.com/items?itemName=Angular.ng-template">angular.ng-template</a>.</p>

<p>I also have some Visual Studio Code Extension that I like, for example the <a href="https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one">Markdown All in One</a> and the <a href="https://marketplace.visualstudio.com/items?itemName=spmeesseman.vscode-taskexplorer">Task Explorer</a>.
But of course, the selection of Visual Studio Code Extensions is opinionated, and therefore feel free to add or remove dependent on your project needs.</p>

<p>To further customize the Dev Container, you basically have three options:</p>

<ul>
  <li>extend the <code class="language-plaintext highlighter-rouge">postCreateCommand</code> in the <em>devcontainer.json</em><br />
This makes sense if you have only few commands to execute, like the installation of the code generator. But it becomes quite uncomfortable when you want to execute several commands.</li>
  <li>use a script file that contains the commands to execute and call that script file in <code class="language-plaintext highlighter-rouge">postCreateCommand</code>, e.g. like this <code class="language-plaintext highlighter-rouge">"postCreateCommand": "bash ./.devcontainer/postCreateCommand.sh"</code><br />
This has the advantage that you can call multiple commands in a bash script which is more comfortable than writing it in a single line in JSON.</li>
  <li>use a dedicated <em>Dockerfile</em> that extends the default dev container<br />
This makes especially sense if you need to add additional files or need to extend the default dev container in several ways.</li>
</ul>

<p>Once you are done with creating the Dev Container, open the workspace in the Dev Container:</p>

<ul>
  <li>Press <em>F1</em> - <em>Dev Containers: Reopen in Container</em></li>
</ul>

<p>Further information about Visual Studio Code development container:</p>

<ul>
  <li><a href="https://code.visualstudio.com/docs/remote/containers">Developing inside a Container</a></li>
  <li><a href="https://code.visualstudio.com/docs/remote/create-dev-container">Create a development container</a></li>
</ul>

<h2 id="project-structure">Project Structure</h2>

<p>In the following sections we will create Visual Studio Code Extensions that provide a custom editor using the Webview API.
I will guide you through the creation of the projects by using different frameworks.
The reason is to get an idea about how to use different Javascript frameworks for the webview development and to compare the approaches.</p>

<p>The ideas for the following descriptions are based on the <a href="https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/frameworks">Webview UI Toolkit Sample Extensions</a> repository.</p>

<p><strong><em>Note:</em></strong><br />
The Webview UI Toolkit had the intention to provide a component library for building webview-based extensions in Visual Studio Code.
The main idea was to allow the creation of user interfaces that look like the native user interface of Visual Studio Code.</p>

<p>The Webview UI Toolkit itself is deprecated, but the project structures in the examples repository are still interesting.
Basically you create a new Visual Studio Code Extension project, and inside that project you create a subfolder for the webview-ui.</p>

<p>The following implementations will be shown:</p>

<ul>
  <li>Webview with Vanilla HTML and Javascript</li>
  <li><a href="#angular-webview-implementation">Angular Webview Implementation</a></li>
  <li><a href="#react-webview-implementation">React Webview Implementation</a></li>
</ul>

<p>Additionally I will show how to use the <a href="https://vscode-elements.github.io/">VSCode Elements</a> component library for implementing a webview.</p>

<p><em><strong>Note:</strong></em><br />
If you are only interested in one of the implementations, you can jump directly to the corresponding section.</p>

<h2 id="visual-studio-code-extension-project">Visual Studio Code Extension Project</h2>

<p>After the Dev Container is ready, we can start to create the project structure.</p>

<p>As mentioned before, in this tutorial the same editor will be created using different frameworks. But the first steps are always the same:</p>

<ul>
  <li>Create the Visual Studio Code Extension project</li>
  <li>Implement the Visual Studio Code Extension<br />
In our case this means to contribute a custom editor.</li>
  <li>Implement the webview<br />
This is specific to the used framework.</li>
</ul>

<h3 id="create-the-visual-studio-code-extension-project">Create the Visual Studio Code Extension project</h3>

<p>First create a new Visual Studio Code Extension project:</p>

<ul>
  <li>
    <p>Open a <strong>Terminal</strong> and execute the following command</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yo code
</code></pre></div>    </div>
  </li>
  <li>
    <p>Answer the questions of the wizard for example like shown below:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? vscode-extension
# ? What's the identifier of your extension? vscode-extension
# ? What's the description of your extension? LEAVE BLANK
# ? Initialize a git repository? N
# ? Which bundler to use? unbundled
# ? Which package manager to use? npm

# ? Do you want to open the new folder with Visual Studio Code? Skip
</code></pre></div>    </div>
  </li>
</ul>

<p>Further information:</p>

<ul>
  <li><a href="https://code.visualstudio.com/api/get-started/your-first-extension">Your First Extension</a></li>
</ul>

<p>A new subfolder <em>vscode-extension</em> will be created that contains the sources of the Visual Studio Code Extension.
We keep the subfolder as we will add further projects in this repository. To make the setup work with the Visual Studio Code Extension in a subfolder, the following modifications are needed:</p>

<ul>
  <li>
    <p>In the opened <strong>Terminal</strong> move the <em>vscode-extension/.vscode</em> folder to the root folder. This is necessary so the Visual Studio Code settings are resolved from the project root and not a subfolder.</p>

    <div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">mv vscode-extension/.vscode .
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Edit the <em>.vscode/launch.json</em> and add the project folder to the <code class="language-plaintext highlighter-rouge">extensionDevelopmentPath</code> and the <code class="language-plaintext highlighter-rouge">outFiles</code></p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
	</span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/vscode-extension"</span><span class="w">
</span><span class="p">]</span><span class="err">,</span><span class="w">
</span><span class="nl">"outFiles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
	</span><span class="s2">"${workspaceFolder}/vscode-extension/out/**/*.js"</span><span class="w">
</span><span class="p">]</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Edit the <em>.vscode/tasks.json</em> and change the working directory the existing task to the project directory</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/vscode-extension"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<p>To verify that the setup works, open the file <em>vscode-extension/src/extension.ts</em> and press F5 to start a new Visual Studio Code instance with the extension, open the <strong>Command Palette</strong> (CTRL + SHIFT + P) and search for <em>Hello</em> to run the command.</p>

<ul>
  <li>
    <p>Create a <em>.gitignore</em> in the repository root<br />
As we did not initialize a git repository by the code generator, there is no <em>.gitignore</em>. We therefore need to create one ourselves to ensure that not too much will be added to the repository.</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Compiled output
dist
out
tmp
out-tsc

# Node
node_modules

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
*.vsix
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="implement-the-visual-studio-code-extension">Implement the Visual Studio Code Extension</h3>

<p>Instead of showing a message or opening a simple example view, we will create a custom editor.</p>

<ul>
  <li>
    <p>Open the <em>vscode-extension/package.json</em></p>

    <ul>
      <li>
        <p>Replace the <code class="language-plaintext highlighter-rouge">contributes</code> section with the following snippet:</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"contributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"customEditors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"viewType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vscode-extension.personEditor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Visual Studio Code Person Editor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"filenamePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*.person"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"priority"</span><span class="p">:</span><span class="w"> </span><span class="s2">"default"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Create a new file <em>vscode-extension/src/personEditor.ts</em></p>

    <ul>
      <li>
        <p>Implement <code class="language-plaintext highlighter-rouge">vscode.CustomTextEditorProvider</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nc">PersonEditorProvider</span>
  <span class="k">implements</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CustomTextEditorProvider</span>
<span class="p">{</span>
  <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="nx">viewType</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">vscode-extension.personEditor</span><span class="dl">"</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Add the following <code class="language-plaintext highlighter-rouge">register()</code> method that is used to register the provider via <code class="language-plaintext highlighter-rouge">vscode.window.registerCustomEditorProvider()</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="nf">register</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">):</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Disposable</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PersonEditorProvider</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">providerRegistration</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nb">window</span><span class="p">.</span><span class="nf">registerCustomEditorProvider</span><span class="p">(</span>
    <span class="nx">PersonEditorProvider</span><span class="p">.</span><span class="nx">viewType</span><span class="p">,</span>
    <span class="nx">provider</span>
  <span class="p">);</span>
  <span class="k">return</span> <span class="nx">providerRegistration</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Implement <code class="language-plaintext highlighter-rouge">resolveCustomTextEditor()</code> that uses a webview</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">async</span> <span class="nf">resolveCustomTextEditor</span><span class="p">(</span>
  <span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span><span class="p">,</span>
  <span class="nx">webviewPanel</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">WebviewPanel</span><span class="p">,</span>
  <span class="nx">_token</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">CancellationToken</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="c1">// Setup initial content for the webview</span>
  <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="c1">// Enable scripts in the webview</span>
    <span class="na">enableScripts</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="p">};</span>

  <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nx">html</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">);</span>

  <span class="c1">// Hook up event handlers so that we can synchronize the webview with the text document.</span>
  <span class="c1">//</span>
  <span class="c1">// The text document acts as our model, so we have to sync change in the document to our</span>
  <span class="c1">// editor and sync changes in the editor back to the document.</span>
  <span class="c1">//</span>
  <span class="c1">// Remember that a single text document can also be shared between multiple custom</span>
  <span class="c1">// editors (this happens for example when you split a custom editor)</span>

  <span class="kd">const</span> <span class="nx">changeDocumentSubscription</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nf">onDidChangeTextDocument</span><span class="p">(</span>
    <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nf">toString</span><span class="p">()</span> <span class="o">===</span> <span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nf">toString</span><span class="p">())</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nf">updateWebview</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">,</span> <span class="nb">document</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">);</span>

  <span class="c1">// Make sure we get rid of the listener when our editor is closed.</span>
  <span class="nx">webviewPanel</span><span class="p">.</span><span class="nf">onDidDispose</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">changeDocumentSubscription</span><span class="p">.</span><span class="nf">dispose</span><span class="p">();</span>
  <span class="p">});</span>

  <span class="k">this</span><span class="p">.</span><span class="nf">updateWebview</span><span class="p">(</span><span class="nx">webviewPanel</span><span class="p">,</span> <span class="nb">document</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Implement the bidirectional messaging between webview and editor</p>

        <ul>
          <li>
            <p>Add the following code in <code class="language-plaintext highlighter-rouge">resolveCustomTextEditor()</code></p>

            <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Receive message from the webview.</span>
<span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nf">onDidReceiveMessage</span><span class="p">((</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">switch </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">case</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">:</span>
      <span class="k">this</span><span class="p">.</span><span class="nf">updateDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">text</span><span class="p">);</span>
      <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div>            </div>
          </li>
          <li>
            <p>Add the following methods to the <code class="language-plaintext highlighter-rouge">PersonEditorProvider</code></p>

            <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Updates the webview with the content of the current document.
* @param webviewPanel The webview panel to post the message to.
* @param document The current document.
*/</span>
<span class="k">private</span> <span class="nf">updateWebview</span><span class="p">(</span>
  <span class="nx">webviewPanel</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">WebviewPanel</span><span class="p">,</span>
  <span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span>
<span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Post message with text of the document to the webview</span>
  <span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
    <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">text</span><span class="p">:</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getText</span><span class="p">(),</span>
  <span class="p">});</span>
<span class="p">}</span>

<span class="cm">/**
 * Applies a set of text edits to a document.
* @param document The current document.
* @param text The text to set to the document.
* @returns A thenable that resolves when the edit could be applied.
*/</span>
<span class="k">private</span> <span class="nf">updateDocument</span><span class="p">(</span><span class="nb">document</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">TextDocument</span><span class="p">,</span> <span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">edit</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">WorkspaceEdit</span><span class="p">();</span>
  <span class="nx">edit</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span>
    <span class="nb">document</span><span class="p">.</span><span class="nx">uri</span><span class="p">,</span>
    <span class="k">new</span> <span class="nx">vscode</span><span class="p">.</span><span class="nc">Range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">document</span><span class="p">.</span><span class="nx">lineCount</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
    <span class="nx">text</span>
  <span class="p">);</span>
  <span class="k">return</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">workspace</span><span class="p">.</span><span class="nf">applyEdit</span><span class="p">(</span><span class="nx">edit</span><span class="p">);</span>
<span class="p">}</span>

<span class="cm">/**
 * Get the static html used for the editor webviews.
*/</span>
<span class="k">private</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
  <span class="c1">// TODO implement</span>
  <span class="k">return</span> <span class="dl">""</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>            </div>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <p>Change <em>vscode-extension/src/extension.ts</em></p>

    <ul>
      <li>
        <p>Replace the existing example code with the following snippet</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The module 'vscode' contains the Visual Studio Code extensibility API</span>
<span class="c1">// Import the module and reference it with the alias vscode in your code below</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PersonEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./personEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// This method is called when your extension is activated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">activate</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Register our custom editor provider</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">PersonEditorProvider</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">context</span><span class="p">));</span>
<span class="p">}</span>

<span class="c1">// This method is called when your extension is deactivated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">deactivate</span><span class="p">()</span> <span class="p">{}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<h3 id="implement-the-webview">Implement the webview</h3>

<p><em><strong>Note:</strong></em><br />
The following code is inspired and adapted from <a href="https://github.com/microsoft/vscode-extension-samples/tree/main/custom-editor-sample">Custom Editor API Samples</a>.</p>

<ul>
  <li>
    <p>Open the file <em>vscode-extension/src/personEditor.ts</em></p>

    <ul>
      <li>Replace the <code class="language-plaintext highlighter-rouge">getWebviewHtml()</code> method that was added before</li>
      <li>
        <p>Add the <code class="language-plaintext highlighter-rouge">getNonce()</code> method to <code class="language-plaintext highlighter-rouge">PersonEditorProvider</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Get the static html used for the editor webviews.
*/</span>
<span class="k">private</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
  <span class="c1">// Local path to script and css for the webview</span>
  <span class="kd">const</span> <span class="nx">stylesUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">styles.css</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">);</span>
  <span class="kd">const</span> <span class="nx">scriptUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">main.js</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">nonce</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getNonce</span><span class="p">();</span>

  <span class="c1">// Tip: Install the es6-string-html Visual Studio Code extension to enable code highlighting below</span>
  <span class="k">return</span> <span class="cm">/* html */</span> <span class="s2">`
  &lt;!DOCTYPE html&gt;
  &lt;html lang="en"&gt;
  &lt;head&gt;
      &lt;meta charset="UTF-8"&gt;

      &lt;!--
      Use a content security policy to only allow loading images, styles and fonts from https or from our extension directory,
      and only allow scripts that have a specific nonce.
      --&gt;
      &lt;meta
        http-equiv="Content-Security-Policy"
        content="default-src 'none'; img-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; font-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; style-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; script-src 'nonce-</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">';"&gt;

      &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;

      &lt;link href="</span><span class="p">${</span><span class="nx">stylesUri</span><span class="p">}</span><span class="s2">" rel="stylesheet" /&gt;

      &lt;title&gt;Person Editor&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
      &lt;h1&gt;Visual Studio Code Person Editor&lt;/h1&gt;
      &lt;div class="person"&gt;
        &lt;div class="row"&gt;
          &lt;label for="firstname"&gt;Firstname:&lt;/label&gt;
          &lt;div class="value"&gt;
            &lt;input type="text" id="firstname"/&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="row"&gt;
          &lt;label for="lastname"&gt;Lastname:&lt;/label&gt;
          &lt;div class="value"&gt;
            &lt;input type="text" id="lastname"/&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;script nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">scriptUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;`</span><span class="p">;</span>
<span class="p">}</span>

<span class="cm">/**
 * A helper function that returns a unique alphanumeric identifier called a nonce.
*
* @remarks This function is primarily used to help enforce content security
* policies for resources/scripts being executed in a webview context.
*
* @returns A nonce
*/</span>
<span class="nf">getNonce</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">text</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">possible</span> <span class="o">=</span>
    <span class="dl">"</span><span class="s2">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789</span><span class="dl">"</span><span class="p">;</span>
  <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">32</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">text</span> <span class="o">+=</span> <span class="nx">possible</span><span class="p">.</span><span class="nf">charAt</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nf">random</span><span class="p">()</span> <span class="o">*</span> <span class="nx">possible</span><span class="p">.</span><span class="nx">length</span><span class="p">));</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nx">text</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Create the folder <em>vscode-extension/media</em></li>
  <li>
    <p>Create the file <em>vscode-extension/media/main.js</em> with the following content</p>

    <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Script run within the webview itself.</span>
<span class="p">(</span><span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// Get a reference to the Visual Studio Code webview api.</span>
  <span class="c1">// We use this API to post messages back to our extension.</span>

  <span class="kd">const</span> <span class="nx">vscode</span> <span class="o">=</span> <span class="nf">acquireVsCodeApi</span><span class="p">();</span>

  <span class="kd">const</span> <span class="nx">personContainer</span> <span class="o">=</span> <span class="cm">/** @type {HTMLElement} */</span> <span class="p">(</span>
    <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">.person</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">errorContainer</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">div</span><span class="dl">"</span><span class="p">);</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">errorContainer</span><span class="p">);</span>
  <span class="nx">errorContainer</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">;</span>
  <span class="nx">errorContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">;</span>

  <span class="cm">/**
   * Render the document in the webview.
   */</span>
  <span class="kd">function</span> <span class="nf">updateContent</span><span class="p">(</span><span class="cm">/** @type {string} */</span> <span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">json</span><span class="p">;</span>
    <span class="k">try</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">text</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">{}</span><span class="dl">"</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="nx">json</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
      <span class="nx">personContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">;</span>
      <span class="nx">errorContainer</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Error: Document is not valid json</span><span class="dl">"</span><span class="p">;</span>
      <span class="nx">errorContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nx">personContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
    <span class="nx">errorContainer</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">;</span>

    <span class="kd">const</span> <span class="nx">firstname</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">firstname</span><span class="dl">"</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">lastname</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">lastname</span><span class="dl">"</span><span class="p">);</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">firstname</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">firstname</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">lastname</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">lastname</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">firstname</span><span class="p">.</span><span class="nx">oninput</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
      <span class="c1">// wait 500 ms before updating the document</span>
      <span class="c1">// only update if in the meantime no other input was given</span>
      <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">json</span><span class="p">.</span><span class="nx">firstname</span> <span class="o">=</span> <span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
          <span class="nx">vscode</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
            <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">,</span>
            <span class="na">text</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">json</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
          <span class="p">});</span>
        <span class="p">}</span>
      <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
    <span class="p">};</span>

    <span class="nx">lastname</span><span class="p">.</span><span class="nx">oninput</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
      <span class="c1">// wait 500 ms before updating the document</span>
      <span class="c1">// only update if in the meantime no other input was given</span>
      <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">json</span><span class="p">.</span><span class="nx">lastname</span> <span class="o">=</span> <span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
          <span class="nx">vscode</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
            <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">,</span>
            <span class="na">text</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">json</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
          <span class="p">});</span>
        <span class="p">}</span>
      <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
    <span class="p">};</span>
  <span class="p">}</span>

  <span class="c1">// Handle messages sent from the extension to the webview</span>
  <span class="nb">window</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// The json data that the extension sent</span>
    <span class="k">switch </span><span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">case</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">:</span>
        <span class="kd">const</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">message</span><span class="p">.</span><span class="nx">text</span><span class="p">;</span>

        <span class="c1">// Update our webview's content</span>
        <span class="nf">updateContent</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>

        <span class="c1">// Then persist state information.</span>
        <span class="c1">// This state is returned in the call to `vscode.getState` below when a webview is reloaded.</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nf">setState</span><span class="p">({</span> <span class="nx">text</span> <span class="p">});</span>

        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">});</span>

  <span class="c1">// Webviews are normally torn down when not visible and re-created when they become visible again.</span>
  <span class="c1">// State lets us save information across these re-loads</span>
  <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nf">getState</span><span class="p">();</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">updateContent</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">text</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">})();</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Create the file <em>vscode-extension/media/styles.css</em> with the following content</p>

    <div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">label</span> <span class="p">{</span>
  <span class="nl">font-weight</span><span class="p">:</span> <span class="m">600</span><span class="p">;</span>
  <span class="nl">display</span><span class="p">:</span> <span class="nb">block</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>If everything is correctly in place, you can verify the editor by</p>

<ul>
  <li>pressing F5 to start a new Visual Studio Code instance with the extension</li>
  <li>opening a folder somewhere<br />
Create a folder <em>example</em> in the home directory of the <em>node</em> user in the Dev Container for example.</li>
  <li>creating a new file named <em>homer.person</em></li>
</ul>

<p>Now a webview with two input fields should be visible.</p>

<ul>
  <li>enter values for <em>Firstname</em> and <em>Lastname</em></li>
  <li>Save via <em>CTRL + S</em></li>
  <li>Right click on the created file in the <em>Explorer</em></li>
  <li>Select <em>Open With…</em> - <em>Text Editor</em></li>
</ul>

<p>The default text editor should open with the JSON content of the created file.</p>

<p>Further information about the used API and official examples:</p>

<ul>
  <li><a href="https://code.visualstudio.com/api/extension-guides/webview">Webview API</a></li>
  <li><a href="https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample">Webview API Sample</a></li>
  <li><a href="https://code.visualstudio.com/api/extension-guides/custom-editors">Custom Editor API</a></li>
  <li><a href="https://github.com/microsoft/vscode-extension-samples/tree/main/custom-editor-sample">Custom Editor API Samples</a></li>
</ul>

<h3 id="install-scripts">Install scripts</h3>

<p>When new developers in the project check out the repository and open it in a Dev Container, they first need to run the <code class="language-plaintext highlighter-rouge">npm</code> commands to install the project dependencies.
This can be also automated via scripts and a Dev Container configuration.</p>

<ul>
  <li>
    <p>Create a <em>package.json</em> in the repository root</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vscode-theia-cookbook"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"private"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"install:all"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cd vscode-extension &amp;&amp; npm install"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Execute the script on <code class="language-plaintext highlighter-rouge">postCreateCommand</code></p>

    <ul>
      <li>
        <p>If you use the <code class="language-plaintext highlighter-rouge">postCreateCommand</code> directly, open <em>.devcontainer/devcontainer.json</em> and update the instruction</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"postCreateCommand"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm install -g npm yo generator-code @vscode/vsce; npm run install:all"</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p>If you use a init shell script, add the following instruction at the end of the script file</p>

        <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run <span class="nb">install</span>:all
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>If you now rebuild the Dev Container, you will notice that at the npm install scripts are automatically executed.</p>

<h3 id="vscode-elements-lite">VSCode Elements Lite</h3>

<p>You might have noticed that the input fields in the webview do not look like the input fields in Visual Studio Code, e.g. in the <em>Settings</em>.
To achieve this, we would need to spend quite some time in the definition of the CSS. And of course we need to use the various CSS variables defined in Visual Studio Code.</p>

<p>To avoid this effort, we can also use the <a href="https://vscode-elements.github.io/">VSCode Elements</a> component library, or in case of vanilla HTML and CSS, <a href="https://vscode-elements.github.io/elements-lite/">VSCode Elements Lite</a>.</p>

<p>As a first step, we will only use the styling variant by using <a href="https://vscode-elements.github.io/elements-lite/">VSCode Elements Lite</a>.</p>

<p>Add <code class="language-plaintext highlighter-rouge">@vscode-elements/elements-lite</code> as a dependency in the <em>package.json</em> and reference the CSS files in the webview HTML.</p>

<ul>
  <li>Open a <strong>Terminal</strong></li>
  <li>Switch to the <em>vscode-extension</em> folder</li>
  <li>
    <p>Install <code class="language-plaintext highlighter-rouge">@vscode-elements/elements-lite</code> as a <code class="language-plaintext highlighter-rouge">dependency</code></p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @vscode-elements/elements-lite
</code></pre></div>    </div>
  </li>
  <li>
    <p>Open the file <em>vscode-extension/src/personEditor.ts</em></p>

    <ul>
      <li>Resolve the URIs to the VSCode Elements Lite CSS files</li>
      <li>Add the references to the CSS files in the <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> section of the HTML content</li>
      <li>
        <p>Add <code class="language-plaintext highlighter-rouge">class="vscode-label"</code> to the <code class="language-plaintext highlighter-rouge">label</code> tags, and <code class="language-plaintext highlighter-rouge">class="vscode-textfield"</code> to the <code class="language-plaintext highlighter-rouge">input</code> fields</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Get the static html used for the editor webviews.
 */</span>
<span class="k">private</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
  <span class="c1">// Local path to script and css for the webview</span>
  <span class="kd">const</span> <span class="nx">stylesUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">styles.css</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">);</span>
  <span class="kd">const</span> <span class="nx">scriptUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">main.js</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">labelUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">node_modules</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">@vscode-elements/elements-lite</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">components</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">label</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">label.css</span><span class="dl">"</span>
    <span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">textfieldUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">node_modules</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">@vscode-elements/elements-lite</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">components</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">textfield</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">textfield.css</span><span class="dl">"</span>
    <span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">nonce</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getNonce</span><span class="p">();</span>

  <span class="c1">// Tip: Install the es6-string-html Visual Studio Code extension to enable code highlighting below</span>
  <span class="k">return</span> <span class="cm">/* html */</span> <span class="s2">`
  &lt;!DOCTYPE html&gt;
  &lt;html lang="en"&gt;
  &lt;head&gt;
      &lt;meta charset="UTF-8"&gt;

      &lt;!--
      Use a content security policy to only allow loading images, styles and fonts from https or from our extension directory,
      and only allow scripts that have a specific nonce.
      --&gt;
      &lt;meta
        http-equiv="Content-Security-Policy"
        content="default-src 'none'; img-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; font-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; style-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; script-src 'nonce-</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">';"&gt;

      &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;

      &lt;link href="</span><span class="p">${</span><span class="nx">stylesUri</span><span class="p">}</span><span class="s2">" rel="stylesheet" /&gt;
      &lt;link href="</span><span class="p">${</span><span class="nx">labelUri</span><span class="p">}</span><span class="s2">" rel="stylesheet" /&gt;
      &lt;link href="</span><span class="p">${</span><span class="nx">textfieldUri</span><span class="p">}</span><span class="s2">" rel="stylesheet" /&gt;

      &lt;title&gt;Person Editor&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
      &lt;h1&gt;Visual Studio Code Person Editor&lt;/h1&gt;
    &lt;div class="person"&gt;
      &lt;div class="row"&gt;
        &lt;label for="firstname" class="vscode-label"&gt;Firstname:&lt;/label&gt;
        &lt;div class="value"&gt;
          &lt;input type="text" id="firstname" class="vscode-textfield"/&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class="row"&gt;
        &lt;label for="lastname" class="vscode-label"&gt;Lastname:&lt;/label&gt;
        &lt;div class="value"&gt;
          &lt;input type="text" id="lastname" class="vscode-textfield"/&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

      &lt;script nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">scriptUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;`</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>If you now restart the application, the input fields in the webview of the custom editor will look like native Visual Studio Code components.</p>

<h3 id="vscode-elements">VSCode Elements</h3>

<p>If you like to use the Javascript enabled web components, you can use the <a href="https://vscode-elements.github.io/">VSCode Elements</a> component library.</p>

<p>Add <code class="language-plaintext highlighter-rouge">@vscode-elements/elements</code> as a dependency in the <em>package.json</em> and reference the CSS files in the webview HTML.</p>

<ul>
  <li>Open a <strong>Terminal</strong></li>
  <li>Switch to the <em>vscode-extension</em> folder</li>
  <li>
    <p>Install <code class="language-plaintext highlighter-rouge">@vscode-elements/elements</code> as a <code class="language-plaintext highlighter-rouge">dependency</code></p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @vscode-elements/elements
</code></pre></div>    </div>
  </li>
  <li>
    <p>Open the file <em>vscode-extension/src/personEditor.ts</em></p>

    <ul>
      <li>Resolve the URI to the VSCode Elements bundled version Javascript file</li>
      <li>Add the reference to the Javascript file in the <code class="language-plaintext highlighter-rouge">&lt;body&gt;</code> section of the HTML content and use the attribute <code class="language-plaintext highlighter-rouge">type="module"</code></li>
      <li>
        <p>Replace the <code class="language-plaintext highlighter-rouge">label</code> tag with <code class="language-plaintext highlighter-rouge">vscode-label</code>, and the <code class="language-plaintext highlighter-rouge">input</code> tag with <code class="language-plaintext highlighter-rouge">vscode-textfield</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Get the static html used for the editor webviews.
 */</span>
<span class="k">private</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
  <span class="c1">// Local path to script and css for the webview</span>
  <span class="kd">const</span> <span class="nx">stylesUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">styles.css</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">);</span>
  <span class="kd">const</span> <span class="nx">scriptUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">media</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">main.js</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">elementsUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">node_modules</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">@vscode-elements/elements</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">bundled.js</span><span class="dl">"</span>
    <span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">nonce</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getNonce</span><span class="p">();</span>

  <span class="c1">// Tip: Install the es6-string-html Visual Studio Code extension to enable code highlighting below</span>
  <span class="k">return</span> <span class="cm">/* html */</span> <span class="s2">`
  &lt;!DOCTYPE html&gt;
  &lt;html lang="en"&gt;
  &lt;head&gt;
      &lt;meta charset="UTF-8"&gt;

      &lt;!--
      Use a content security policy to only allow loading images, styles and fonts from https or from our extension directory,
      and only allow scripts that have a specific nonce.
      --&gt;
      &lt;meta
        http-equiv="Content-Security-Policy"
        content="default-src 'none'; img-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; font-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; style-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; script-src 'nonce-</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">';"&gt;

      &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;

      &lt;link href="</span><span class="p">${</span><span class="nx">stylesUri</span><span class="p">}</span><span class="s2">" rel="stylesheet" /&gt;

      &lt;title&gt;Person Editor&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
      &lt;h1&gt;Visual Studio Code Person Editor&lt;/h1&gt;
      &lt;div class="person"&gt;
        &lt;div class="row"&gt;
          &lt;vscode-label for="firstname"&gt;Firstname:&lt;/vscode-label&gt;
          &lt;div class="value"&gt;
            &lt;vscode-textfield type="text" id="firstname"/&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="row"&gt;
          &lt;vscode-label for="lastname"&gt;Lastname:&lt;/vscode-label&gt;
          &lt;div class="value"&gt;
            &lt;vscode-textfield type="text" id="lastname"/&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;script nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">scriptUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
      &lt;script nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">elementsUri</span><span class="p">}</span><span class="s2">" type="module"&gt;&lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;`</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>If you now restart the application, the input fields in the webview of the custom editor will look and behave like native Visual Studio Code components.</p>

<h2 id="angular-webview-implementation">Angular Webview Implementation</h2>

<p>The above steps showed how to create a basic Visual Studio Code Extension with vanilla HTML, Javascript and CSS.
If the user interface is more complicated than two simple input fields, this can become quite complicated.
In such a case it can be interesting to use a Javascript framework for the implementation of the webview.</p>

<p>In the following chapter we create an Angular project that serves as the webview frontend of the Visual Studio Code Extension.</p>

<p><em><strong>Note:</strong></em><br />
The following description is based on the <a href="https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/frameworks/hello-world-angular">vscode-webview-ui-toolkit-samples Hello World (Angular) example</a>. I reused several parts of the sample code and adapted it where necessary.<br />
Also note that I am not an expert in developing Angular applications. So probably the following sample can be more efficient, but the goal is to help getting started in setting up a Visual Studio Code Extension with Angular webview.</p>

<ul>
  <li>
    <p>Add the Angular related extensions and configurations to the Dev Container</p>

    <ul>
      <li>Edit the <em>.devcontainer/devcontainer.json</em> and add the <code class="language-plaintext highlighter-rouge">angular.ng-template</code> to the <code class="language-plaintext highlighter-rouge">customizations/vscode/extensions</code></li>
      <li>Edit the <code class="language-plaintext highlighter-rouge">npm install -g</code> command to additionally install <code class="language-plaintext highlighter-rouge">@angular/cli</code><br />
Dependent on your setup, this is either in the <code class="language-plaintext highlighter-rouge">postCreateCommand</code> of the <em>devcontainer.json</em> or in the <em>postCreateCommand.sh</em> script file.</li>
      <li>
        <p>Optional:<br />
In case you use the <em>postCreateCommand.sh</em> script file, you can also add the following two instructions to load the Angular CLI autocompletion by default</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo '# Load Angular CLI autocompletion.' &gt;&gt; ~/.bashrc
echo 'source &lt;(ng completion script)' &gt;&gt; ~/.bashrc
</code></pre></div>        </div>
      </li>
      <li>Edit the <em>.vscode/extension.json</em> and add the <code class="language-plaintext highlighter-rouge">angular.ng-template</code> to the <code class="language-plaintext highlighter-rouge">recommendations</code></li>
      <li>Rebuild the Dev Container<br />
Press F1 - <em>Dev Containers: Rebuild Container</em></li>
    </ul>
  </li>
  <li>
    <p>Create a new Visual Studio Code Extension project.</p>

    <ul>
      <li>
        <p>Open a <strong>Terminal</strong> and execute the following command</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yo code
</code></pre></div>        </div>
      </li>
      <li>
        <p>Answer the questions of the wizard for example like shown below:</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? angular-extension
# ? What's the identifier of your extension? angular-extension
# ? What's the description of your extension? LEAVE BLANK
# ? Initialize a git repository? N
# ? Which bundler to use? unbundled
# ? Which package manager to use? npm

# ? Do you want to open the new folder with Visual Studio Code? Skip
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Delete the created <em>angular-extension/.vscode</em> folder</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm -rf angular-extension/.vscode
</code></pre></div>    </div>
  </li>
  <li>
    <p>Open the <em>.vscode/launch.json</em></p>

    <ul>
      <li>Add the <code class="language-plaintext highlighter-rouge">extensionDevelopmentPath</code> to the newly created <em>angular-extension</em> to the <code class="language-plaintext highlighter-rouge">args</code> get both extensions started on launch</li>
      <li>
        <p>Add the path to the build result directory of the newly created <em>angular-extension</em> to the <code class="language-plaintext highlighter-rouge">outFiles</code></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.2.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Run Extension"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"extensionHost"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/vscode-extension"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/angular-extension"</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"outFiles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"${workspaceFolder}/vscode-extension/out/**/*.js"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${workspaceFolder}/angular-extension/out/**/*.js"</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"preLaunchTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${defaultBuildTask}"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Create a new ng application as described in <a href="https://angular.dev/tools/cli/setup-local#create-a-workspace-and-initial-application">Create a workspace and initial application</a>.<br />
It is created inside the Visual Studio Code Extension project folder, as it serves as the implementation of the webview.</p>

    <ul>
      <li>Open a <strong>Terminal</strong></li>
      <li>Switch to the <em>angular-extension</em> folder</li>
      <li>
        <p>Execute the following command to create the ng application</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng new webview-ui
</code></pre></div>        </div>
      </li>
      <li>
        <p>Answer the questions of the wizard for example like shown below:</p>

        <div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>? Would you like to share pseudonymous usage data ... N
<span class="gp">#</span><span class="w"> </span>? Which stylesheet format would you like to use? CSS
<span class="gp">#</span><span class="w"> </span>? Do you want to <span class="nb">enable </span>Server-Side Rendering <span class="o">(</span>SSR<span class="o">)</span> and Static Site Generation <span class="o">(</span>SSG/Prerendering<span class="o">)</span>? N
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>This command creates a new Angular project in the folder <em>angular-extension/webview-ui</em> with Static Site Generation.</p>

<p>Of course this creates much more than we need for our setup. Therefore we need to cleanup as a next step.</p>

<ul>
  <li>
    <p>If no git repository is initialized already, it will be done automatically for the <em>webview-ui</em> folder. In that case delete the generated <em>.git</em> folder.</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm -rf webview-ui/.git
</code></pre></div>    </div>
  </li>
  <li>
    <p>Transfer the generated content in <em>angular-extension/webview-ui/.vscode</em> to the files in <em>.vscode</em>.</p>

    <ul>
      <li>
        <p><em>launch.json</em><br />
Copy the two configurations to <em>.vscode/launch.json</em> and add the <code class="language-plaintext highlighter-rouge">webRoot</code> setting</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ng serve"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"chrome"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"preLaunchTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm: start"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:4200/"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"webRoot"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/angular-extension/webview-ui"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ng test"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"chrome"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"preLaunchTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm: test"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:9876/debug.html"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"webRoot"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/angular-extension/webview-ui"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p><em>tasks.json</em><br />
Copy the two configurations to <em>.vscode/tasks.json</em> and add the <code class="language-plaintext highlighter-rouge">cwd</code> option</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"start"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"owner"</span><span class="p">:</span><span class="w"> </span><span class="s2">"typescript"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"pattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(.*?)"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bundle generation complete"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/angular-extension/webview-ui"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"test"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"owner"</span><span class="p">:</span><span class="w"> </span><span class="s2">"typescript"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"pattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(.*?)"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bundle generation complete"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/angular-extension/webview-ui"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p>After the configurations are transfered, the generated <em>.vscode</em> folder can be deleted from the <em>webview-ui</em> folder</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm -rf webview-ui/.vscode
</code></pre></div>        </div>
      </li>
      <li>
        <p>Update the <em>angular-extension/webview-ui/package.json</em></p>
        <ul>
          <li>Change the <code class="language-plaintext highlighter-rouge">name</code> to <code class="language-plaintext highlighter-rouge">angular-webview-ui</code></li>
        </ul>
      </li>
    </ul>

    <p>To verify that the setup works, you can now either run the task via</p>
  </li>
  <li>Press <em>F1</em> - <em>Tasks: Run Task</em> - <em>npm:start</em></li>
  <li>Run the launch configuration
    <ul>
      <li>Open <em>Run and Debug</em></li>
      <li>Select <em>ng serve</em> in the dropdown</li>
      <li>Click <em>Start Debugging</em> or press <em>F5</em></li>
    </ul>
  </li>
</ul>

<p>This will start the ng application and host it via http://localhost:4200.</p>

<h2 id="prepare-the-ng-application-as-webview">Prepare the NG Application as webview</h2>

<p>In the next steps the two projects need to be configured so the NG application can be used as webview in the Visual Studio Code Extension.</p>

<ul>
  <li>
    <p>Update <em>angular-extension/tsconfig.json</em></p>

    <ul>
      <li>Change the <code class="language-plaintext highlighter-rouge">outDir</code> to <code class="language-plaintext highlighter-rouge">./dist</code><br />
This might not be really necessary, but having a good naming convention for the folders helps in understanding the structure. The <code class="language-plaintext highlighter-rouge">dist</code> folder will contain the content that gets distributed in the packaged Visual Studio Code Extension.</li>
      <li>Add <code class="language-plaintext highlighter-rouge">DOM</code> to the <code class="language-plaintext highlighter-rouge">lib</code> configuration</li>
      <li>
        <p>Configure <code class="language-plaintext highlighter-rouge">exclude</code> to avoid <code class="language-plaintext highlighter-rouge">node_modules</code> and <code class="language-plaintext highlighter-rouge">webview-ui</code> being included in the codebase</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"module"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Node16"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ES2022"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"outDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./dist"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"lib"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ES2022"</span><span class="p">,</span><span class="w"> </span><span class="s2">"DOM"</span><span class="p">],</span><span class="w">
    </span><span class="nl">"sourceMap"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"rootDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"src"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"strict"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"exclude"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"node_modules"</span><span class="p">,</span><span class="w"> </span><span class="s2">"webview-ui"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Update <em>angular-extension/package.json</em>
    <ul>
      <li>Change <code class="language-plaintext highlighter-rouge">main</code> to point to <code class="language-plaintext highlighter-rouge">./dist/extension.js</code></li>
    </ul>
  </li>
  <li>Update <em>.vscode/launch.json</em>
    <ul>
      <li>Correct the <code class="language-plaintext highlighter-rouge">outFiles</code> value to <code class="language-plaintext highlighter-rouge">"${workspaceFolder}/angular-extension/dist/**/*.js"</code></li>
    </ul>
  </li>
  <li>
    <p>Update <em>angular-extension/.vscodeignore</em> to ignore all <em>webview-ui</em> files except the build directory</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Ignore extension configs
.vscode/**

# Ignore test files
.vscode-test/**
**/.vscode-test.*
dist/test/**

# Ignore source code
src/**

# Ignore all webview-ui files except the build directory
node_modules/**
webview-ui/**
!webview-ui/build/**

# Ignore Misc
.yarnrc
vsc-extension-quickstart.md
**/.gitignore
**/tsconfig.json
**/vite.config.ts
**/.eslintrc.json
**/*.map
**/*.ts
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update <em>angular-extension/webview-ui/tsconfig.json</em></p>

    <ul>
      <li>
        <p>Add the following configurations in the <code class="language-plaintext highlighter-rouge">compilerOptions</code></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"baseUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./"</span><span class="err">,</span><span class="w">
</span><span class="nl">"lib"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ES2022"</span><span class="p">,</span><span class="w"> </span><span class="s2">"dom"</span><span class="p">]</span><span class="err">,</span><span class="w">
</span><span class="nl">"sourceMap"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span><span class="nl">"declaration"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Update <em>angular-extension/webview-ui/angular.json</em></p>

    <ul>
      <li>
        <p>Set the <code class="language-plaintext highlighter-rouge">schematics</code> to the following</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"schematics"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"@schematics/angular:application"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"strict"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p>Change the <code class="language-plaintext highlighter-rouge">builder</code> from <code class="language-plaintext highlighter-rouge">application</code> to <code class="language-plaintext highlighter-rouge">browser-esbuild</code></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"builder"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@angular-devkit/build-angular:browser-esbuild"</span><span class="err">,</span><span class="w">
</span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"outputPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"src/index.html"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"src/main.ts"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"polyfills"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"zone.js"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"tsConfig"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tsconfig.app.json"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"assets"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"glob"</span><span class="p">:</span><span class="w"> </span><span class="s2">"**/*"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"input"</span><span class="p">:</span><span class="w"> </span><span class="s2">"public"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"styles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"src/styles.css"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p>Ensure that the <code class="language-plaintext highlighter-rouge">outputHashing</code> is set to <code class="language-plaintext highlighter-rouge">none</code> for the <code class="language-plaintext highlighter-rouge">production</code> and the <code class="language-plaintext highlighter-rouge">development</code> configuration, otherwise resources can not be referenced in the webview correctly.</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"outputHashing"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Update <em>angular-extension/webview-ui/.gitignore</em>
    <ul>
      <li>Add the <em>/build</em> folder</li>
    </ul>
  </li>
  <li>
    <p>After we changed the output folder to <em>dist</em> you can delete the folder <em>angular-extension/out</em> if it was already created.</p>
  </li>
  <li>
    <p>Remove the <code class="language-plaintext highlighter-rouge">RouterOutlet</code><br />
This is necessary to make the usage of assets from third-party modules that are transfered to the <em>media</em> folder work correctly.</p>

    <ul>
      <li>Update <em>angular-extension/webview-ui/src/app/app.component.html</em>
        <ul>
          <li>Remove <code class="language-plaintext highlighter-rouge">&lt;router-outlet /&gt;</code> usage (bottom of the file)</li>
        </ul>
      </li>
      <li>Update <em>angular-extension/webview-ui/src/app/app.component.ts</em>
        <ul>
          <li>Remove the <code class="language-plaintext highlighter-rouge">RouterOutlet</code> import</li>
        </ul>
      </li>
      <li>
        <p>Update <em>angular-extension/webview-ui/src/app/app.config.ts</em></p>

        <ul>
          <li>
            <p>Remove the router configuration</p>

            <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">ApplicationConfig</span><span class="p">,</span>
  <span class="nx">provideZoneChangeDetection</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@angular/core</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">appConfig</span><span class="p">:</span> <span class="nx">ApplicationConfig</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nf">provideZoneChangeDetection</span><span class="p">({</span> <span class="na">eventCoalescing</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})],</span>
<span class="p">};</span>
</code></pre></div>            </div>
          </li>
        </ul>
      </li>
      <li>Delete <em>angular-extension/webview-ui/src/app/app.routes.ts</em></li>
    </ul>
  </li>
  <li>Test if the build succeeds
    <ul>
      <li>Open a <strong>Terminal</strong></li>
      <li>Switch to the <em>angular-extension/webview-ui</em> folder</li>
      <li>Call <code class="language-plaintext highlighter-rouge">ng build</code><br />
This should create the folder <em>angular-extension/webview-ui/build</em></li>
      <li>Switch to the <em>angular-extension</em> folder</li>
      <li>Call <code class="language-plaintext highlighter-rouge">npm run compile</code><br />
This should create the folder <em>angular-extension/dist</em>.</li>
    </ul>
  </li>
</ul>

<h2 id="use-the-ng-application-as-webview">Use the NG Application as webview</h2>

<p>The next step is to use the NG Application as a Visual Studio Code Extension WebView.
For this we need to perform the same steps as described previously in <a href="#implement-the-visual-studio-code-extension">Implement the Visual Studio Code Extension</a>.</p>

<ul>
  <li>
    <p>Open the <em>angular-extension/package.json</em></p>

    <ul>
      <li>
        <p>Replace the <code class="language-plaintext highlighter-rouge">contributes</code> section with the following snippet:</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"contributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"customEditors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"viewType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"angular-extension.personEditor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Angular Person Editor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"filenamePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*.person"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Create a new file <em>angular-extension/src/personEditor.ts</em></p>

    <ul>
      <li>Copy the content from <em>vscode-extension/src/personEditor.ts</em><br />
<em><strong>Note:</strong></em><br />
If you have not created that file in the previous vanilla HTML, CSS, Javascript part, follow the steps in <a href="#implement-the-visual-studio-code-extension">Implement the Visual Studio Code Extension</a> to create the content of the file.</li>
      <li>
        <p>Change the value of <code class="language-plaintext highlighter-rouge">viewType</code> to <code class="language-plaintext highlighter-rouge">angular-extension.personEditor</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="nx">viewType</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">angular-extension.personEditor</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Optional:<br />
Extend the <code class="language-plaintext highlighter-rouge">webview.options</code> to restrict the webview to only load resources from <em>dist</em> and <em>webview-ui/build</em> directories</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Setup initial content for the webview</span>
<span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
  <span class="c1">// Enable scripts in the webview</span>
  <span class="na">enableScripts</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>

  <span class="c1">// Restrict the webview to only load resources from the `dist` and `webview-ui/build` directories</span>
  <span class="na">localResourceRoots</span><span class="p">:</span> <span class="p">[</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">),</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">webview-ui/build</span><span class="dl">"</span><span class="p">),</span>
  <span class="p">],</span>
<span class="p">};</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Change the implementation of <code class="language-plaintext highlighter-rouge">getWebviewHtml()</code> so that it basically returns the same HTML as in <em>angular-extension/webview-ui/src/index.html</em>. But instead of relative URLs to resources like CSS and the Javascript files, special WebView URIs are used. This is necessary so the resources can be correctly resolved inside a webview, which is described in <a href="https://code.visualstudio.com/api/extension-guides/webview#loading-local-content">Loading local content</a>.</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Get the static html used for the editor webviews.
 */</span>
<span class="k">private</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
  <span class="c1">// The CSS file from the Angular build output</span>
  <span class="kd">const</span> <span class="nx">stylesUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">styles.css</span><span class="dl">"</span>
    <span class="p">)</span>
  <span class="p">);</span>
  <span class="c1">// The JS files from the Angular build output</span>
  <span class="kd">const</span> <span class="nx">polyfillsUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">polyfills.js</span><span class="dl">"</span>
    <span class="p">)</span>
  <span class="p">);</span>
  <span class="kd">const</span> <span class="nx">scriptUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">main.js</span><span class="dl">"</span>
    <span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">nonce</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getNonce</span><span class="p">();</span>

  <span class="c1">// Tip: Install the es6-string-html VS Code extension to enable code highlighting below</span>
  <span class="k">return</span> <span class="cm">/*html*/</span> <span class="s2">`
      &lt;!DOCTYPE html&gt;
      &lt;html lang="en"&gt;
      &lt;head&gt;
          &lt;meta charset="UTF-8" /&gt;
          &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
          &lt;meta
              http-equiv="Content-Security-Policy"
              content="default-src 'none'; style-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2"> 'unsafe-inline'; font-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; img-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; script-src 'nonce-</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">';"&gt;
          &lt;link rel="stylesheet" type="text/css" href="</span><span class="p">${</span><span class="nx">stylesUri</span><span class="p">}</span><span class="s2">"&gt;
          &lt;title&gt;Person Editor&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
          &lt;app-root&gt;&lt;/app-root&gt;
          &lt;script type="module" nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">polyfillsUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
          &lt;script type="module" nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">scriptUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
      &lt;/body&gt;
      &lt;/html&gt;
    `</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Change the content of <em>angular-extension/src/extension.ts</em></p>

    <ul>
      <li>
        <p>Replace the content with the following code</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The module 'vscode' contains the Visual Studio Code extensibility API</span>
<span class="c1">// Import the module and reference it with the alias vscode in your code below</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PersonEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./personEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// This method is called when your extension is activated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">activate</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Register our custom editor provider</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">PersonEditorProvider</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">context</span><span class="p">));</span>
<span class="p">}</span>

<span class="c1">// This method is called when your extension is deactivated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">deactivate</span><span class="p">()</span> <span class="p">{}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<h2 id="script-updates">Script Updates</h2>

<p>We now have a NG application project inside a Visual Studio Code Extension project. The build and run scripts are not automatically connected. For example, if you change code in the NG application and then launch the Visual Studio Code Extension, the changes are not automatically reflected. This is because the Visual Studio Code Extension is using the build results of the NG Application, and not the sources.</p>

<p>To connect the two projects, we need to update the <code class="language-plaintext highlighter-rouge">scripts</code> section in <em>angular-extension/package.json</em></p>

<p><em><strong>Note:</strong></em><br />
The <code class="language-plaintext highlighter-rouge">watch</code> script needs to execute the watch operations in parallel and not sequentially.
While one solution to this could be the usage of <code class="language-plaintext highlighter-rouge">&amp;</code> instead of <code class="language-plaintext highlighter-rouge">&amp;&amp;</code> on a Unix system, a better and OS independent solution is the usage of <a href="https://www.npmjs.com/package/concurrently"><code class="language-plaintext highlighter-rouge">concurrently</code></a>.</p>

<ul>
  <li>
    <p>Add <code class="language-plaintext highlighter-rouge">concurrently</code> as a <code class="language-plaintext highlighter-rouge">devDependency</code> to the <em>angular-extension/package.json</em></p>

    <ul>
      <li>Open a <strong>Terminal</strong></li>
      <li>Switch to the <em>angular-extension</em> folder</li>
      <li>
        <p>Execute the following command</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D concurrently
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Open <em>angular-extension/package.json</em></p>

    <ul>
      <li>
        <p>Add <code class="language-plaintext highlighter-rouge">scripts</code> for the <em>webview-ui</em></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"start:webview"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix webview-ui run start"</span><span class="err">,</span><span class="w">
</span><span class="nl">"build:webview"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix webview-ui run build"</span><span class="err">,</span><span class="w">
</span><span class="nl">"watch:webview"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix webview-ui run watch"</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p>Update <code class="language-plaintext highlighter-rouge">vscode:prepublish</code> to call the <code class="language-plaintext highlighter-rouge">build:webview</code> script additionally</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"vscode:prepublish"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run build:webview &amp;&amp; npm run compile"</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p>Update the <code class="language-plaintext highlighter-rouge">watch</code> script to call also <code class="language-plaintext highlighter-rouge">watch:webview</code> by using <code class="language-plaintext highlighter-rouge">concurrently</code></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"watch"</span><span class="p">:</span><span class="w"> </span><span class="s2">"concurrently --kill-others </span><span class="se">\"</span><span class="s2">npm run watch:webview</span><span class="se">\"</span><span class="s2"> </span><span class="se">\"</span><span class="s2">tsc -watch -p ./</span><span class="se">\"</span><span class="s2">"</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>To make the updated <code class="language-plaintext highlighter-rouge">watch</code> script also work when launching the extension, the corresponding task in <em>.vscode/tasks.json</em> needs to be updated.</p>

<p>If you only want to watch the <em>angular-extension</em>, update the configuration like this (notice the <code class="language-plaintext highlighter-rouge">endsPattern</code>). Replace the <code class="language-plaintext highlighter-rouge">npm:watch</code> script at the top of the <em>tasks.json</em>.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"base"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc-watch"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(.*?)"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bundle generation complete"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"presentation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"reveal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"never"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"isDefault"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/angular-extension"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>If you want to watch the <em>vscode-extension</em> and the <em>angular-extension</em> at the same time, update the configuration like this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Watch Extensions"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"isDefault"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"dependsOn"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"VS Code Extension Watch"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Angular Extension Watch"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"VS Code Extension Watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shell"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc-watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"presentation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"reveal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"never"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/vscode-extension"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Angular Extension Watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shell"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"base"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc-watch"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(.*?)"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bundle generation complete"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"presentation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"reveal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"never"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/angular-extension"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>The above snippet defines a <em>Compound Task</em>, makes it the default task that should be executed via <em>launch.json</em> <code class="language-plaintext highlighter-rouge">preLaunchTask</code> configuration and changes the task types from <code class="language-plaintext highlighter-rouge">npm</code> to <code class="language-plaintext highlighter-rouge">shell</code>.
This change is necessary because otherwise there is a name collision in the <em>Task auto-detection</em>.</p>

<p>To verify that it works</p>

<ul>
  <li>Launch the Visual Studio Code Extension(s)
    <ul>
      <li>Open <em>Run and Debug</em></li>
      <li>Ensure <em>Run Extension</em> is selected in the dropdown</li>
      <li>Click <em>Start Debugging</em> or press <em>F5</em></li>
    </ul>
  </li>
  <li>Right click on the file created in the previous example in the <em>Explorer</em></li>
  <li>Select <em>Open With…</em> - <em>Angular Person Editor</em></li>
</ul>

<p>This should open the webview with the content of the Angular application.</p>

<ul>
  <li>In the Visual Studio Code instance that is used for development, open the file <em>angular-extension/webview-ui/src/app/app.component.html</em></li>
  <li>Search for the string <code class="language-plaintext highlighter-rouge">Congratulations</code>, change the sentence, e.g. by adding <code class="language-plaintext highlighter-rouge">May the force by with you</code>, and save the changes</li>
  <li>Switch to the Visual Studio Code instance launched for testing the extension
    <ul>
      <li>Either close and reopen the webview</li>
      <li>Reload the webview by pressing F1 - <em>Developer: Reload Webviews</em></li>
    </ul>
  </li>
</ul>

<p>You should now see the applied changes.</p>

<ul>
  <li>In the Visual Studio Code instance that is used for development, open the file <em>angular-extension/src/personEditor.ts</em></li>
  <li>Change for example the webview HTML by adding <code class="language-plaintext highlighter-rouge">&lt;p&gt;Hello World&lt;/p&gt;</code> in the <code class="language-plaintext highlighter-rouge">&lt;body&gt;</code>.</li>
  <li>Switch to the Visual Studio Code instance launched for testing the extension</li>
  <li>Reload the <strong>Extension Development Host</strong> so that it picks up the changes. You have two options to do this:
    <ul>
      <li>Click on the debug restart action</li>
      <li>Press <code class="language-plaintext highlighter-rouge">Ctrl + R</code> / <code class="language-plaintext highlighter-rouge">Cmd + R</code> in the <strong>Extension Development Host</strong> window</li>
    </ul>
  </li>
</ul>

<p>You should see the applied changes now. If not reopen the webview and verify that the applied changes are visible.</p>

<p><em><strong>Note:</strong></em><br />
Don’t forget to remove the modification to the webview HTML, so it does not affect the next steps.</p>

<h3 id="angular-editor-implementation">Angular Editor Implementation</h3>

<p>The next step is to implement a custom editor, just like in the vanilla HTML, CSS, Javascript example before.
It uses the same principles with regards to communication between extension and webview, and looks quite the same.
But of course we use Angular for the UI implementation, in this case <a href="https://angular.dev/guide/forms/reactive-forms">Reactive forms</a> for the implementation.</p>

<ul>
  <li>
    <p>Add <code class="language-plaintext highlighter-rouge">@types/vscode-webview</code> as a <code class="language-plaintext highlighter-rouge">devDependency</code> to the <em>angular-extension/webview-ui</em> project</p>

    <ul>
      <li>Open a <strong>Terminal</strong></li>
      <li>Switch to the folder <em>angular-extension/webview-ui</em></li>
      <li>
        <p>Run the following command</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D @types/vscode-webview
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Create a new folder <em>angular-extension/webview-ui/src/app/utilities</em></li>
  <li>
    <p>Create a new file <em>vscode.ts</em> in the new folder</p>

    <ul>
      <li>
        <p>Add the following code, which is a copy of <a href="https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-angular/webview-ui/src/app/utilities/vscode.ts">vscode-webview-ui-toolkit-samples</a></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">WebviewApi</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode-webview</span><span class="dl">"</span><span class="p">;</span>

<span class="cm">/**
 * A utility wrapper around the acquireVsCodeApi() function, which enables
 * message passing and state management between the webview and extension
 * contexts.
 *
 * This utility also enables webview code to be run in a web browser-based
 * dev server by using native web browser features that mock the functionality
 * enabled by acquireVsCodeApi.
 */</span>
<span class="kd">class</span> <span class="nc">VSCodeAPIWrapper</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">vsCodeApi</span><span class="p">:</span> <span class="nx">WebviewApi</span><span class="o">&lt;</span><span class="nx">unknown</span><span class="o">&gt;</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Check if the acquireVsCodeApi function exists in the current development</span>
    <span class="c1">// context (i.e. VS Code development window or web browser)</span>
    <span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">acquireVsCodeApi</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">function</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span> <span class="o">=</span> <span class="nf">acquireVsCodeApi</span><span class="p">();</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Post a message (i.e. send arbitrary data) to the owner of the webview.
   *
   * @remarks When running webview code inside a web browser, postMessage will instead
   * log the given message to the console.
   *
   * @param message Abitrary data (must be JSON serializable) to send to the extension context.
   */</span>
  <span class="k">public</span> <span class="nf">postMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">message</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Get the persistent state stored for this webview.
   *
   * @remarks When running webview source code inside a web browser, getState will retrieve state
   * from local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
   *
   * @return The current state or `undefined` if no state has been set.
   */</span>
  <span class="k">public</span> <span class="nf">getState</span><span class="p">():</span> <span class="nx">unknown</span> <span class="o">|</span> <span class="kc">undefined</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">.</span><span class="nf">getState</span><span class="p">();</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nf">getItem</span><span class="p">(</span><span class="dl">"</span><span class="s2">vscodeState</span><span class="dl">"</span><span class="p">);</span>
      <span class="k">return</span> <span class="nx">state</span> <span class="p">?</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">:</span> <span class="kc">undefined</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Set the persistent state stored for this webview.
   *
   * @remarks When running webview source code inside a web browser, setState will set the given
   * state using local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
   *
   * @param newState New persisted state. This must be a JSON serializable object. Can be retrieved
   * using {@link getState}.
   *
   * @return The new state.
   */</span>
  <span class="k">public</span> <span class="nx">setState</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">unknown</span> <span class="o">|</span> <span class="kc">undefined</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">newState</span><span class="p">:</span> <span class="nx">T</span><span class="p">):</span> <span class="nx">T</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">.</span><span class="nf">setState</span><span class="p">(</span><span class="nx">newState</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">localStorage</span><span class="p">.</span><span class="nf">setItem</span><span class="p">(</span><span class="dl">"</span><span class="s2">vscodeState</span><span class="dl">"</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">newState</span><span class="p">));</span>
      <span class="k">return</span> <span class="nx">newState</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Exports class singleton to prevent multiple invocations of acquireVsCodeApi.</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">vscode</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">VSCodeAPIWrapper</span><span class="p">();</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>The <code class="language-plaintext highlighter-rouge">VSCodeAPIWrapper</code> is used for</p>
        <ul>
          <li>Aquiring the VSCode API</li>
          <li>Managing the persistent state of the webview</li>
          <li>Posting messages from the webview to the extension</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <p>Update <em>angular-extension/webview-ui/src/app/app.component.ts</em> by replacing the content with the following code</p>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Component</span><span class="p">,</span> <span class="nx">HostListener</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@angular/core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">FormControl</span><span class="p">,</span> <span class="nx">ReactiveFormsModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@angular/forms</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">vscode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./utilities/vscode</span><span class="dl">"</span><span class="p">;</span>

<span class="p">@</span><span class="nd">Component</span><span class="p">({</span>
  <span class="na">selector</span><span class="p">:</span> <span class="dl">"</span><span class="s2">app-root</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">ReactiveFormsModule</span><span class="p">],</span>
  <span class="na">templateUrl</span><span class="p">:</span> <span class="dl">"</span><span class="s2">./app.component.html</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">styleUrl</span><span class="p">:</span> <span class="dl">"</span><span class="s2">./app.component.css</span><span class="dl">"</span><span class="p">,</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">AppComponent</span> <span class="p">{</span>
  <span class="nx">firstname</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FormControl</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
  <span class="nx">lastname</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FormControl</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
  <span class="nl">personObject</span><span class="p">:</span> <span class="kr">any</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Webviews are normally torn down when not visible and re-created when they become visible again.</span>
    <span class="c1">// State lets us save information across these re-loads</span>
    <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nf">getState</span><span class="p">()</span> <span class="kd">as </span><span class="kr">any</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nf">updateContent</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">text</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="c1">// Handle messages sent from the extension to the webview</span>
  <span class="p">@</span><span class="nd">HostListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">window:message</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="dl">"</span><span class="s2">$event</span><span class="dl">"</span><span class="p">])</span>
  <span class="nf">handleMessage</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="nx">MessageEvent</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// The json data that the extension sent</span>
    <span class="k">switch </span><span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">case</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">:</span>
        <span class="kd">const</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">message</span><span class="p">.</span><span class="nx">text</span><span class="p">;</span>

        <span class="c1">// Update our webview's content</span>
        <span class="k">this</span><span class="p">.</span><span class="nf">updateContent</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>

        <span class="c1">// Then persist state information.</span>
        <span class="c1">// This state is returned in the call to `vscode.getState` below when a webview is reloaded.</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nf">setState</span><span class="p">({</span> <span class="nx">text</span> <span class="p">});</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Update the data shown in the document in the webview.
   */</span>
  <span class="nf">updateContent</span><span class="p">(</span><span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">text</span> <span class="o">!==</span> <span class="dl">""</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">personObject</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">firstname</span><span class="p">.</span><span class="nf">setValue</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">personObject</span><span class="p">.</span><span class="nx">firstname</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">lastname</span><span class="p">.</span><span class="nf">setValue</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">personObject</span><span class="p">.</span><span class="nx">lastname</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Update the document in the extension.
   */</span>
  <span class="nf">updateDocument</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">firstname</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
    <span class="kd">let</span> <span class="nx">lastname</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>

    <span class="c1">// wait 500 ms before updating the document</span>
    <span class="c1">// only update if in the meantime no other input was given</span>
    <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">firstname</span> <span class="o">&amp;&amp;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">lastname</span>
      <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">personObject</span> <span class="o">=</span> <span class="p">{</span>
          <span class="na">firstname</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">firstname</span><span class="p">.</span><span class="nx">value</span><span class="p">,</span>
          <span class="na">lastname</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">lastname</span><span class="p">.</span><span class="nx">value</span><span class="p">,</span>
        <span class="p">};</span>

        <span class="nx">vscode</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
          <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">updateDocument</span><span class="dl">'</span><span class="p">,</span>
          <span class="na">text</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">personObject</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
        <span class="p">});</span>
      <span class="p">}</span>
    <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update <em>angular-extension/webview-ui/src/app/app.component.html</em> by replacing the content with the following code</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;main</span> <span class="na">class=</span><span class="s">"main"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;h1&gt;</span>Angular Person Editor<span class="nt">&lt;/h1&gt;</span>
  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"content"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"person"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"row"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"firstname"</span><span class="nt">&gt;</span>Firstname:<span class="nt">&lt;/label&gt;</span>
        <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"value"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;input</span>
            <span class="na">type=</span><span class="s">"text"</span>
            <span class="na">[formControl]=</span><span class="s">"firstname"</span>
            <span class="na">(input)=</span><span class="s">"updateDocument()"</span>
          <span class="nt">/&gt;</span>
        <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"row"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"lastname"</span><span class="nt">&gt;</span>Lastname:<span class="nt">&lt;/label&gt;</span>
        <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"value"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;input</span>
            <span class="na">type=</span><span class="s">"text"</span>
            <span class="na">[formControl]=</span><span class="s">"lastname"</span>
            <span class="na">(input)=</span><span class="s">"updateDocument()"</span>
          <span class="nt">/&gt;</span>
        <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
    <span class="nt">&lt;/div&gt;</span>
  <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/main&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Add the following code to the file <em>angular-extension/webview-ui/src/app/app.component.css</em></p>

    <div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">label</span> <span class="p">{</span>
  <span class="nl">font-weight</span><span class="p">:</span> <span class="m">600</span><span class="p">;</span>
  <span class="nl">display</span><span class="p">:</span> <span class="nb">block</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>Verify the changes and launch the Visual Studio Code Extension(s)
    <ul>
      <li>Open <em>Run and Debug</em></li>
      <li>Ensure <em>Run Extension</em> is selected in the dropdown</li>
      <li>Click <em>Start Debugging</em> or press <em>F5</em></li>
    </ul>
  </li>
  <li>Right click on the file created in the previous example in the <em>Explorer</em></li>
  <li>Select <em>Open With…</em> - <em>Angular Person Editor</em>
    <ul>
      <li>This should open the webview with the two input fields</li>
    </ul>
  </li>
</ul>

<h3 id="install-scripts-1">Install scripts</h3>

<p>Like with the Visual Studio Code Extension project before, add a install script to the <em>angular-extension</em> that is called on <code class="language-plaintext highlighter-rouge">postCreateCommand</code> of the Dev Container. It needs to run <code class="language-plaintext highlighter-rouge">npm install</code> for the extension and the contained webview-ui project.</p>

<ul>
  <li>
    <p>Open the <em>angular-extension/package.json</em> and add the following script to the <code class="language-plaintext highlighter-rouge">scripts</code> section</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"install:all"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm install &amp;&amp; cd webview-ui &amp;&amp; npm install"</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Open the <em>package.json</em> in the repository root and modify the <code class="language-plaintext highlighter-rouge">install:all</code> script to also handle the <em>angular-extension</em> project</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vscode-theia-cookbook"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"private"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"install:all"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cd vscode-extension &amp;&amp; npm install &amp;&amp; cd ../angular-extension &amp;&amp; npm run install:all"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<h3 id="vscode-elements-lite-1">VSCode Elements Lite</h3>

<p>Like the vanilla HTML, CSS, Javascript example in the first section of this tutorial, the UI components do not look like native Visual Studio Code components.
To get a more native Visual Studio Code look and feel you would need to spend quite some time to implement this yourself.</p>

<p>An alterative to implementing this yourself is to use <a href="https://vscode-elements.github.io/">VSCode Elements</a> or <a href="https://vscode-elements.github.io/elements-lite/">VSCode Elements Lite</a>.</p>

<p>First let’s use the styling variant by using <a href="https://vscode-elements.github.io/elements-lite/">VSCode Elements Lite</a>.</p>

<p>Add <code class="language-plaintext highlighter-rouge">@vscode-elements/elements-lite</code> as a dependency in the <em>package.json</em> and reference the CSS files in the webview HTML.</p>

<ul>
  <li>Open a <strong>Terminal</strong></li>
  <li>Switch to the <em>angular-extension/webview-ui</em> folder</li>
  <li>
    <p>Install <code class="language-plaintext highlighter-rouge">@vscode-elements/elements-lite</code> as a <code class="language-plaintext highlighter-rouge">dependency</code></p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @vscode-elements/elements-lite
</code></pre></div>    </div>
  </li>
  <li>
    <p>Open the file <em>angular-extension/webview-ui/src/app/app.component.html</em></p>

    <ul>
      <li>
        <p>Add <code class="language-plaintext highlighter-rouge">class="vscode-label"</code> to the <code class="language-plaintext highlighter-rouge">label</code> tags, and <code class="language-plaintext highlighter-rouge">class="vscode-textfield"</code> to the <code class="language-plaintext highlighter-rouge">input</code> fields</p>

        <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;main</span> <span class="na">class=</span><span class="s">"main"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;h1&gt;</span>Angular Person Editor<span class="nt">&lt;/h1&gt;</span>
  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"content"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"person"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"row"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"firstname"</span> <span class="na">class=</span><span class="s">"vscode-label"</span><span class="nt">&gt;</span>Firstname:<span class="nt">&lt;/label&gt;</span>
        <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"value"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;input</span>
            <span class="na">type=</span><span class="s">"text"</span>
            <span class="na">class=</span><span class="s">"vscode-textfield"</span>
            <span class="na">[formControl]=</span><span class="s">"firstname"</span>
            <span class="na">(input)=</span><span class="s">"updateDocument()"</span>
          <span class="nt">/&gt;</span>
        <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"row"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"lastname"</span> <span class="na">class=</span><span class="s">"vscode-label"</span><span class="nt">&gt;</span>Lastname:<span class="nt">&lt;/label&gt;</span>
        <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"value"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;input</span>
            <span class="na">type=</span><span class="s">"text"</span>
            <span class="na">class=</span><span class="s">"vscode-textfield"</span>
            <span class="na">[formControl]=</span><span class="s">"lastname"</span>
            <span class="na">(input)=</span><span class="s">"updateDocument()"</span>
          <span class="nt">/&gt;</span>
        <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
    <span class="nt">&lt;/div&gt;</span>
  <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/main&gt;</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Open the file <em>angular-extension/webview-ui/angular.json</em></p>

    <ul>
      <li>
        <p>Add the VSCode Elements Lite CSS files to the <code class="language-plaintext highlighter-rouge">architect/build/options/styles</code></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"styles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
  </span><span class="s2">"src/styles.css"</span><span class="p">,</span><span class="w">
  </span><span class="s2">"node_modules/@vscode-elements/elements-lite/components/label/label.css"</span><span class="p">,</span><span class="w">
  </span><span class="s2">"node_modules/@vscode-elements/elements-lite/components/textfield/textfield.css"</span><span class="w">
</span><span class="p">]</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>If you now restart the application, the input fields in the webview of the custom editor will look like native Visual Studio Code components.</p>

<h3 id="vscode-elements-1">VSCode Elements</h3>

<p>If you like to use the Javascript enabled web components, you can use the <a href="https://vscode-elements.github.io/">VSCode Elements</a> component library.
Unfortunately the implementation of <a href="https://vscode-elements.github.io/">VSCode Elements</a> is based on the <a href="https://lit.dev/">Lit</a> library.
And using Lit Webcomponents with Angular requires some more work.</p>

<p>Add <code class="language-plaintext highlighter-rouge">@vscode-elements/elements</code> as a dependency in the <em>package.json</em> and reference the CSS files in the webview HTML.</p>

<ul>
  <li>Open a <strong>Terminal</strong></li>
  <li>Switch to the <em>vscode-extension</em> folder</li>
  <li>
    <p>Install <code class="language-plaintext highlighter-rouge">@vscode-elements/elements</code> as a <code class="language-plaintext highlighter-rouge">dependency</code></p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @vscode-elements/elements
</code></pre></div>    </div>
  </li>
</ul>

<p>To be able to use Lit Webcomponents in an Angular application, you need to implement a <a href="https://angular.dev/api/forms/ControlValueAccessor">ControlValueAccessor</a> as a <a href="https://angular.dev/guide/directives/directive-composition-api">Directive</a>.</p>

<ul>
  <li>
    <p>Create a new file <em>angular-extension/webview-ui/src/app/vscode-textfield-input.directive.ts</em></p>

    <ul>
      <li>Implement <code class="language-plaintext highlighter-rouge">ControlValueAccessor</code> with the name <code class="language-plaintext highlighter-rouge">VscodeTextfieldInputDirective</code></li>
      <li>Specify a <code class="language-plaintext highlighter-rouge">Directive</code> with the selector for the <code class="language-plaintext highlighter-rouge">vscode-textfield</code> HTML tag</li>
      <li>Add listener for <em>change</em> and <em>input</em> events</li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span>
  <span class="nx">Directive</span><span class="p">,</span>
  <span class="nx">ElementRef</span><span class="p">,</span>
  <span class="nx">forwardRef</span><span class="p">,</span>
  <span class="nx">HostListener</span><span class="p">,</span>
  <span class="nx">Provider</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@angular/core</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ControlValueAccessor</span><span class="p">,</span> <span class="nx">NG_VALUE_ACCESSOR</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@angular/forms</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">VSCODE_TEXTFIELD_INPUT_VALUE_ACCESSOR</span><span class="p">:</span> <span class="nx">Provider</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">provide</span><span class="p">:</span> <span class="nx">NG_VALUE_ACCESSOR</span><span class="p">,</span>
  <span class="na">useExisting</span><span class="p">:</span> <span class="nf">forwardRef</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">VscodeTextfieldInputDirective</span><span class="p">),</span>
  <span class="na">multi</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">};</span>

<span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
  <span class="na">selector</span><span class="p">:</span> <span class="dl">"</span><span class="s2">vscode-textfield</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nx">VSCODE_TEXTFIELD_INPUT_VALUE_ACCESSOR</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">VscodeTextfieldInputDirective</span> <span class="k">implements</span> <span class="nx">ControlValueAccessor</span> <span class="p">{</span>
  <span class="nx">val</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
  <span class="nl">onChange</span><span class="p">:</span> <span class="kr">any</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{};</span>
  <span class="nl">onTouched</span><span class="p">:</span> <span class="kr">any</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{};</span>

  <span class="kd">get</span> <span class="nf">value</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">val</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="kd">set</span> <span class="nf">value</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">value</span> <span class="o">||</span> <span class="nx">value</span> <span class="o">===</span> <span class="k">this</span><span class="p">.</span><span class="nx">val</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">val</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">elRef</span><span class="p">.</span><span class="nx">nativeElement</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>

    <span class="k">this</span><span class="p">.</span><span class="nf">onChange</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">val</span><span class="p">);</span>
    <span class="k">this</span><span class="p">.</span><span class="nf">onTouched</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="nx">elRef</span><span class="p">:</span> <span class="nx">ElementRef</span><span class="p">)</span> <span class="p">{}</span>

  <span class="nf">registerOnChange</span><span class="p">(</span><span class="nx">fn</span><span class="p">:</span> <span class="kr">any</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">onChange</span> <span class="o">=</span> <span class="nx">fn</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">registerOnTouched</span><span class="p">(</span><span class="nx">fn</span><span class="p">:</span> <span class="kr">any</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">onTouched</span> <span class="o">=</span> <span class="nx">fn</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">writeValue</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">val</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">elRef</span><span class="p">.</span><span class="nx">nativeElement</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="p">@</span><span class="nd">HostListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">change</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="dl">"</span><span class="s2">$event</span><span class="dl">"</span><span class="p">])</span>
  <span class="nf">onHostChange</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="nx">Event</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">elRef</span><span class="p">.</span><span class="nx">nativeElement</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="p">@</span><span class="nd">HostListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">input</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="dl">"</span><span class="s2">$event</span><span class="dl">"</span><span class="p">])</span>
  <span class="nf">onHostInput</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="nx">Event</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">elRef</span><span class="p">.</span><span class="nx">nativeElement</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Open the file <em>angular-extension/webview-ui/src/app/app.component.ts</em></p>

    <ul>
      <li>Import the VSCode Elements webcomponents</li>
      <li>Import the <code class="language-plaintext highlighter-rouge">ControlValueAccessor</code></li>
      <li>Add the <code class="language-plaintext highlighter-rouge">CUSTOM_ELEMENTS_SCHEMA</code></li>
    </ul>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Component</span><span class="p">,</span> <span class="nx">CUSTOM_ELEMENTS_SCHEMA</span><span class="p">,</span> <span class="nx">HostListener</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/core</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">FormControl</span><span class="p">,</span> <span class="nx">ReactiveFormsModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/forms</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">vscode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./utilities/vscode</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">VscodeTextfieldInputDirective</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./vscode-textfield-input.directive</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">'</span><span class="s1">../../node_modules/@vscode-elements/elements/dist/vscode-label</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">'</span><span class="s1">../../node_modules/@vscode-elements/elements/dist/vscode-textfield</span><span class="dl">'</span><span class="p">;</span>

<span class="p">@</span><span class="nd">Component</span><span class="p">({</span>
  <span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">app-root</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">ReactiveFormsModule</span><span class="p">,</span> <span class="nx">VscodeTextfieldInputDirective</span><span class="p">],</span>
  <span class="na">templateUrl</span><span class="p">:</span> <span class="dl">'</span><span class="s1">./app.component.html</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">styleUrl</span><span class="p">:</span> <span class="dl">'</span><span class="s1">./app.component.css</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">schemas</span><span class="p">:</span> <span class="p">[</span><span class="nx">CUSTOM_ELEMENTS_SCHEMA</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nc">AppComponent</span> <span class="p">{</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Open the file <em>angular-extension/webview-ui/src/app/app.component.html</em></p>

    <ul>
      <li>Replace the <code class="language-plaintext highlighter-rouge">label</code> tag with <code class="language-plaintext highlighter-rouge">vscode-label</code>, and the <code class="language-plaintext highlighter-rouge">input</code> tag with <code class="language-plaintext highlighter-rouge">vscode-textfield</code></li>
      <li>Remove the <code class="language-plaintext highlighter-rouge">className</code> attributes if you applied the VSCode Elements Lite CSS before.</li>
    </ul>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;main</span> <span class="na">class=</span><span class="s">"main"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;h1&gt;</span>Angular Person Editor<span class="nt">&lt;/h1&gt;</span>
  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"content"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"person"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"row"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;vscode-label</span> <span class="na">for=</span><span class="s">"firstname"</span><span class="nt">&gt;</span>Firstname:<span class="nt">&lt;/vscode-label&gt;</span>
        <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"value"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;vscode-textfield</span>
            <span class="na">type=</span><span class="s">"text"</span>
            <span class="na">[formControl]=</span><span class="s">"firstname"</span>
            <span class="na">(input)=</span><span class="s">"updateDocument()"</span>
          <span class="nt">/&gt;</span>
        <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"row"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;vscode-label</span> <span class="na">for=</span><span class="s">"lastname"</span><span class="nt">&gt;</span>Lastname:<span class="nt">&lt;/vscode-label&gt;</span>
        <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"value"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;vscode-textfield</span>
            <span class="na">type=</span><span class="s">"text"</span>
            <span class="na">[formControl]=</span><span class="s">"lastname"</span>
            <span class="na">(input)=</span><span class="s">"updateDocument()"</span>
          <span class="nt">/&gt;</span>
        <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
    <span class="nt">&lt;/div&gt;</span>
  <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/main&gt;</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>If you now restart the application, the input fields in the webview of the custom editor will look and behave like native Visual Studio Code components.</p>

<p>Here are some links that helped me finding the above solution:</p>

<ul>
  <li><a href="https://www.thinktecture.com/en/web-components/web-component-forms-integration-with-lit-and-angular/">Master Web Component Forms Integration</a></li>
  <li><a href="https://stackoverflow.com/a/76307099/2955861">Stackoverflow - Use lit-element web component in Angular reactive form</a></li>
  <li><a href="https://luixaviles.com/2021/10/how-to-integrate-web-components-using-lit-in-angular/">How to integrate Web Components using Lit in Angular</a></li>
  <li><a href="https://www.holisticon.de/en/knowledge-hub/angular-custom-form-components-and-the-controlvalueaccessor">Angular: custom form components and the ControlValueAccessor</a></li>
</ul>

<h2 id="react-webview-implementation">React Webview Implementation</h2>

<p>When discussing about which Javascript framework should be used for implementing the webview, there can be several answers. The most prominent are Angular or React as far as I can tell. There are also others like Vue or Svelte, but well, in that area you are never done.</p>

<p>In the following chapter we create a React project that serves as the webview frontend of the Visual Studio Code Extension.</p>

<p><em><strong>Note:</strong></em><br />
The following description is based on the <a href="https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/frameworks/hello-world-react-vite">vscode-webview-ui-toolkit-samples Hello World (React + Vite) example</a>. I reused several parts of the sample code and adapted it where necessary.<br />
Also note that I am not an expert in developing React applications. So probably the following sample can be more efficient, but the goal is to help getting started in setting up a Visual Studio Code Extension with a React webview.</p>

<ul>
  <li>
    <p>Create a new Visual Studio Code Extension project.</p>

    <ul>
      <li>
        <p>Open a <strong>Terminal</strong> and execute the following command</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yo code
</code></pre></div>        </div>
      </li>
      <li>
        <p>Answer the questions of the wizard for example like shown below:</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? react-extension
# ? What's the identifier of your extension? react-extension
# ? What's the description of your extension? LEAVE BLANK
# ? Initialize a git repository? N
# ? Which bundler to use? unbundled
# ? Which package manager to use? npm

# ? Do you want to open the new folder with Visual Studio Code? Skip
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Delete the created <em>react-extension/.vscode</em> folder</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm -rf react-extension/.vscode
</code></pre></div>    </div>
  </li>
  <li>
    <p>Open the <em>.vscode/launch.json</em></p>

    <ul>
      <li>Add the <code class="language-plaintext highlighter-rouge">extensionDevelopmentPath</code> to the newly created <em>react-extension</em> to the <code class="language-plaintext highlighter-rouge">args</code> get both extensions started on launch</li>
      <li>
        <p>Add the path to the build result directory of the newly created <em>react-extension</em> to the <code class="language-plaintext highlighter-rouge">outFiles</code></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.2.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Run Extension"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"extensionHost"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/vscode-extension"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/angular-extension"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"--extensionDevelopmentPath=${workspaceFolder}/react-extension"</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"outFiles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"${workspaceFolder}/vscode-extension/out/**/*.js"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${workspaceFolder}/angular-extension/dist/**/*.js"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${workspaceFolder}/react-extension/out/**/*.js"</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"preLaunchTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${defaultBuildTask}"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>To create a React application you typically use either <a href="https://create-react-app.dev/">CRA</a> or <a href="https://vite.dev/guide/">Vite</a>.
I tried to use CRA and came across issue <a href="https://github.com/facebook/react/issues/32016">React Issue 32016</a>.
There is a statement that <em>“CRA has become somewhat outdated”</em>.
Instead of trying to workaround the issue, I therefore decided to switch to <a href="https://vite.dev/guide/">Vite</a> directly.</p>

<ul>
  <li>
    <p>Create a React App using <a href="https://vite.dev/guide/">Vite</a></p>

    <ul>
      <li>Open a <strong>Terminal</strong></li>
      <li>Switch to the <em>react-extension</em> folder</li>
      <li>
        <p>Execute the following command to create the react application</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm create vite@latest webview-ui
</code></pre></div>        </div>

        <p>The wizard will ask the following questions. Answer them for example like shown below:</p>

        <div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">Need to install the following packages:
create-vite@6.1.1
Ok to proceed? (y) y


</span><span class="gp">&gt;</span><span class="w"> </span>react-extension@0.0.1 npx
<span class="gp">&gt;</span><span class="w"> </span>create-vite webview-ui
<span class="go">
✔ Select a framework: › React
✔ Select a variant: › TypeScript
</span></code></pre></div>        </div>

        <p>This command creates a new React project in the Typescript variant in the folder <em>react-extension/webview-ui</em>.</p>
      </li>
      <li>
        <p>Switch to the new generated <code class="language-plaintext highlighter-rouge">webview-ui</code> folder and install the dependencies</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd webview-ui
npm install
</code></pre></div>        </div>
      </li>
      <li>
        <p>Update the <em>launch.json</em></p>

        <ul>
          <li>
            <p>Add the following configuration to <em>.vscode/launch.json</em></p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Start React"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"chrome"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"preLaunchTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm: dev"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:5173/"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"webRoot"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/react-extension/webview-ui"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>            </div>
          </li>
        </ul>
      </li>
      <li>
        <p>Update the <em>tasks.json</em></p>

        <ul>
          <li>
            <p>Add the following configuration to <em>.vscode/tasks.json</em> and add the <code class="language-plaintext highlighter-rouge">cwd</code> option</p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dev"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"owner"</span><span class="p">:</span><span class="w"> </span><span class="s2">"typescript"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"pattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(.*?)"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"VITE v(.*)  ready in </span><span class="se">\\</span><span class="s2">d* ms"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/react-extension/webview-ui"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>            </div>
          </li>
        </ul>
      </li>
      <li>
        <p>Update the <em>react-extension/webview-ui/package.json</em></p>

        <ul>
          <li>Change the <code class="language-plaintext highlighter-rouge">name</code> to <code class="language-plaintext highlighter-rouge">react-webview-ui</code></li>
          <li>
            <p>Update the <code class="language-plaintext highlighter-rouge">dev</code> script to ensure that the server is started on port 5173</p>

            <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vite --port 5173"</span><span class="err">,</span><span class="w">
</span></code></pre></div>            </div>
          </li>
        </ul>
      </li>
    </ul>

    <p>To verify that the setup works, you can now either run the task via</p>
  </li>
  <li>Press <em>F1</em> - <em>Tasks: Run Task</em> - <em>npm:dev</em></li>
  <li>Run the launch configuration via <em>Run and Debug</em> - Select <em>Start React</em> in the dropdown - <em>Start Debugging</em></li>
</ul>

<p>This will start the React + Vite + TS application and host it via http://localhost:5173.</p>

<h3 id="prepare-the-react-application-as-webview">Prepare the React application as webview</h3>

<p>In the next steps the two projects need to be configured so the React application can be used as webview in the Visual Studio Code Extension.</p>

<ul>
  <li>
    <p>Update <em>react-extension/tsconfig.json</em></p>

    <ul>
      <li>Change the <code class="language-plaintext highlighter-rouge">outDir</code> to <code class="language-plaintext highlighter-rouge">./dist</code><br />
This might not be really necessary, but having a good naming convention for the folders helps in understanding the structure. The <code class="language-plaintext highlighter-rouge">dist</code> folder will contain the content that gets distributed in the packaged Visual Studio Code Extension.</li>
      <li>Add <code class="language-plaintext highlighter-rouge">DOM</code> to the <code class="language-plaintext highlighter-rouge">lib</code> configuration</li>
      <li>
        <p>Configure <code class="language-plaintext highlighter-rouge">exclude</code> to avoid <code class="language-plaintext highlighter-rouge">node_modules</code> and <code class="language-plaintext highlighter-rouge">webview-ui</code> being included in the codebase</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"module"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Node16"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ES2022"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"outDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./dist"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"lib"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ES2022"</span><span class="p">,</span><span class="w"> </span><span class="s2">"DOM"</span><span class="p">],</span><span class="w">
    </span><span class="nl">"sourceMap"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"rootDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"src"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"strict"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"exclude"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"node_modules"</span><span class="p">,</span><span class="w"> </span><span class="s2">".vscode-test"</span><span class="p">,</span><span class="w"> </span><span class="s2">"webview-ui"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Update <em>react-extension/package.json</em>
    <ul>
      <li>Change <code class="language-plaintext highlighter-rouge">main</code> to point to <code class="language-plaintext highlighter-rouge">./dist/extension.js</code></li>
    </ul>
  </li>
  <li>Update <em>.vscode/launch.json</em>
    <ul>
      <li>Correct the <code class="language-plaintext highlighter-rouge">outFiles</code> value to <code class="language-plaintext highlighter-rouge">"${workspaceFolder}/react-extension/dist/**/*.js"</code></li>
    </ul>
  </li>
  <li>
    <p>Update <em>react-extension/.vscodeignore</em> to ignore all <em>webview-ui</em> files except the <em>build</em> directory</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Ignore extension configs
.vscode/**

# Ignore test files
.vscode-test/**
out/test/**

# Ignore source code
src/**

# Ignore all webview-ui files except the build directory
webview-ui/src/**
webview-ui/public/**
webview-ui/scripts/**
webview-ui/index.html
webview-ui/README.md
webview-ui/package.json
webview-ui/package-lock.json
webview-ui/node_modules/**

# Ignore Misc
.yarnrc
vsc-extension-quickstart.md
**/.gitignore
**/tsconfig.json
**/vite.config.ts
**/.eslintrc.json
**/*.map
**/*.ts
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update the <em>react-extension/webview-ui/vite.config.ts</em></p>

    <ul>
      <li>
        <p>Add <code class="language-plaintext highlighter-rouge">build</code> options to specify the output folder and the <code class="language-plaintext highlighter-rouge">rollupOptions</code> to disable the filename hashing in the build result and configure a relative base path via <code class="language-plaintext highlighter-rouge">base: ""</code> as described in <a href="https://vite.dev/guide/build.html#public-base-path">vite.dev - Public Base Path</a></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vite</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">react</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@vitejs/plugin-react</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// https://vite.dev/config/</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
  <span class="na">plugins</span><span class="p">:</span> <span class="p">[</span><span class="nf">react</span><span class="p">()],</span>
  <span class="na">base</span><span class="p">:</span> <span class="dl">""</span><span class="p">,</span>
  <span class="na">build</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">outDir</span><span class="p">:</span> <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">rollupOptions</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">output</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">entryFileNames</span><span class="p">:</span> <span class="s2">`assets/[name].js`</span><span class="p">,</span>
        <span class="na">chunkFileNames</span><span class="p">:</span> <span class="s2">`assets/[name].js`</span><span class="p">,</span>
        <span class="na">assetFileNames</span><span class="p">:</span> <span class="s2">`assets/[name].[ext]`</span><span class="p">,</span>
      <span class="p">},</span>
    <span class="p">},</span>
  <span class="p">},</span>
<span class="p">});</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Delete the file <em>vite.config.js</em> and <em>vite.config.map.js</em> if they exist</li>
  <li>Update <em>react-extension/webview-ui/.gitignore</em>
    <ul>
      <li>Add the <em>build</em> folder</li>
    </ul>
  </li>
  <li>After we changed the output folder to <em>build</em> in the <em>vite.config.ts</em> you can delete the folder <em>react-extension/webview-ui/dist</em> if it was already created.</li>
  <li>After we changed the output folder to <em>dist</em> in the <em>tsconfig.json</em> you can delete the folder <em>react-extension/out</em> if it was already created.</li>
</ul>

<p>Test if the build succeeds</p>

<ul>
  <li>Open a <strong>Terminal</strong>
    <ul>
      <li>Switch to the <em>react-extension/webview-ui</em> folder</li>
      <li>Call <code class="language-plaintext highlighter-rouge">npm run build</code><br />
This should create the folder <em>react-extension/webview-ui/build</em></li>
      <li>Switch to the <em>react-extension</em> folder</li>
      <li>Call <code class="language-plaintext highlighter-rouge">npm run compile</code><br />
This should create the folder <em>react-extension/dist</em></li>
    </ul>
  </li>
</ul>

<h3 id="use-the-react-application-as-webview">Use the React Application as webview</h3>

<p>The next step is to use the React Application as a Visual Studio Code Extension WebView.
For this we need to perform the same steps as described previously in <a href="#implement-the-visual-studio-code-extension">Implement the Visual Studio Code Extension</a>.</p>

<ul>
  <li>
    <p>Open the <em>react-extension/package.json</em></p>

    <ul>
      <li>
        <p>Replace the <code class="language-plaintext highlighter-rouge">contributes</code> section with the following snippet:</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"contributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"customEditors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"viewType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"react-extension.personEditor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"React Person Editor"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"selector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"filenamePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*.person"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Create a new file <em>react-extension/src/personEditor.ts</em></p>

    <ul>
      <li>Copy the content from <em>vscode-extension/src/personEditor.ts</em><br />
<em><strong>Note:</strong></em><br />
If you have not created that file in the previous vanilla HTML, CSS, Javascript part, follow the steps in <a href="#implement-the-visual-studio-code-extension">Implement the Visual Studio Code Extension</a></li>
      <li>
        <p>Change the value of <code class="language-plaintext highlighter-rouge">viewType</code> to <code class="language-plaintext highlighter-rouge">react-extension.personEditor</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="nx">viewType</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">react-extension.personEditor</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Optional:<br />
Extend the <code class="language-plaintext highlighter-rouge">webview.options</code> to restrict the webview to only load resources from <em>dist</em> and <em>webview-ui/build</em> directories</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Setup initial content for the webview</span>
<span class="nx">webviewPanel</span><span class="p">.</span><span class="nx">webview</span><span class="p">.</span><span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
  <span class="c1">// Enable scripts in the webview</span>
  <span class="na">enableScripts</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>

  <span class="c1">// Restrict the webview to only load resources from the `dist` and `webview-ui/build` directories</span>
  <span class="na">localResourceRoots</span><span class="p">:</span> <span class="p">[</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">dist</span><span class="dl">"</span><span class="p">),</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span> <span class="dl">"</span><span class="s2">webview-ui/build</span><span class="dl">"</span><span class="p">),</span>
  <span class="p">],</span>
<span class="p">};</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Change the implementation of <code class="language-plaintext highlighter-rouge">getWebviewHtml()</code> so that it basically returns the same HTML as in <em>react-extension/webview-ui/index.html</em>. But instead of relative URLs to resources like CSS and the Javascript files, special WebView URIs are used. This is necessary so the resources can be correctly resolved inside a webview, which is described in <a href="https://code.visualstudio.com/api/extension-guides/webview#loading-local-content">Loading local content</a>.</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Get the static html used for the editor webviews.
 */</span>
<span class="k">private</span> <span class="nf">getWebviewHtml</span><span class="p">(</span><span class="nx">webview</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">Webview</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
  <span class="c1">// The CSS file from the React build output</span>
  <span class="kd">const</span> <span class="nx">stylesUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">assets</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">index.css</span><span class="dl">"</span>
    <span class="p">)</span>
  <span class="p">);</span>
  <span class="c1">// The JS file from the React build output</span>
  <span class="kd">const</span> <span class="nx">scriptUri</span> <span class="o">=</span> <span class="nx">webview</span><span class="p">.</span><span class="nf">asWebviewUri</span><span class="p">(</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nx">Uri</span><span class="p">.</span><span class="nf">joinPath</span><span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">extensionUri</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">webview-ui</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">assets</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">index.js</span><span class="dl">"</span>
    <span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">nonce</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getNonce</span><span class="p">();</span>

  <span class="c1">// Tip: Install the es6-string-html VS Code extension to enable code highlighting below</span>
  <span class="k">return</span> <span class="cm">/*html*/</span> <span class="s2">`
  &lt;!DOCTYPE html&gt;
  &lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
      &lt;meta
        http-equiv="Content-Security-Policy"
        content="default-src 'none'; img-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; font-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; style-src </span><span class="p">${</span><span class="nx">webview</span><span class="p">.</span><span class="nx">cspSource</span><span class="p">}</span><span class="s2">; script-src 'nonce-</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">';"&gt;
    &lt;link rel="stylesheet" type="text/css" href="</span><span class="p">${</span><span class="nx">stylesUri</span><span class="p">}</span><span class="s2">"&gt;
    &lt;title&gt;Person Editor&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id="root"&gt;&lt;/div&gt;
    &lt;script type="module" nonce="</span><span class="p">${</span><span class="nx">nonce</span><span class="p">}</span><span class="s2">" src="</span><span class="p">${</span><span class="nx">scriptUri</span><span class="p">}</span><span class="s2">"&gt;&lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;`</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Change the content of <em>react-extension/src/extension.ts</em></p>

    <ul>
      <li>
        <p>Replace the content with the following code</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The module 'vscode' contains the Visual Studio Code extensibility API</span>
<span class="c1">// Import the module and reference it with the alias vscode in your code below</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vscode</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PersonEditorProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./personEditor</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// This method is called when your extension is activated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">activate</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">vscode</span><span class="p">.</span><span class="nx">ExtensionContext</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Register our custom editor provider</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">PersonEditorProvider</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">context</span><span class="p">));</span>
<span class="p">}</span>

<span class="c1">// This method is called when your extension is deactivated</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">deactivate</span><span class="p">()</span> <span class="p">{}</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<h3 id="script-updates-1">Script Updates</h3>

<p>We now have a React application project as frontend inside a Visual Studio Code Extension project.
The build and run scripts are not automatically connected.
For example, if you change code in the React application and then launch the Visual Studio Code Extension, the changes are not automatically reflected.
This is because the Visual Studio Code Extension is using the build results of the React Application, and not the sources.</p>

<p>To connect the two projects, we need to update the <code class="language-plaintext highlighter-rouge">scripts</code> section in <em>react-extension/webview-ui/package.json</em> and <em>react-extension/package.json</em></p>

<p><em><strong>Note:</strong></em><br />
The <code class="language-plaintext highlighter-rouge">watch</code> script needs to execute the watch operations in parallel and not sequentially.
While one solution to this could be the usage of <code class="language-plaintext highlighter-rouge">&amp;</code> instead of <code class="language-plaintext highlighter-rouge">&amp;&amp;</code> on a Unix system, a better and OS independent solution is the usage of <a href="https://www.npmjs.com/package/concurrently"><code class="language-plaintext highlighter-rouge">concurrently</code></a>.</p>

<ul>
  <li>
    <p>Open the file <em>react-extension/webview-ui/package.json</em></p>

    <ul>
      <li>
        <p>Add the following watch script, which is needed because the Visual Studio Code Extension consumes the created static site<br />
See <a href="https://vite.dev/guide/build">Building for Production</a></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"watch"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vite build --watch"</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Add <code class="language-plaintext highlighter-rouge">concurrently</code> as a <code class="language-plaintext highlighter-rouge">devDependency</code> to the <em>react-extension/package.json</em></p>

    <ul>
      <li>Open a <strong>Terminal</strong></li>
      <li>Switch to the <em>react-extension</em> folder</li>
      <li>
        <p>Execute the following command</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D concurrently
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Open <em>react-extension/package.json</em></p>

    <ul>
      <li>
        <p>Add <code class="language-plaintext highlighter-rouge">scripts</code> for the <em>webview-ui</em></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"start:webview"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix webview-ui run dev"</span><span class="err">,</span><span class="w">
</span><span class="nl">"build:webview"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix webview-ui run build"</span><span class="err">,</span><span class="w">
</span><span class="nl">"watch:webview"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm --prefix webview-ui run watch"</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p>Update <code class="language-plaintext highlighter-rouge">vscode:prepublish</code> to call the <code class="language-plaintext highlighter-rouge">build:webview</code> script additionally</p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"vscode:prepublish"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run build:webview &amp;&amp; npm run compile"</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>
        <p>Update the <code class="language-plaintext highlighter-rouge">watch</code> script to call also <code class="language-plaintext highlighter-rouge">watch:webview</code> by using <code class="language-plaintext highlighter-rouge">concurrently</code></p>

        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"watch"</span><span class="p">:</span><span class="w"> </span><span class="s2">"concurrently --kill-others </span><span class="se">\"</span><span class="s2">npm run watch:webview</span><span class="se">\"</span><span class="s2"> </span><span class="se">\"</span><span class="s2">tsc -watch -p ./</span><span class="se">\"</span><span class="s2">"</span><span class="err">,</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>To make the updated <code class="language-plaintext highlighter-rouge">watch</code> script also work when launching the extension, the corresponding task in <em>.vscode/tasks.json</em> needs to be updated like this (notice the <code class="language-plaintext highlighter-rouge">endsPattern</code>):</p>

<p>If you only want to watch the <em>react-extension</em>, update the configuration like this (notice the <code class="language-plaintext highlighter-rouge">endsPattern</code>). Replace the <code class="language-plaintext highlighter-rouge">npm:watch</code> script at the top of the <em>tasks.json</em>.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"base"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc-watch"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(.*?)"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"built in </span><span class="se">\\</span><span class="s2">d*ms"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"presentation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"reveal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"never"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"isDefault"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/react-extension"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>If you want to watch the <em>vscode-extension</em>, the <em>angular-extension</em> and the <em>react-extension</em> at the same time, update the configuration like this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Watch Extensions"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"isDefault"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"dependsOn"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"VS Code Extension Watch"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"Angular Extension Watch"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"React Extension Watch"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"VS Code Extension Watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shell"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc-watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"presentation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"reveal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"never"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/vscode-extension"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Angular Extension Watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shell"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"base"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc-watch"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(.*?)"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bundle generation complete"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"presentation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"reveal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"never"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/angular-extension"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"React Extension Watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shell"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"base"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$tsc-watch"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(.*?)"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"built in </span><span class="se">\\</span><span class="s2">d*ms"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"presentation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"reveal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"never"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="p">,</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/react-extension"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>The above snippet defines a <em>Compound Task</em>, makes it the default task that should be executed via <em>launch.json</em> <code class="language-plaintext highlighter-rouge">preLaunchTask</code> configuration and changes the task types from <code class="language-plaintext highlighter-rouge">npm</code> to <code class="language-plaintext highlighter-rouge">shell</code>.
This change is necessary because otherwise there is a name collision in the <em>Task auto-detection</em>.</p>

<p>To verify that it works</p>

<ul>
  <li>Launch the Visual Studio Code Extension(s)
    <ul>
      <li>Open <em>Run and Debug</em></li>
      <li>Ensure <em>Run Extension</em> is selected in the dropdown</li>
      <li>Click <em>Start Debugging</em> or press <em>F5</em></li>
    </ul>
  </li>
  <li>Right click on the file created in the previous example in the <em>Explorer</em></li>
  <li>Select <em>Open With…</em> - <em>React Person Editor</em></li>
</ul>

<p>This should open the webview with the content of the React application</p>

<ul>
  <li>In the Visual Studio Code instance that is used for development, open the file <em>react-extension/webview-ui/src/App.tsx</em></li>
  <li>Change something in code, e.g. by adding <code class="language-plaintext highlighter-rouge">May the force by with you</code>, and save the changes</li>
  <li>Switch to the Visual Studio Code instance launched for testing the extension
    <ul>
      <li>Either close and reopen the webview</li>
      <li>Reload the webview by pressing F1 - <em>Developer: Reload Webviews</em></li>
    </ul>
  </li>
</ul>

<p>You should now see the applied changes.</p>

<ul>
  <li>In the Visual Studio Code instance that is used for development, open the file <em>react-extension/src/personEditor.ts</em></li>
  <li>Change for example the webview HTML by adding <code class="language-plaintext highlighter-rouge">&lt;p&gt;Hello World&lt;/p&gt;</code> in the <code class="language-plaintext highlighter-rouge">&lt;body&gt;</code>.</li>
  <li>Switch to the Visual Studio Code instance launched for testing the extension</li>
  <li>Reload the <strong>Extension Development Host</strong> so that it picks up the changes. You have two options to do this:
    <ul>
      <li>Click on the debug restart action</li>
      <li>Press <code class="language-plaintext highlighter-rouge">Ctrl + R</code> / <code class="language-plaintext highlighter-rouge">Cmd + R</code> in the <strong>Extension Development Host</strong> window</li>
    </ul>
  </li>
</ul>

<p>You should see the applied changes now. If not reopen the webview and verify that the applied changes are visible.</p>

<p><em><strong>Note:</strong></em><br />
Don’t forget to remove the modification to the webview HTML, so it does not affect the next steps.</p>

<h3 id="react-editor-implementation">React Editor Implementation</h3>

<p>The next step is to implement a custom editor, just like in the examples before.
It uses the same principles with regards to communication between extension and webview, and looks quite the same.
But of course we use React for the UI implementation.</p>

<ul>
  <li>
    <p>Add <code class="language-plaintext highlighter-rouge">@types/vscode-webview</code> as a <code class="language-plaintext highlighter-rouge">devDependency</code> to the <em>react-extension/webview-ui</em> project</p>

    <ul>
      <li>Open a <strong>Terminal</strong></li>
      <li>Switch to the folder <em>react-extension/webview-ui</em></li>
      <li>
        <p>Run the following command</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -D @types/vscode-webview
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Create a new folder <em>angular-extension/webview-ui/src/utilities</em></li>
  <li>
    <p>Create a new file <em>vscode.ts</em> in the new folder</p>

    <ul>
      <li>
        <p>Add the following code, which is a copy of <a href="https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-vite/webview-ui/src/utilities/vscode.ts">vscode-webview-ui-toolkit-samples</a></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">WebviewApi</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vscode-webview</span><span class="dl">"</span><span class="p">;</span>

<span class="cm">/**
 * A utility wrapper around the acquireVsCodeApi() function, which enables
 * message passing and state management between the webview and extension
 * contexts.
 *
 * This utility also enables webview code to be run in a web browser-based
 * dev server by using native web browser features that mock the functionality
 * enabled by acquireVsCodeApi.
 */</span>
<span class="kd">class</span> <span class="nc">VSCodeAPIWrapper</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">vsCodeApi</span><span class="p">:</span> <span class="nx">WebviewApi</span><span class="o">&lt;</span><span class="nx">unknown</span><span class="o">&gt;</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>

  <span class="nf">constructor</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Check if the acquireVsCodeApi function exists in the current development</span>
    <span class="c1">// context (i.e. VS Code development window or web browser)</span>
    <span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">acquireVsCodeApi</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">function</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span> <span class="o">=</span> <span class="nf">acquireVsCodeApi</span><span class="p">();</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Post a message (i.e. send arbitrary data) to the owner of the webview.
   *
   * @remarks When running webview code inside a web browser, postMessage will instead
   * log the given message to the console.
   *
   * @param message Abitrary data (must be JSON serializable) to send to the extension context.
   */</span>
  <span class="k">public</span> <span class="nf">postMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">message</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Get the persistent state stored for this webview.
   *
   * @remarks When running webview source code inside a web browser, getState will retrieve state
   * from local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
   *
   * @return The current state or `undefined` if no state has been set.
   */</span>
  <span class="k">public</span> <span class="nf">getState</span><span class="p">():</span> <span class="nx">unknown</span> <span class="o">|</span> <span class="kc">undefined</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">.</span><span class="nf">getState</span><span class="p">();</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nf">getItem</span><span class="p">(</span><span class="dl">"</span><span class="s2">vscodeState</span><span class="dl">"</span><span class="p">);</span>
      <span class="k">return</span> <span class="nx">state</span> <span class="p">?</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">:</span> <span class="kc">undefined</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Set the persistent state stored for this webview.
   *
   * @remarks When running webview source code inside a web browser, setState will set the given
   * state using local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
   *
   * @param newState New persisted state. This must be a JSON serializable object. Can be retrieved
   * using {@link getState}.
   *
   * @return The new state.
   */</span>
  <span class="k">public</span> <span class="nx">setState</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">unknown</span> <span class="o">|</span> <span class="kc">undefined</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">newState</span><span class="p">:</span> <span class="nx">T</span><span class="p">):</span> <span class="nx">T</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">vsCodeApi</span><span class="p">.</span><span class="nf">setState</span><span class="p">(</span><span class="nx">newState</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">localStorage</span><span class="p">.</span><span class="nf">setItem</span><span class="p">(</span><span class="dl">"</span><span class="s2">vscodeState</span><span class="dl">"</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">newState</span><span class="p">));</span>
      <span class="k">return</span> <span class="nx">newState</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Exports class singleton to prevent multiple invocations of acquireVsCodeApi.</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">vscode</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">VSCodeAPIWrapper</span><span class="p">();</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>The <code class="language-plaintext highlighter-rouge">VSCodeAPIWrapper</code> is used for</p>
        <ul>
          <li>Aquiring the VSCode API</li>
          <li>Managing the persistent state of the webview</li>
          <li>Posting messages from the webview to the extension</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <p>Replace the content of <em>react-extension/webview-ui/src/App.tsx</em> with the following snippet</p>

    <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">vscode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./utilities/vscode</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">./App.css</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">function</span> <span class="nf">App</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">personObject</span><span class="p">,</span> <span class="nx">setPersonObject</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="nx">loadState</span><span class="p">);</span>

  <span class="cm">/**
   * Load the initial state via vscode API or return an empty object as initial state.
   */</span>
  <span class="kd">function</span> <span class="nf">loadState</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// eslint-disable-next-line @typescript-eslint/no-explicit-any</span>
    <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">vscode</span><span class="p">.</span><span class="nf">getState</span><span class="p">()</span> <span class="kd">as </span><span class="kr">any</span><span class="p">;</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">text</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="p">{</span>
      <span class="na">firstname</span><span class="p">:</span> <span class="dl">""</span><span class="p">,</span>
      <span class="na">lastname</span><span class="p">:</span> <span class="dl">""</span><span class="p">,</span>
    <span class="p">};</span>
  <span class="p">}</span>

  <span class="c1">// Handle messages sent from the extension to the webview</span>
  <span class="nb">window</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span> <span class="c1">// The json data that the extension sent</span>
    <span class="k">switch </span><span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">case</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">message</span><span class="p">.</span><span class="nx">text</span><span class="p">;</span>

        <span class="c1">// Update our webview's content</span>
        <span class="nf">updateContent</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>

        <span class="c1">// Then persist state information.</span>
        <span class="c1">// This state is returned in the call to `vscode.getState` below when a webview is reloaded.</span>
        <span class="nx">vscode</span><span class="p">.</span><span class="nf">setState</span><span class="p">({</span> <span class="nx">text</span> <span class="p">});</span>

        <span class="k">return</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">});</span>

  <span class="cm">/**
   * Update the data shown in the document in the webview.
   */</span>
  <span class="kd">function</span> <span class="nf">updateContent</span><span class="p">(</span><span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">text</span> <span class="o">!==</span> <span class="dl">""</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">parsed</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span>
      <span class="nf">setPersonObject</span><span class="p">({</span>
        <span class="na">firstname</span><span class="p">:</span> <span class="nx">parsed</span><span class="p">.</span><span class="nx">firstname</span><span class="p">,</span>
        <span class="na">lastname</span><span class="p">:</span> <span class="nx">parsed</span><span class="p">.</span><span class="nx">lastname</span><span class="p">,</span>
      <span class="p">});</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * Update the document in the extension.
   */</span>
  <span class="kd">function</span> <span class="nf">updateDocument</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">vscode</span><span class="p">.</span><span class="nf">postMessage</span><span class="p">({</span>
      <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">updateDocument</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">text</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">personObject</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
    <span class="p">});</span>
  <span class="p">}</span>

  <span class="k">return </span><span class="p">(</span>
    <span class="o">&lt;&gt;</span>
      <span class="o">&lt;</span><span class="nx">main</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">main</span><span class="dl">"</span><span class="o">&gt;</span>
        <span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;</span><span class="nx">React</span> <span class="nx">Person</span> <span class="nx">Editor</span><span class="o">&lt;</span><span class="sr">/h1</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">content</span><span class="dl">"</span><span class="o">&gt;</span>
          <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">person</span><span class="dl">"</span><span class="o">&gt;</span>
            <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">row</span><span class="dl">"</span><span class="o">&gt;</span>
              <span class="o">&lt;</span><span class="nx">label</span> <span class="nx">htmlFor</span><span class="o">=</span><span class="dl">"</span><span class="s2">firstname</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Firstname</span><span class="p">:</span><span class="o">&lt;</span><span class="sr">/label</span><span class="err">&gt;
</span>              <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="o">&gt;</span>
                <span class="o">&lt;</span><span class="nx">input</span>
                  <span class="kd">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text</span><span class="dl">"</span>
                  <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">firstname</span><span class="dl">"</span>
                  <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">personObject</span><span class="p">.</span><span class="nx">firstname</span><span class="p">}</span>
                  <span class="nx">onChange</span><span class="o">=</span><span class="p">{(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                    <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
                    <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">;</span>
                    <span class="c1">// wait 500 ms before updating the document</span>
                    <span class="c1">// only update if in the meantime no other input was given</span>
                    <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                      <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                        <span class="nx">personObject</span><span class="p">.</span><span class="nx">firstname</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
                        <span class="nf">updateDocument</span><span class="p">();</span>
                      <span class="p">}</span>
                    <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
                  <span class="p">}}</span>
                <span class="sr">/</span><span class="err">&gt;
</span>              <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">row</span><span class="dl">"</span><span class="o">&gt;</span>
              <span class="o">&lt;</span><span class="nx">label</span> <span class="nx">htmlFor</span><span class="o">=</span><span class="dl">"</span><span class="s2">lastname</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Lastname</span><span class="p">:</span><span class="o">&lt;</span><span class="sr">/label</span><span class="err">&gt;
</span>              <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="o">&gt;</span>
                <span class="o">&lt;</span><span class="nx">input</span>
                  <span class="kd">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text</span><span class="dl">"</span>
                  <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">lastname</span><span class="dl">"</span>
                  <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">personObject</span><span class="p">.</span><span class="nx">lastname</span><span class="p">}</span>
                  <span class="nx">onChange</span><span class="o">=</span><span class="p">{(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                    <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
                    <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">;</span>
                    <span class="c1">// wait 500 ms before updating the document</span>
                    <span class="c1">// only update if in the meantime no other input was given</span>
                    <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                      <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                        <span class="nx">personObject</span><span class="p">.</span><span class="nx">lastname</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
                        <span class="nf">updateDocument</span><span class="p">();</span>
                      <span class="p">}</span>
                    <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
                  <span class="p">}}</span>
                <span class="sr">/</span><span class="err">&gt;
</span>              <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>          <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="sr">/main</span><span class="err">&gt;
</span>    <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt;
</span>  <span class="p">);</span>
<span class="p">}</span>

<span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span>
</code></pre></div>    </div>
  </li>
  <li>Update <em>react-extension/webview-ui/src/index.css</em>
    <ul>
      <li>Delete the whole content of the file</li>
    </ul>
  </li>
  <li>
    <p>Replace the content of <em>react-extension/webview-ui/src/App.css</em> with the following snippet</p>

    <div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">label</span> <span class="p">{</span>
  <span class="nl">font-weight</span><span class="p">:</span> <span class="m">600</span><span class="p">;</span>
  <span class="nl">display</span><span class="p">:</span> <span class="nb">block</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>Verify the changes and launch the Visual Studio Code Extension(s)
    <ul>
      <li>Open <em>Run and Debug</em></li>
      <li>Ensure <em>Run Extension</em> is selected in the dropdown</li>
      <li>Click <em>Start Debugging</em> or press <em>F5</em></li>
    </ul>
  </li>
  <li>Right click on the file created in the previous example in the <em>Explorer</em></li>
  <li>Select <em>Open With…</em> - <em>React Person Editor</em>
    <ul>
      <li>This should open the webview with the two input fields</li>
    </ul>
  </li>
</ul>

<h3 id="install-scripts-2">Install scripts</h3>

<p>Like with the Visual Studio Code Extension project and the Angular project before, add a install script to the <em>react-extension</em> that is called on <code class="language-plaintext highlighter-rouge">postCreateCommand</code> of the Dev Container. It needs to run <code class="language-plaintext highlighter-rouge">npm install</code> for the extension and the contained webview-ui project.</p>

<ul>
  <li>
    <p>Open the <em>react-extension/package.json</em> and add the following script to the <code class="language-plaintext highlighter-rouge">scripts</code> section</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"install:all"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm install &amp;&amp; cd webview-ui &amp;&amp; npm install"</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Open the <em>package.json</em> in the repository root and modify the <code class="language-plaintext highlighter-rouge">install:all</code> script to also handle the <em>react-extension</em> project</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vscode-theia-cookbook"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"private"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"install:all"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cd vscode-extension &amp;&amp; npm install &amp;&amp; cd ../angular-extension &amp;&amp; npm run install:all &amp;&amp; cd ../react-extension &amp;&amp; npm run install:all"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<h3 id="vscode-elements-lite-2">VSCode Elements Lite</h3>

<p>Like the vanilla HTML, CSS, Javascript example in the first section of this tutorial, the UI components do not look like native Visual Studio Code components. To get a more native Visual Studio Code look and feel you would need to spend quite some time to implement this yourself.</p>

<p>To avoid this effort, we can also use the <a href="https://vscode-elements.github.io/">VSCode Elements</a> component library, or in case of vanilla HTML and CSS, <a href="https://vscode-elements.github.io/elements-lite/">VSCode Elements Lite</a>.</p>

<p>As a first step, we will only use the styling variant by using <a href="https://vscode-elements.github.io/elements-lite/">VSCode Elements Lite</a>.</p>

<p>Add <code class="language-plaintext highlighter-rouge">@vscode-elements/elements-lite</code> as a dependency in the <em>package.json</em> and reference the CSS files in the webview HTML.</p>

<ul>
  <li>Open a <strong>Terminal</strong></li>
  <li>Switch to the <em>react-extension/webview-ui</em> folder</li>
  <li>
    <p>Install <code class="language-plaintext highlighter-rouge">@vscode-elements/elements-lite</code> as a <code class="language-plaintext highlighter-rouge">dependency</code></p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @vscode-elements/elements-lite
</code></pre></div>    </div>
  </li>
  <li>
    <p>Open the file <em>react-extension/webview-ui/src/App.tsx</em></p>

    <ul>
      <li>
        <p>Import the VSCode Elements Lite CSS files</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="dl">"</span><span class="s2">../node_modules/@vscode-elements/elements-lite/components/label/label.css</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">../node_modules/@vscode-elements/elements-lite/components/textfield/textfield.css</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>Add <code class="language-plaintext highlighter-rouge">className="vscode-label"</code> to the <code class="language-plaintext highlighter-rouge">label</code> tags, and <code class="language-plaintext highlighter-rouge">className="vscode-textfield"</code> to the <code class="language-plaintext highlighter-rouge">input</code> fields</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return </span><span class="p">(</span>
  <span class="o">&lt;&gt;</span>
    <span class="o">&lt;</span><span class="nx">main</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">main</span><span class="dl">"</span><span class="o">&gt;</span>
      <span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;</span><span class="nx">React</span> <span class="nx">Person</span> <span class="nx">Editor</span><span class="o">&lt;</span><span class="sr">/h1</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">content</span><span class="dl">"</span><span class="o">&gt;</span>
        <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">person</span><span class="dl">"</span><span class="o">&gt;</span>
          <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">row</span><span class="dl">"</span><span class="o">&gt;</span>
            <span class="o">&lt;</span><span class="nx">label</span> <span class="nx">htmlFor</span><span class="o">=</span><span class="dl">"</span><span class="s2">firstname</span><span class="dl">"</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">vscode-label</span><span class="dl">"</span><span class="o">&gt;</span>
              <span class="nx">Firstname</span><span class="p">:</span>
            <span class="o">&lt;</span><span class="sr">/label</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="o">&gt;</span>
              <span class="o">&lt;</span><span class="nx">input</span>
                <span class="kd">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text</span><span class="dl">"</span>
                <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">firstname</span><span class="dl">"</span>
                <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">vscode-textfield</span><span class="dl">"</span>
                <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">personObject</span><span class="p">.</span><span class="nx">firstname</span><span class="p">}</span>
                <span class="nx">onChange</span><span class="o">=</span><span class="p">{(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                  <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
                  <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">;</span>
                  <span class="c1">// wait 500 ms before updating the document</span>
                  <span class="c1">// only update if in the meantime no other input was given</span>
                  <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                    <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                      <span class="nx">personObject</span><span class="p">.</span><span class="nx">firstname</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
                      <span class="nf">updateDocument</span><span class="p">();</span>
                    <span class="p">}</span>
                  <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
                <span class="p">}}</span>
              <span class="sr">/</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>          <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>          <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">row</span><span class="dl">"</span><span class="o">&gt;</span>
            <span class="o">&lt;</span><span class="nx">label</span> <span class="nx">htmlFor</span><span class="o">=</span><span class="dl">"</span><span class="s2">lastname</span><span class="dl">"</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">vscode-label</span><span class="dl">"</span><span class="o">&gt;</span>
              <span class="nx">Lastname</span><span class="p">:</span>
            <span class="o">&lt;</span><span class="sr">/label</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="o">&gt;</span>
              <span class="o">&lt;</span><span class="nx">input</span>
                <span class="kd">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text</span><span class="dl">"</span>
                <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">lastname</span><span class="dl">"</span>
                <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">vscode-textfield</span><span class="dl">"</span>
                <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">personObject</span><span class="p">.</span><span class="nx">lastname</span><span class="p">}</span>
                <span class="nx">onChange</span><span class="o">=</span><span class="p">{(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                  <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
                  <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">;</span>
                  <span class="c1">// wait 500 ms before updating the document</span>
                  <span class="c1">// only update if in the meantime no other input was given</span>
                  <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                    <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                      <span class="nx">personObject</span><span class="p">.</span><span class="nx">lastname</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
                      <span class="nf">updateDocument</span><span class="p">();</span>
                    <span class="p">}</span>
                  <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
                <span class="p">}}</span>
              <span class="sr">/</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>          <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>    <span class="o">&lt;</span><span class="sr">/main</span><span class="err">&gt;
</span>  <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt;
</span><span class="p">);</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>If you now restart the application, the input fields in the webview of the custom editor will look like native Visual Studio Code components.</p>

<h3 id="vscode-elements-2">VSCode Elements</h3>

<p>If you like to use the Javascript enabled web components, you can use the <a href="https://vscode-elements.github.io/">VSCode Elements</a> component library.
In the previous section we only applied the styling approach by using VSCode Elements Lite.
But since React 19 web components are fully supported, therefore it is easy to use them directly.</p>

<p>Add <code class="language-plaintext highlighter-rouge">@vscode-elements/elements</code> as a dependency in the <em>package.json</em> and reference the CSS files in the webview HTML.</p>

<ul>
  <li>Open a <strong>Terminal</strong></li>
  <li>Switch to the <em>react-extension/webview-ui</em> folder</li>
  <li>
    <p>Install <code class="language-plaintext highlighter-rouge">@vscode-elements/elements</code> as a <code class="language-plaintext highlighter-rouge">dependency</code></p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @vscode-elements/elements
</code></pre></div>    </div>
  </li>
</ul>

<p>To use custom tag names, you must configure the TypeScript parser to recognize the custom elements.
This can be done via a TypeScript definition.</p>

<ul>
  <li>
    <p>Create the file <em>react-extension/webview-ui/src/global.d.ts</em></p>

    <ul>
      <li>
        <p>Add the following content</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">VscodeLabel</span><span class="p">,</span> <span class="nx">VscodeTextfield</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@vscode-elements/elements</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">type</span> <span class="nx">ElementProps</span><span class="o">&lt;</span><span class="nx">I</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nb">Partial</span><span class="o">&lt;</span><span class="nb">Omit</span><span class="o">&lt;</span><span class="nx">I</span><span class="p">,</span> <span class="kr">keyof</span> <span class="nx">HTMLElement</span><span class="o">&gt;&gt;</span><span class="p">;</span>
<span class="kd">type</span> <span class="nx">CustomEventHandler</span><span class="o">&lt;</span><span class="nx">E</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">(</span><span class="nx">e</span><span class="p">:</span> <span class="nx">E</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>

<span class="kd">type</span> <span class="nx">WebComponentProps</span><span class="o">&lt;</span><span class="nx">I</span> <span class="kd">extends</span> <span class="nx">HTMLElement</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">DetailedHTMLProps</span><span class="o">&lt;</span>
  <span class="nx">React</span><span class="p">.</span><span class="nx">HTMLAttributes</span><span class="o">&lt;</span><span class="nx">I</span><span class="o">&gt;</span><span class="p">,</span>
  <span class="nx">I</span>
<span class="o">&gt;</span> <span class="o">&amp;</span>
  <span class="nx">ElementProps</span><span class="o">&lt;</span><span class="nx">I</span><span class="o">&gt;</span><span class="p">;</span>

<span class="kr">declare</span> <span class="kr">module</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span> <span class="p">{</span>
  <span class="k">namespace</span> <span class="nx">JSX</span> <span class="p">{</span>
    <span class="kr">interface</span> <span class="nx">IntrinsicElements</span> <span class="p">{</span>
      <span class="dl">"</span><span class="s2">vscode-label</span><span class="dl">"</span><span class="p">:</span> <span class="nx">WebComponentProps</span><span class="o">&lt;</span><span class="nx">VscodeLabel</span><span class="o">&gt;</span><span class="p">;</span>
      <span class="dl">"</span><span class="s2">vscode-textfield</span><span class="dl">"</span><span class="p">:</span> <span class="nx">WebComponentProps</span><span class="o">&lt;</span><span class="nx">VscodeTextfield</span><span class="o">&gt;</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>        </div>

        <p><em><strong>Note:</strong></em><br />
A more complete example TypeScript definition can be found in the <a href="https://github.com/vscode-elements/examples/blob/react-vite/react-vite/src/global.d.ts">VSCode Elements Examples Repository</a>.</p>
      </li>
    </ul>
  </li>
  <li>
    <p>Update the file <em>react-extension/webview-ui/src/App.tsx</em></p>

    <ul>
      <li>
        <p>Add the imports for <code class="language-plaintext highlighter-rouge">vscode-label</code> and <code class="language-plaintext highlighter-rouge">vscode-textfield</code><br />
If you applied the VSCode Elements Lite CSS files before, replace them with the following imports.</p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="dl">"</span><span class="s2">@vscode-elements/elements/dist/vscode-label</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">@vscode-elements/elements/dist/vscode-textfield</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div>        </div>
      </li>
      <li>Replace the <code class="language-plaintext highlighter-rouge">label</code> tag with <code class="language-plaintext highlighter-rouge">vscode-label</code>, and the <code class="language-plaintext highlighter-rouge">input</code> tag with <code class="language-plaintext highlighter-rouge">vscode-textfield</code></li>
      <li>Remove the <code class="language-plaintext highlighter-rouge">className</code> attributes if you applied the VSCode Elements Lite CSS before.</li>
      <li>For <code class="language-plaintext highlighter-rouge">vscode-textfield</code> replace the <code class="language-plaintext highlighter-rouge">onChange</code> with <code class="language-plaintext highlighter-rouge">onInput</code><br />
<em><strong>Note:</strong></em><br />
React chose to make <code class="language-plaintext highlighter-rouge">onChange</code> behave like <code class="language-plaintext highlighter-rouge">onInput</code>.
The VSCode Elements web component library does not follow this decision.
Therefore we need to change <code class="language-plaintext highlighter-rouge">onChange</code> to <code class="language-plaintext highlighter-rouge">onInput</code> to achieve the same behavior as before.</li>
      <li>
        <p>In the <code class="language-plaintext highlighter-rouge">onInput</code> function retrieve the value via <code class="language-plaintext highlighter-rouge">event.currentTarget.value</code></p>

        <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return </span><span class="p">(</span>
  <span class="o">&lt;&gt;</span>
    <span class="o">&lt;</span><span class="nx">main</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">main</span><span class="dl">"</span><span class="o">&gt;</span>
      <span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;</span><span class="nx">React</span> <span class="nx">Person</span> <span class="nx">Editor</span><span class="o">&lt;</span><span class="sr">/h1</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">content</span><span class="dl">"</span><span class="o">&gt;</span>
        <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">person</span><span class="dl">"</span><span class="o">&gt;</span>
          <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">row</span><span class="dl">"</span><span class="o">&gt;</span>
            <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">label</span> <span class="nx">htmlFor</span><span class="o">=</span><span class="dl">"</span><span class="s2">firstname</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Firstname</span><span class="p">:</span><span class="o">&lt;</span><span class="sr">/vscode-label</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="o">&gt;</span>
              <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">textfield</span>
                <span class="kd">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text</span><span class="dl">"</span>
                <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">firstname</span><span class="dl">"</span>
                <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">personObject</span><span class="p">.</span><span class="nx">firstname</span><span class="p">}</span>
                <span class="nx">onInput</span><span class="o">=</span><span class="p">{(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                  <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
                  <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">;</span>
                  <span class="c1">// wait 500 ms before updating the document</span>
                  <span class="c1">// only update if in the meantime no other input was given</span>
                  <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                    <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                      <span class="nx">personObject</span><span class="p">.</span><span class="nx">firstname</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
                      <span class="nf">updateDocument</span><span class="p">();</span>
                    <span class="p">}</span>
                  <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
                <span class="p">}}</span>
              <span class="sr">/</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>          <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>          <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">row</span><span class="dl">"</span><span class="o">&gt;</span>
            <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">label</span> <span class="nx">htmlFor</span><span class="o">=</span><span class="dl">"</span><span class="s2">lastname</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Lastname</span><span class="p">:</span><span class="o">&lt;</span><span class="sr">/vscode-label</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="o">&gt;</span>
              <span class="o">&lt;</span><span class="nx">vscode</span><span class="o">-</span><span class="nx">textfield</span>
                <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">lastname</span><span class="dl">"</span>
                <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">personObject</span><span class="p">.</span><span class="nx">lastname</span><span class="p">}</span>
                <span class="nx">onInput</span><span class="o">=</span><span class="p">{(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                  <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
                  <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">;</span>
                  <span class="c1">// wait 500 ms before updating the document</span>
                  <span class="c1">// only update if in the meantime no other input was given</span>
                  <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                    <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">===</span> <span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                      <span class="nx">personObject</span><span class="p">.</span><span class="nx">lastname</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
                      <span class="nf">updateDocument</span><span class="p">();</span>
                    <span class="p">}</span>
                  <span class="p">},</span> <span class="mi">500</span><span class="p">);</span>
                <span class="p">}}</span>
              <span class="sr">/</span><span class="err">&gt;
</span>            <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>          <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>    <span class="o">&lt;</span><span class="sr">/main</span><span class="err">&gt;
</span>  <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt;
</span><span class="p">);</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>

<p>If you now restart the application, the input fields in the webview of the custom editor will look and behave like native Visual Studio Code components.</p>

<h2 id="conclusion">Conclusion</h2>

<p>At the end this blog post got again longer than I initially intended.
And it contains more information than I planned.
But as I use my blog posts as <em>my external memory</em> I am quite satisfied with the outcome.</p>

<p>To summarize again what the blog post covered:</p>

<ul>
  <li>Getting started with Visual Studio Code Extension development</li>
  <li>Creation of a custom Visual Studio Code Editor using Webviews</li>
  <li>Creating a Webview using
    <ul>
      <li>Vanilla HTML, CSS and Javascript</li>
      <li>Angular</li>
      <li>React</li>
    </ul>
  </li>
  <li>Usage of VSCode Elements to get an almost native Visual Studio Code look and feel</li>
</ul>

<p>If you have any improvements you would like to share, feel free to open an issue in the corresponding Github repository.
I am happy to learn and to improve this blog post and my external memory.</p>

<p>The sources for this and the following tutorials are located in my <a href="https://github.com/fipro78/vscode_theia_cookbook">Github repository</a>.</p>

<p>I hope you enjoyed this tutorial and I could share some information that I had to gather via various resources.</p>

<h2 id="link-collection">Link Collection</h2>

<ul>
  <li><a href="https://github.com/fipro78/vscode_theia_cookbook">Tutorial Sources</a></li>
  <li>Visual Studio Code Resources
    <ul>
      <li><a href="https://code.visualstudio.com/api/extension-guides/webview">Webview API</a></li>
      <li><a href="https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample">Webview API Sample</a></li>
      <li><a href="https://code.visualstudio.com/api/extension-guides/custom-editors">Custom Editor API</a></li>
      <li><a href="https://github.com/microsoft/vscode-extension-samples/tree/main/custom-editor-sample">Custom Editor API Samples</a></li>
      <li><a href="https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/frameworks">vscode-webview-ui-toolkit-samples (deprecated)</a></li>
    </ul>
  </li>
  <li><a href="https://angular.dev/overview">angular.dev - Docs</a></li>
  <li><a href="https://react.dev/learn">react.dev - Learn React</a></li>
  <li><a href="https://vite.dev/guide/">Vite</a></li>
  <li><a href="https://vscode-elements.github.io/">VSCode Elements</a></li>
  <li><a href="https://vscode-elements.github.io/elements-lite/">VSCode Elements Lite</a></li>
</ul>]]></content><author><name>Dirk Fauth</name></author><category term="fipro" /><category term="vscode" /><category term="typescript" /><category term="angular" /><category term="react" /><summary type="html"><![CDATA[Over the past years I wrote several blog posts about Java, Eclipse and OSGi. While I still like those technologies very much, I can’t ignore that Visual Studio Code, Eclipse Theia and several web technologies became more important. The following blog posts will therefore cover these topics, to help developers like me, that need to switch and provide tools based on Visual Studio Code and/or Eclipse Theia.]]></summary></entry><entry><title type="html">OSGi Component Testing (2024 Edition)</title><link href="https://vogella.com/blog/osgi-component-testing-2024/" rel="alternate" type="text/html" title="OSGi Component Testing (2024 Edition)" /><published>2024-12-09T00:00:00+00:00</published><updated>2024-12-09T00:00:00+00:00</updated><id>https://vogella.com/blog/osgi-component-testing-2024</id><content type="html" xml:base="https://vogella.com/blog/osgi-component-testing-2024/"><![CDATA[<p>In my last blog post I talked about <a href="https://vogella.com/blog/getting-started-with-osgi-declarative-services-2024/">Getting Started with OSGi Declarative Services</a>. In this blog post I want to show how to test OSGi service components. This is an update to my <a href="https://vogella.com/blog/osgi-component-testing/">2016 version of this blog post about OSGi Component testing</a>.</p>

<h2 id="unit-testing--white-box-testing">Unit testing / “white-box testing”</h2>

<p>The first approach for testing components is to write unit tests. In a plain Java project such tests are added in an additional source folder (typically named <em>test</em>). They can then be executed from the IDE and at build time, but left out in the resulting JAR. With Bndtools the same approach is used for unit or <em>white-box-testing</em>.</p>

<p>In Eclipse RCP development you typically create a test fragment for the bundle that should be tested. This way the tests can be executed automatically via <a href="https://tycho.eclipseprojects.io/doc/latest/tycho-surefire-plugin/plugin-info.html">Tycho Surefire Plugin</a>, when running the build process via Maven.</p>

<p><strong>Note:</strong><br />
In <a href="https://vogella.com/blog/osgi-bundles-fragments-dependencies/">one of my previous blog posts</a> I wrote about the wrong usage of fragments in various projects. Also about when fragments should be used and when they shouldn’t. As I got feedback that I did not mention testing with Tycho, I want to add that with this post. Using a fragment for unit testing is also a valid approach. Compared with the typical Java approach to have the test code located in the same project in a separate source folder, using a fragment is similar and gives the same opportunities. The classes are in the same classpath and therefore share the same visibility (e.g. access to package private or protected methods).</p>

<p>Execute the following steps to create a test fragment for unit testing the <code class="language-plaintext highlighter-rouge">StringInverterImpl</code> of the <a href="https://vogella.com/blog/getting-started-with-osgi-declarative-services-2024/">Getting Started Tutorial</a>:</p>

<ul>
  <li>Create a new fragment project
    <ul>
      <li><em>File -&gt; New -&gt; Other -&gt; Plug-in Development -&gt; Fragment Project</em>
        <ul>
          <li>Set the name to <em>org.fipro.inverter.provider.tests</em><br />
It is important that the name ends with <strong><em>.tests</em></strong> so we can later use pom-less Tycho for building.</li>
          <li>Click <em>Next</em></li>
          <li>Set the host plug-in to <em>org.fipro.inverter.provider</em></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Open the <em>MANIFEST.MF</em> file and switch to the <em>Dependencies</em> section
    <ul>
      <li>Add <em>org.junit.jupiter.api</em> to the <em>Imported Packages</em></li>
    </ul>
  </li>
  <li>Create a new package <em>org.fipro.inverter.provider</em></li>
  <li>Create a new JUnit 5 based test class</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">org.fipro.inverter.provider</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">static</span> <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.fipro.inverter.StringInverter</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">StringInverterImplTest</span> <span class="o">{</span>

    <span class="nd">@Test</span> 
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">shouldInvertText</span><span class="o">()</span> <span class="o">{</span> 
        <span class="nc">StringInverter</span> <span class="n">inverter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringInverterImpl</span><span class="o">();</span> 
        <span class="n">assertEquals</span><span class="o">(</span><span class="s">"nospmiS"</span><span class="o">,</span> <span class="n">inverter</span><span class="o">.</span><span class="na">invert</span><span class="o">(</span><span class="s">"Simpson"</span><span class="o">));</span> 
    <span class="o">}</span> 
<span class="o">}</span>
</code></pre></div></div>

<p>The test can be executed via <em>right click -&gt; Run As -&gt; JUnit Test</em></p>

<p><em><strong>Note:</strong></em><br />
It is currently not possible to use the <em>Automatic Manifest Generation</em> PDE project layout for a fragment project.</p>

<h3 id="bndtools-vs-pde">Bndtools vs. PDE</h3>

<p>To add a unit test to a <em>Bnd OSGi Project</em>, you don’t have to create a test fragment. The project layout is similar to a Maven project. Test classes can therefore be placed in the source folder <em>src/test/java</em>.</p>

<p><em><strong>Note:</strong></em><br />
If you have trouble to configure <em>src/test/java</em> as a source folder for test classes, try to open the <em>.classpath</em> file of the project and add the following entry manually:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;classpathentry kind="src" output="generated/test-classes" path="src/test/java"&gt;
    &lt;attributes&gt;
        &lt;attribute name="test" value="true"/&gt;
    &lt;/attributes&gt;
&lt;/classpathentry&gt;
</code></pre></div></div>

<p>To use JUnit 5 for writing the test cases, you will need to prepare the workspace and add the necessary dependencies.</p>

<ul>
  <li>Open the file <em>cnf/ext/build.mvn</em>
    <ul>
      <li>Add the following dependencies
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>org.opentest4j:opentest4j:1.3.0
org.apiguardian:apiguardian-api:1.1.2
org.junit.platform:junit-platform-commons:1.11.2
org.junit.platform:junit-platform-engine:1.11.2
org.junit.platform:junit-platform-launcher:1.11.2
org.junit.jupiter:junit-jupiter-api:5.11.2
org.junit.jupiter:junit-jupiter-engine:5.11.2
org.junit.jupiter:junit-jupiter-params:5.11.2
</code></pre></div>        </div>
      </li>
      <li>In the <em>Repositories</em> view click <em>Refresh Repositories Tree</em></li>
    </ul>
  </li>
  <li>Open the file <em>org.fipro.inverter.provider/bnd.bnd</em>
    <ul>
      <li>Add the <code class="language-plaintext highlighter-rouge">-testpath</code> with the necessary dependencies
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-testpath: \
    org.opentest4j,\
    org.apiguardian.api,\
    junit-jupiter-api,\
    junit-platform-commons,\
    junit-platform-engine,\
    junit-jupiter-engine
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Add the class <code class="language-plaintext highlighter-rouge">org.fipro.inverter.provider.StringInverterImplTest</code> to <em>src/test/java</em></li>
</ul>

<p>Now the unit test can be executed via <em>right click -&gt; Run As -&gt; JUnit Test</em></p>

<p><em><strong>Note:</strong></em><br />
In a Maven project layout, adding a unit test is as simple as with any other Maven project. You need to add <code class="language-plaintext highlighter-rouge">junit-jupiter</code> as <code class="language-plaintext highlighter-rouge">test</code> dependency to the project and then add the test class to <em>src/test/java</em>.</p>

<h2 id="integration-testing--black-box-testing">Integration testing / “black-box-testing”</h2>

<p>Integration tests or <em>black-box-tests</em> are used to test if our bundle and the provided services behave correctly in an OSGi environment. This is especially necessary if the services to test reference other services or OSGi features are used, like the <code class="language-plaintext highlighter-rouge">EventAdmin</code> for event processing or the <code class="language-plaintext highlighter-rouge">ConfigurationAdmin</code> to configure components at runtime. Integration tests are contained in a test bundle, so also the bundle wiring is tested accordingly.</p>

<p>As the test is executed in the JUnit test runtime, we can not simply make use of the service binding mechanisms and injections. In 2016 it was necessary to find and access the service in a programmatical way by using a <code class="language-plaintext highlighter-rouge">ServiceTracker</code>. Nowadays you can use the <a href="https://github.com/osgi/osgi-test">OSGi Testing Support</a> framework, which makes the creation of tests for OSGi components much easier.</p>

<p>We will use <a href="https://github.com/osgi/osgi-test/blob/main/org.osgi.test.junit5/README.md">org.osgi.test.junit5</a> that uses the <a href="https://junit.org/junit5/docs/snapshot/user-guide/#extensions">JUnit 5 Extension Model</a>. This enables the use of OSGi API in test classes similar to the usage in production code.</p>

<p>The OSGi Testing Support bundles are not available via a p2 update site. Currently they are also not included in the Eclipse Orbit Update Site. However it is possible to consume them via Maven Locations in a <em>Target Platform</em>. Therefore the first step is to create a <em>Target Platform</em> that contains the <em>Eclipse Projekt SDK</em> for the Equinox OSGi bundles and the JUnit Support and the Maven Location for the <code class="language-plaintext highlighter-rouge">osgi-test</code> bundles.</p>

<ul>
  <li>Create the target platform project
    <ul>
      <li><em>Main Menu → File → New → Project → General → Project</em></li>
      <li>Set name to <em>org.fipro.osgi.target</em></li>
      <li>Click <em>Finish</em></li>
    </ul>
  </li>
  <li>Create a new target definition
    <ul>
      <li><em>Right click on project → New → Target Definition</em></li>
      <li>Set the filename to <em>org.fipro.osgi.target.target</em></li>
      <li>Initialize the target definition with: <em>Nothing: Start with an empty target definition</em></li>
      <li>Click <em>Finish</em></li>
    </ul>
  </li>
  <li>Add a new Software Site in the opened <em>Target Definition Editor</em>
    <ul>
      <li>Alternative A
        <ul>
          <li>Switch to the <em>Source</em> tab and add the following snippet to the editor</li>
        </ul>
      </li>
    </ul>

    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="cp">&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;</span>
  <span class="cp">&lt;?pde version="3.8"?&gt;</span>
  <span class="nt">&lt;target</span> <span class="na">name=</span><span class="s">"org.fipro.osgi.target"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;locations&gt;</span>
          <span class="nt">&lt;location</span> 
              <span class="na">includeAllPlatforms=</span><span class="s">"false"</span> 
              <span class="na">includeConfigurePhase=</span><span class="s">"false"</span> 
              <span class="na">includeMode=</span><span class="s">"planner"</span> 
              <span class="na">includeSource=</span><span class="s">"true"</span> 
              <span class="na">type=</span><span class="s">"InstallableUnit"</span><span class="nt">&gt;</span>

              <span class="nt">&lt;unit</span> <span class="na">id=</span><span class="s">"org.eclipse.sdk.feature.group"</span> <span class="na">version=</span><span class="s">"0.0.0"</span><span class="nt">/&gt;</span>

              <span class="nt">&lt;repository</span> <span class="na">location=</span><span class="s">"https://download.eclipse.org/releases/2024-09"</span><span class="nt">/&gt;</span>
          <span class="nt">&lt;/location&gt;</span>
          <span class="nt">&lt;location</span> 
              <span class="na">includeDependencyDepth=</span><span class="s">"infinite"</span> 
              <span class="na">includeDependencyScopes=</span><span class="s">"compile"</span> 
              <span class="na">includeSource=</span><span class="s">"true"</span> 
              <span class="na">label=</span><span class="s">"org.osgi.test"</span> 
              <span class="na">missingManifest=</span><span class="s">"generate"</span> 
              <span class="na">type=</span><span class="s">"Maven"</span><span class="nt">&gt;</span>
              <span class="nt">&lt;dependencies&gt;</span>
                  <span class="nt">&lt;dependency&gt;</span>
                      <span class="nt">&lt;groupId&gt;</span>org.osgi<span class="nt">&lt;/groupId&gt;</span>
                      <span class="nt">&lt;artifactId&gt;</span>org.osgi.test.junit5<span class="nt">&lt;/artifactId&gt;</span>
                      <span class="nt">&lt;version&gt;</span>1.3.0<span class="nt">&lt;/version&gt;</span>
                      <span class="nt">&lt;type&gt;</span>jar<span class="nt">&lt;/type&gt;</span>
                  <span class="nt">&lt;/dependency&gt;</span>
              <span class="nt">&lt;/dependencies&gt;</span>
          <span class="nt">&lt;/location&gt;</span>
      <span class="nt">&lt;/locations&gt;</span>
  <span class="nt">&lt;/target&gt;</span>
</code></pre></div>    </div>

    <ul>
      <li>Alternative B
        <ul>
          <li>By clicking <em>Add…</em> in the <em>Locations</em> section
            <ul>
              <li>Select <em>Software Site</em></li>
              <li>Software Site <em>https://download.eclipse.org/releases/2024-09</em></li>
              <li>Disable <em>Group by Category</em> and filter for <em>Eclipse</em></li>
              <li>Select <em>Eclipse Project SDK</em></li>
              <li>Click <em>Finish</em></li>
            </ul>
          </li>
          <li>Click <em>Add…</em> in the <em>Locations</em> section
            <ul>
              <li>Select <em>Maven</em></li>
              <li>Add the GAV to org.osgi.test.junit5
                <ul>
                  <li><em>Group Id: <strong>org.osgi</strong></em></li>
                  <li><em>Artifact Id: <strong>org.osgi.test.junit5</strong></em></li>
                  <li><em>Version: <strong>1.3.0</strong></em></li>
                  <li><em>Type: <strong>jar</strong></em></li>
                </ul>
              </li>
              <li>Set a <em>Label</em>: <em><strong>org.osgi.test</strong></em></li>
              <li><em>Dependencies depth</em>: <em><strong>Infinite</strong></em></li>
              <li>Click <em>Finish</em></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Switch to the <em>Definition</em> tab
    <ul>
      <li>Wait until the Target Definition is completely resolved (check the progress at the bottom right)</li>
      <li>Activate the target platform by clicking <em>Set as Target Platform</em> in the upper right corner of the Target Definition Editor</li>
    </ul>
  </li>
</ul>

<p>Execute the following steps to create a test bundle / plug-in for integration testing of the <em>org.fipro.inverter.provider</em> bundle from the <a href="https://vogella.com/blog/getting-started-with-osgi-declarative-services-2024/">Getting Started Tutorial</a>:</p>

<ul>
  <li>Create a new plug-in project
    <ul>
      <li><em>File -&gt; New -&gt; Other -&gt; Plug-in Development -&gt; Plug-in Project</em>
        <ul>
          <li>Set the name to <em>org.fipro.inverter.integration.tests</em><br />
It is important that the name ends with <strong><em>.tests</em></strong> so we can later use pom-less Tycho for building.</li>
          <li>Click <em>Next</em></li>
          <li>Set <em>Name</em> to <em>Inverter Integration Tests</em></li>
          <li>Select <em>Execution Environment JavaSE-17</em></li>
          <li>Ensure that <em>Generate an Activator</em> and <em>This plug-in will make contributions to the UI</em> are disabled</li>
          <li>Click <em>Finish</em></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Open the <em>MANIFEST.MF</em> file and switch to the <em>Dependencies</em> section
    <ul>
      <li>Add the following entries to the <em>Imported Packages</em>
        <ul>
          <li><em>org.fipro.inverter</em></li>
          <li><em>org.junit.jupiter.api</em></li>
          <li><em>org.junit.jupiter.api.extension</em></li>
          <li><em>org.osgi.test.common.annotation</em></li>
          <li><em>org.osgi.test.junit5.service</em></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Create a new package <em>org.fipro.inverter.integration.tests</em></li>
</ul>

<p><em><strong>Note:</strong></em><br />
The <em>Automatic Manifest Generation</em> project layout can not be used for integration test bundles. The bundle is not recognized correctly and the option <em>Run As -&gt; JUnit Plug-in Test</em> is not available. I tried to manually create the <em>JUnit Plug-in Test</em> run configuration. But at some point even the <em>Run As -&gt; JUnit Test</em> option vanishes and there are no tests found in that class anymore.</p>

<p><em><strong>Note:</strong></em><br />
We could also add <em>org.fipro.inverter.provider</em> to the <em>Require-Bundle</em> section, to make the integration test explicitly dependent on that provider bundle. And surely there are cases where this makes sense. In that case I would suggest to name the test bundle <em>org.fipro.inverter.<strong>provider</strong>.integration.test</em> to make that clear. But the explained approach in this tutorial simulates a real usage example of the service in other bundles, so IMHO that is a real integration test.</p>

<ul>
  <li>Create a new JUnit 5 based test class that uses <code class="language-plaintext highlighter-rouge">osgi-test</code></li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">org.fipro.inverter.integration.tests</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">static</span> <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span><span class="o">.</span><span class="na">assertNotNull</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.fipro.inverter.StringInverter</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.extension.ExtendWith</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.osgi.test.common.annotation.InjectService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.osgi.test.junit5.service.ServiceExtension</span><span class="o">;</span>

<span class="nd">@ExtendWith</span><span class="o">(</span><span class="nc">ServiceExtension</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">IntegrationTest</span> <span class="o">{</span>

    <span class="nd">@Test</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">shouldInvertWithService</span><span class="o">(</span><span class="nd">@InjectService</span> <span class="nc">StringInverter</span> <span class="n">inverter</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">assertNotNull</span><span class="o">(</span><span class="n">inverter</span><span class="o">,</span> <span class="s">"No StringInverter service found"</span><span class="o">);</span>
        <span class="n">assertEquals</span><span class="o">(</span><span class="s">"nospmiS"</span><span class="o">,</span> <span class="n">inverter</span><span class="o">.</span><span class="na">invert</span><span class="o">(</span><span class="s">"Simpson"</span><span class="o">));</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li>Open the <em>MANIFEST.MF</em> file and switch to the <em>Overview</em> section
    <ul>
      <li>Check <em>Activate this plug-in when one of its classes is loaded</em> which generates the <code class="language-plaintext highlighter-rouge">Bundle-ActivationPolicy: lazy</code> header in the <em>MANIFEST.MF</em> file. This is necessary so the test bundle is started.</li>
    </ul>
  </li>
</ul>

<p>The test can be executed via <em>right click -&gt; Run As -&gt; JUnit Plug-in Test</em></p>

<p>If you are interested in learning more about <code class="language-plaintext highlighter-rouge">osgi-test</code>, watch the recording of the following talk be Stefan Bischof: <a href="https://youtu.be/3YvvXGoOZAs?si=UHj9lka5MCQIEZmO">Testing OSGi with OSGi-Test - OCX 2024</a></p>

<h3 id="dealing-with-implicit-dependencies">Dealing with implicit dependencies</h3>

<p>When executing the integration tests in the IDE via PDE tooling, a launch configuration will be created, that automatically adds all bundles from the workspace and the target platform to the test runtime. This way all necessary bundles are available, even the implicit dependencies.</p>

<p>When I wrote the initial version of this blog post in 2016, executing the tests with Tycho Surefire hat some issues in that area. It was necessary to make implicit dependencies explicit. And it was necessary to configure dependencies to the <em>Service Component Runtime (SCR)</em> and the service provider bundle.</p>

<p>When executing a test bundle/fragment via Tycho Surefire, the OSGi runtime for the test execution consists of the test bundle/fragment and its dependencies. There is no explicit launch configuration. Because of that, the implicit dependencies need to be specified in another way to add them to the test runtime. In general you need to make the implicit dependencies explicit. This can be done in different ways. The most obvious is to add a bundle requirement to the test bundle dependencies. But as explained above, this is more a workaround than a solution. The suggested way in various wiki entries and blog posts is to configure the additional dependencies for the test runtime in the <em>pom.xml</em>. More information on that can be found in the following documentations and blogs:</p>

<ul>
  <li><a href="https://wiki.eclipse.org/Tycho/Packaging_Types#eclipse-test-plugin">Tycho Packaging Types - eclipse-test-plugin</a></li>
  <li><a href="https://wiki.eclipse.org/Tycho/FAQ#How_to_test_OSGi_declarative_services.3F">Tycho FAQ - How to test OSGi declarative services?</a></li>
  <li><a href="https://wiki.eclipse.org/Tycho/Testing_with_Surefire">Testing with Surefire</a></li>
  <li><a href="https://tycho.eclipseprojects.io/doc/latest/tycho-surefire-plugin/plugin-info.html">Tycho Surefire Plugin</a></li>
</ul>

<p>With the rise of pom-less Tycho builds the usage of explicit <em>pom.xml</em> files for test bundles and test fragments is not needed and wanted anymore. It is of course still possible to specify an explicit <em>pom.xml</em> to add special configurations. But if it is not necessary, it should be avoided to let the pom-less extension derive the necessary build information.</p>

<p>In the given example the integration test bundle has two implicit dependencies:</p>

<ul>
  <li>The <em>Service Component Runtime</em> that is needed to manage components and their life cycle.</li>
  <li>The service provider bundle that contains the service implementation to test (e.g. <code class="language-plaintext highlighter-rouge">org.fipro.inverter.provider</code>). As we only specified the package dependency on the service interface, there is no direct dependency on the provider.</li>
</ul>

<p>Such dependencies can be specified via OSGi capabilities. For the <em>Service Component Runtime</em> you need to specify the <em>osgi.extender</em> capability for <em>osgi.component</em>. For the service provider you need to specify the <em>osgi.service</em> capability for the <code class="language-plaintext highlighter-rouge">StringInverter</code> service interface. The corresponding <em>Require-Capability</em> header that needs to be added to the <em>MANIFEST.MF</em> file looks like the following snippet:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Require-Capability: osgi.extender;
  filter:="(&amp;(osgi.extender=osgi.component)(version&gt;=1.3)(!(version&gt;=2.0)))",
 osgi.service;
  filter:="(objectClass=org.fipro.inverter.StringInverter)"
</code></pre></div></div>

<p>Actually the <code class="language-plaintext highlighter-rouge">org.fipro.inverter.provider</code> bundle already requires the <em>osgi.extender</em> capability. Therefore it is already present in the test runtime. But our integration test bundle needs to require the <em>osgi.service</em> capability in order to make the integration tests work.</p>

<ul>
  <li>Open the <em>MANIFEST.MF</em> file of the <em>org.fipro.inverter.integration.test</em> project</li>
  <li>Switch to the <em>MANIFEST.MF</em> tab</li>
  <li>Add the following header to the file (remember that there needs to be an empty new line at the end of the file)
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Require-Capability: osgi.service;
filter:="(objectClass=org.fipro.inverter.StringInverter)"
</code></pre></div>    </div>
  </li>
</ul>

<p><em><strong>Note:</strong></em><br />
As the <em>MANIFEST.MF</em> is not generated for the integration test bundle, the usage of a <em>package-info.java</em> does not work here. For Bndtools the <em>package-info.java</em> is the recommended way to configure the requirements, which is explained in the Bndtools section later.</p>

<p>At last we setup a pom-less Tycho build to proof that everything is working as expected.</p>

<ul>
  <li>Create a <em>.mvn/extensions.xml</em> descriptor file in the root of the project directory</li>
</ul>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;extensions&gt;</span>
  <span class="nt">&lt;extension&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.eclipse.tycho<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>tycho-build<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>4.0.10<span class="nt">&lt;/version&gt;</span>
  <span class="nt">&lt;/extension&gt;</span>
<span class="nt">&lt;/extensions&gt;</span>
</code></pre></div></div>

<ul>
  <li>Create a parent <em>pom.xml</em> file in the root of the project directory to configure the build</li>
</ul>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;project</span>
    <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>
    <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
    <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span><span class="nt">&gt;</span>

    <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.fipro<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>org.fipro.parent<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;packaging&gt;</span>pom<span class="nt">&lt;/packaging&gt;</span>

    <span class="nt">&lt;properties&gt;</span>
        <span class="nt">&lt;tycho.version&gt;</span>4.0.10<span class="nt">&lt;/tycho.version&gt;</span>
        <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span>
        <span class="nt">&lt;java.version&gt;</span>17<span class="nt">&lt;/java.version&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>

    <span class="nt">&lt;build&gt;</span>
        <span class="nt">&lt;plugins&gt;</span>
            <span class="nt">&lt;plugin&gt;</span>
                <span class="nt">&lt;groupId&gt;</span>org.eclipse.tycho<span class="nt">&lt;/groupId&gt;</span>
                <span class="nt">&lt;artifactId&gt;</span>tycho-maven-plugin<span class="nt">&lt;/artifactId&gt;</span>
                <span class="nt">&lt;version&gt;</span>${tycho.version}<span class="nt">&lt;/version&gt;</span>
                <span class="nt">&lt;extensions&gt;</span>true<span class="nt">&lt;/extensions&gt;</span>
            <span class="nt">&lt;/plugin&gt;</span>

            <span class="nt">&lt;plugin&gt;</span>
                <span class="nt">&lt;groupId&gt;</span>org.eclipse.tycho<span class="nt">&lt;/groupId&gt;</span>
                <span class="nt">&lt;artifactId&gt;</span>tycho-packaging-plugin<span class="nt">&lt;/artifactId&gt;</span>
                <span class="nt">&lt;version&gt;</span>${tycho.version}<span class="nt">&lt;/version&gt;</span>
                <span class="nt">&lt;configuration&gt;</span>
                    <span class="nt">&lt;archive&gt;</span>
                        <span class="nt">&lt;addMavenDescriptor&gt;</span>false<span class="nt">&lt;/addMavenDescriptor&gt;</span>
                    <span class="nt">&lt;/archive&gt;</span>
                <span class="nt">&lt;/configuration&gt;</span>
            <span class="nt">&lt;/plugin&gt;</span>

            <span class="nt">&lt;plugin&gt;</span>
                <span class="nt">&lt;groupId&gt;</span>org.eclipse.tycho<span class="nt">&lt;/groupId&gt;</span>
                <span class="nt">&lt;artifactId&gt;</span>target-platform-configuration<span class="nt">&lt;/artifactId&gt;</span>
                <span class="nt">&lt;version&gt;</span>${tycho.version}<span class="nt">&lt;/version&gt;</span>
                <span class="nt">&lt;configuration&gt;</span>
                    <span class="nt">&lt;target&gt;</span>
                        <span class="nt">&lt;artifact&gt;</span>
                            <span class="nt">&lt;groupId&gt;</span>org.fipro<span class="nt">&lt;/groupId&gt;</span>
                            <span class="nt">&lt;artifactId&gt;</span>org.fipro.osgi.target<span class="nt">&lt;/artifactId&gt;</span>
                            <span class="nt">&lt;version&gt;</span>${project.version}<span class="nt">&lt;/version&gt;</span>
                        <span class="nt">&lt;/artifact&gt;</span>
                    <span class="nt">&lt;/target&gt;</span>
                    <span class="nt">&lt;targetDefinitionIncludeSource&gt;</span>honor<span class="nt">&lt;/targetDefinitionIncludeSource&gt;</span>
                    <span class="nt">&lt;executionEnvironment&gt;</span>JavaSE-17<span class="nt">&lt;/executionEnvironment&gt;</span>
                    <span class="nt">&lt;environments&gt;</span>
                        <span class="nt">&lt;environment&gt;</span>
                            <span class="nt">&lt;os&gt;</span>win32<span class="nt">&lt;/os&gt;</span>
                            <span class="nt">&lt;ws&gt;</span>win32<span class="nt">&lt;/ws&gt;</span>
                            <span class="nt">&lt;arch&gt;</span>x86_64<span class="nt">&lt;/arch&gt;</span>
                        <span class="nt">&lt;/environment&gt;</span>
                        <span class="nt">&lt;environment&gt;</span>
                            <span class="nt">&lt;os&gt;</span>linux<span class="nt">&lt;/os&gt;</span>
                            <span class="nt">&lt;ws&gt;</span>gtk<span class="nt">&lt;/ws&gt;</span>
                            <span class="nt">&lt;arch&gt;</span>x86_64<span class="nt">&lt;/arch&gt;</span>
                        <span class="nt">&lt;/environment&gt;</span>
                        <span class="nt">&lt;environment&gt;</span>
                            <span class="nt">&lt;os&gt;</span>macosx<span class="nt">&lt;/os&gt;</span>
                            <span class="nt">&lt;ws&gt;</span>cocoa<span class="nt">&lt;/ws&gt;</span>
                            <span class="nt">&lt;arch&gt;</span>x86_64<span class="nt">&lt;/arch&gt;</span>
                        <span class="nt">&lt;/environment&gt;</span>
                    <span class="nt">&lt;/environments&gt;</span>
                <span class="nt">&lt;/configuration&gt;</span>
            <span class="nt">&lt;/plugin&gt;</span>
        <span class="nt">&lt;/plugins&gt;</span>
        <span class="nt">&lt;pluginManagement&gt;</span>
            <span class="nt">&lt;plugins&gt;</span>
                <span class="c">&lt;!-- 
                    Add this to avoid the warning: 
                    'build.plugins.plugin.version' for org.eclipse.tycho:tycho-p2-director-plugin
                is missing. 
                --&gt;</span>
                <span class="nt">&lt;plugin&gt;</span>
                    <span class="nt">&lt;groupId&gt;</span>org.eclipse.tycho<span class="nt">&lt;/groupId&gt;</span>
                    <span class="nt">&lt;artifactId&gt;</span>tycho-p2-director-plugin<span class="nt">&lt;/artifactId&gt;</span>
                    <span class="nt">&lt;version&gt;</span>${tycho.version}<span class="nt">&lt;/version&gt;</span>
                <span class="nt">&lt;/plugin&gt;</span>
            <span class="nt">&lt;/plugins&gt;</span>
        <span class="nt">&lt;/pluginManagement&gt;</span>
    <span class="nt">&lt;/build&gt;</span>

    <span class="nt">&lt;modules&gt;</span>
        <span class="nt">&lt;module&gt;</span>org.fipro.osgi.target<span class="nt">&lt;/module&gt;</span>
        <span class="nt">&lt;module&gt;</span>org.fipro.inverter.api<span class="nt">&lt;/module&gt;</span>
        <span class="nt">&lt;module&gt;</span>org.fipro.inverter.command<span class="nt">&lt;/module&gt;</span>
        <span class="nt">&lt;module&gt;</span>org.fipro.inverter.provider<span class="nt">&lt;/module&gt;</span>
        <span class="nt">&lt;module&gt;</span>org.fipro.inverter.provider.tests<span class="nt">&lt;/module&gt;</span>
        <span class="nt">&lt;module&gt;</span>org.fipro.inverter.integration.tests<span class="nt">&lt;/module&gt;</span>
    <span class="nt">&lt;/modules&gt;</span>
<span class="nt">&lt;/project&gt;</span> 
</code></pre></div></div>

<p>For further information on setting up a build with Tycho, have a look at the <a href="http://www.vogella.com/tutorials/EclipseTycho/article.html">vogella Tycho Tutorial</a> or the <a href="https://github.com/fipro78/e4-cookbook-basic-recipe/blob/master/tutorials/Eclipse_RCP_Cookbook_Tycho.md">Eclipse RCP Cookbook – The Thermomix Recipe (Automated build with Maven Tycho)</a>.</p>

<p>After the two files are in place and configured correctly, the build can be startet via</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn clean verify
</code></pre></div></div>

<p>If everything is setup correctly, the build should run the unit test fragment and the integration test bundle, and the build should succeed.</p>

<h3 id="bndtools-vs-pde-1">Bndtools vs. PDE</h3>

<p>Bndtools has a similar but slightly different approach than PDE. You need to configure the test runtime in a <em>.bndrun</em> file and do some additional configuration related to the test execution. Some background details can be found in <a href="https://bnd.bndtools.org/chapters/310-testing.html">bnd -Testing</a>.</p>

<ul>
  <li>Open the file <em>cnf/ext/build.mvn</em>
    <ul>
      <li>
        <p>Add the following dependencies</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>org.osgi:org.osgi.test.common:1.3.0
org.osgi:org.osgi.test.junit5:1.3.0
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Create a new <em>Bnd OSGi Project</em></li>
  <li>Configure <em>src/test/java</em> as a source folder for test classes</li>
  <li>
    <p>Edit the <em>bnd.bnd</em> file and configure the dependencies</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Test-Cases: ${classes;HIERARCHY_INDIRECTLY_ANNOTATED;org.junit.platform.commons.annotation.Testable;CONCRETE}

-dependson: \
  org.fipro.inverter.provider

-buildpath:  \
  osgi.annotation,\
  org.osgi.namespace.service,\
  org.osgi.service.component.annotations,\
  org.fipro.inverter.api;version=latest,\
  org.opentest4j,\
  org.apiguardian.api,\
  junit-jupiter-api,\
  junit-jupiter-engine,\
  junit-jupiter-params,\
  junit-platform-commons,\
  junit-platform-engine,\
  junit-platform-launcher,\
  org.osgi.test.common,\
  org.osgi.test.junit5
</code></pre></div>    </div>
  </li>
  <li>
    <p>Create a test class</p>

    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">org.fipro.inverter.integration.tests</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">static</span> <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span><span class="o">.</span><span class="na">assertNotNull</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.fipro.inverter.StringInverter</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.extension.ExtendWith</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.osgi.test.common.annotation.InjectService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.osgi.test.junit5.service.ServiceExtension</span><span class="o">;</span>

<span class="nd">@ExtendWith</span><span class="o">(</span><span class="nc">ServiceExtension</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">IntegrationTest</span> <span class="o">{</span>

  <span class="nd">@Test</span>
  <span class="kd">public</span> <span class="kt">void</span> <span class="nf">shouldInvertWithService</span><span class="o">(</span><span class="nd">@InjectService</span> <span class="nc">StringInverter</span> <span class="n">inverter</span><span class="o">)</span> <span class="o">{</span>
      <span class="n">assertNotNull</span><span class="o">(</span><span class="n">inverter</span><span class="o">,</span> <span class="s">"No StringInverter service found"</span><span class="o">);</span>
      <span class="n">assertEquals</span><span class="o">(</span><span class="s">"nospmiS"</span><span class="o">,</span> <span class="n">inverter</span><span class="o">.</span><span class="na">invert</span><span class="o">(</span><span class="s">"Simpson"</span><span class="o">));</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Create a <em>package-info.java</em> in the package where the test class resides<br />
This way we configure the requirement on a service implementation of type <code class="language-plaintext highlighter-rouge">org.fipro.inverter.StringInverter</code>, which is necessary to tell the resolver that the service provider implementation is needded for the test runtime.</p>

    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// We require a StringInverter service to test</span>
<span class="nd">@Requirement</span><span class="o">(</span>
    <span class="n">namespace</span> <span class="o">=</span> <span class="nc">ServiceNamespace</span><span class="o">.</span><span class="na">SERVICE_NAMESPACE</span><span class="o">,</span> 
    <span class="n">filter</span> <span class="o">=</span> <span class="s">"("</span> <span class="o">+</span> <span class="nc">ServiceNamespace</span><span class="o">.</span><span class="na">CAPABILITY_OBJECTCLASS_ATTRIBUTE</span> <span class="o">+</span> <span class="s">"=org.fipro.inverter.StringInverter)"</span><span class="o">)</span>
<span class="kn">package</span> <span class="nn">org.fipro.inverter.integration.tests</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.osgi.annotation.bundle.Requirement</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.osgi.namespace.service.ServiceNamespace</span><span class="o">;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Create a <em>test.bndrun</em> test launch configuration<br />
Via the <code class="language-plaintext highlighter-rouge">-tester</code> header we configure the tester bundle for JUnit 5.<br />
Via <code class="language-plaintext highlighter-rouge">-runrequires</code> we specify the requirement on this test bundle and its dependencies.</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-tester: biz.aQute.tester.junit-platform 

-runfw: org.apache.felix.framework;version='[7.0.5,7.0.5]'
-runee: JavaSE-17
-resolve.effective: active

-runrequires: \
    bnd.identity;id='${basename;${.}}'
</code></pre></div>    </div>
  </li>
</ul>

<p>To run the integration test from within the IDE, you first need to manually resolve the <code class="language-plaintext highlighter-rouge">-runbundles</code> by clicking on <em>Resolve</em> in the <em>test.bndrun</em> editor. If you now right click on the <code class="language-plaintext highlighter-rouge">IntegrationTest</code> class and select <em>Run As -&gt; Bnd OSGi Test Launcher (JUnit)</em> you will see an exception like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>org.osgi.framework.BundleException: Unable to resolve biz.aQute.tester [2](R 2.0): missing requirement [biz.aQute.tester [2](R 2.0)] osgi.wiring.package; (&amp;(osgi.wiring.package=junit.framework)(version&gt;=3.8.0)(!(version&gt;=5.0.0))) Unresolved requirements: [[biz.aQute.tester [2](R 2.0)] osgi.wiring.package; (&amp;(osgi.wiring.package=junit.framework)(version&gt;=3.8.0)(!(version&gt;=5.0.0)))]
    at org.apache.felix.framework.Felix.resolveBundleRevision(Felix.java:4398)
</code></pre></div></div>

<p>This can be solved by modifing the <em>Run Configuration</em></p>
<ul>
  <li>Open <em>Run -&gt; Run Configurations…</em></li>
  <li>In the tree view select <em>OSGi Framework JUnit Tests -&gt; org.fipro.inverter.integration.tests</em></li>
  <li>On the tab <em>OSGi Tests</em> click <em>Browse Run Files</em></li>
  <li>Select the <em>test.bndrun</em> in the <em>org.fipro.inverter.integration.tests</em> project</li>
  <li>Click <em>Run</em></li>
</ul>

<p>Now it should be possible to launch the integration test within the IDE.</p>

<p>If the integration test should be executed in the Gradle build that comes with the Bndtools project wizards, you also need to create/modify the <em>build.gradle</em> file:</p>
<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">def</span> <span class="n">resolveTask</span> <span class="o">=</span> <span class="n">tasks</span><span class="o">.</span><span class="na">named</span><span class="o">(</span><span class="s2">"resolve.test"</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">outputBndrun</span> <span class="o">=</span> <span class="n">layout</span><span class="o">.</span><span class="na">buildDirectory</span><span class="o">.</span><span class="na">file</span><span class="o">(</span><span class="s2">"test.bndrun"</span><span class="o">)</span>
<span class="o">}</span>

<span class="n">tasks</span><span class="o">.</span><span class="na">named</span><span class="o">(</span><span class="s2">"testOSGi"</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">bndrun</span> <span class="o">=</span> <span class="n">resolveTask</span><span class="o">.</span><span class="na">flatMap</span> <span class="o">{</span> <span class="n">it</span><span class="o">.</span><span class="na">outputBndrun</span> <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="bndtools-with-maven">Bndtools with Maven</h4>

<p>With a Bndtools Maven project setup, you can use the <a href="https://github.com/bndtools/bnd/blob/master/maven-plugins/bnd-testing-maven-plugin/README.md"><code class="language-plaintext highlighter-rouge">bnd-testing-maven-plugin</code></a>. It basically creates a test application jar that defines the runtime and the test cases, and executes the test in the build process.</p>

<p>Perform the following steps to enable the execution of OSGi integration tests:</p>

<ul>
  <li>
    <p>Add the necessary dependencies to the parent <em>pom.xml</em> file</p>

    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c">&lt;!-- OSGi Namespace Service --&gt;</span>
  <span class="nt">&lt;dependency&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>org.osgi<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>org.osgi.namespace.service<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>1.0.0<span class="nt">&lt;/version&gt;</span>
      <span class="nt">&lt;scope&gt;</span>provided<span class="nt">&lt;/scope&gt;</span>
  <span class="nt">&lt;/dependency&gt;</span>

  <span class="c">&lt;!-- Test Dependencies --&gt;</span>
  <span class="nt">&lt;dependency&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>org.junit<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>junit-bom<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>5.11.2<span class="nt">&lt;/version&gt;</span>
      <span class="nt">&lt;type&gt;</span>pom<span class="nt">&lt;/type&gt;</span>
      <span class="nt">&lt;scope&gt;</span>import<span class="nt">&lt;/scope&gt;</span>
  <span class="nt">&lt;/dependency&gt;</span>
  <span class="nt">&lt;dependency&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>org.osgi<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>org.osgi.test.bom<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>1.3.0<span class="nt">&lt;/version&gt;</span>
      <span class="nt">&lt;type&gt;</span>pom<span class="nt">&lt;/type&gt;</span>
      <span class="nt">&lt;scope&gt;</span>import<span class="nt">&lt;/scope&gt;</span>
  <span class="nt">&lt;/dependency&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Add additional configurations to the <code class="language-plaintext highlighter-rouge">bnd-maven-plugin</code>, <code class="language-plaintext highlighter-rouge">bnd-resolve-maven-plugin</code> and the <code class="language-plaintext highlighter-rouge">bnd-testing-maven-plugin</code></p>

    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;plugin&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>biz.aQute.bnd<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>bnd-maven-plugin<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>${bnd.version}<span class="nt">&lt;/version&gt;</span>
      <span class="nt">&lt;extensions&gt;</span>true<span class="nt">&lt;/extensions&gt;</span>
      ...
  <span class="nt">&lt;/plugin&gt;</span>
  <span class="nt">&lt;plugin&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>biz.aQute.bnd<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>bnd-resolver-maven-plugin<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>${bnd.version}<span class="nt">&lt;/version&gt;</span>
      <span class="nt">&lt;configuration&gt;</span>
          <span class="nt">&lt;failOnChanges&gt;</span>false<span class="nt">&lt;/failOnChanges&gt;</span>
          <span class="nt">&lt;bndruns&gt;&lt;/bndruns&gt;</span>
      <span class="nt">&lt;/configuration&gt;</span>
      <span class="nt">&lt;executions&gt;</span>
          <span class="c">&lt;!-- Integration Test Configuration --&gt;</span>
          <span class="nt">&lt;execution&gt;</span>
              <span class="nt">&lt;id&gt;</span>resolve-test<span class="nt">&lt;/id&gt;</span>
              <span class="nt">&lt;phase&gt;</span>pre-integration-test<span class="nt">&lt;/phase&gt;</span>
              <span class="nt">&lt;goals&gt;</span>
                  <span class="nt">&lt;goal&gt;</span>resolve<span class="nt">&lt;/goal&gt;</span>
              <span class="nt">&lt;/goals&gt;</span>
              <span class="nt">&lt;configuration&gt;</span>
                  <span class="nt">&lt;outputBndrunDir&gt;</span>${project.build.directory}<span class="nt">&lt;/outputBndrunDir&gt;</span>
                  <span class="nt">&lt;bndruns&gt;</span>
                      <span class="nt">&lt;include&gt;</span>test.bndrun<span class="nt">&lt;/include&gt;</span>
                  <span class="nt">&lt;/bndruns&gt;</span>
                  <span class="nt">&lt;failOnChanges&gt;</span>false<span class="nt">&lt;/failOnChanges&gt;</span>
                  <span class="nt">&lt;includeDependencyManagement&gt;</span>true<span class="nt">&lt;/includeDependencyManagement&gt;</span>
                  <span class="nt">&lt;reportOptional&gt;</span>false<span class="nt">&lt;/reportOptional&gt;</span>
                  <span class="nt">&lt;scopes&gt;</span>
                      <span class="nt">&lt;scope&gt;</span>compile<span class="nt">&lt;/scope&gt;</span>
                      <span class="nt">&lt;scope&gt;</span>runtime<span class="nt">&lt;/scope&gt;</span>
                      <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span>
                  <span class="nt">&lt;/scopes&gt;</span>
              <span class="nt">&lt;/configuration&gt;</span>
          <span class="nt">&lt;/execution&gt;</span>
      <span class="nt">&lt;/executions&gt;</span>
  <span class="nt">&lt;/plugin&gt;</span>
  <span class="nt">&lt;plugin&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>biz.aQute.bnd<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>bnd-testing-maven-plugin<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>${bnd.version}<span class="nt">&lt;/version&gt;</span>
      <span class="nt">&lt;executions&gt;</span>
          <span class="c">&lt;!-- OSGi integration tests execution --&gt;</span>
          <span class="nt">&lt;execution&gt;</span>
              <span class="nt">&lt;goals&gt;</span>
                  <span class="nt">&lt;goal&gt;</span>testing<span class="nt">&lt;/goal&gt;</span>
              <span class="nt">&lt;/goals&gt;</span>
              <span class="nt">&lt;configuration&gt;</span>
                  <span class="nt">&lt;bndrunDir&gt;</span>${project.build.directory}<span class="nt">&lt;/bndrunDir&gt;</span>
                  <span class="nt">&lt;bndruns&gt;</span>
                      <span class="nt">&lt;include&gt;</span>test.bndrun<span class="nt">&lt;/include&gt;</span>
                  <span class="nt">&lt;/bndruns&gt;</span>
                  <span class="nt">&lt;failOnChanges&gt;</span>false<span class="nt">&lt;/failOnChanges&gt;</span>
                  <span class="nt">&lt;includeDependencyManagement&gt;</span>true<span class="nt">&lt;/includeDependencyManagement&gt;</span>
                  <span class="nt">&lt;resolve&gt;</span>false<span class="nt">&lt;/resolve&gt;</span>
                  <span class="nt">&lt;scopes&gt;</span>
                      <span class="nt">&lt;scope&gt;</span>compile<span class="nt">&lt;/scope&gt;</span>
                      <span class="nt">&lt;scope&gt;</span>runtime<span class="nt">&lt;/scope&gt;</span>
                      <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span>
                  <span class="nt">&lt;/scopes&gt;</span>
              <span class="nt">&lt;/configuration&gt;</span>
          <span class="nt">&lt;/execution&gt;</span>
      <span class="nt">&lt;/executions&gt;</span>
  <span class="nt">&lt;/plugin&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>Create a new Maven submodule for the integration test and configure the <em>pom.xml</em> as follows
    <ul>
      <li>Define the dependencies of the test runtime</li>
      <li>Disable the <code class="language-plaintext highlighter-rouge">maven-surefire-plugin</code></li>
      <li>Configure the <code class="language-plaintext highlighter-rouge">bnd-maven-plugin</code> to create a test jar</li>
      <li>Enable the <code class="language-plaintext highlighter-rouge">bnd-resolver-maven-plugin</code> and the <code class="language-plaintext highlighter-rouge">bnd-testing-maven-plugin</code></li>
    </ul>

    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
      <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
      <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>
      <span class="nt">&lt;parent&gt;</span>
          <span class="nt">&lt;groupId&gt;</span>org.fipro.osgi.ds<span class="nt">&lt;/groupId&gt;</span>
          <span class="nt">&lt;artifactId&gt;</span>getting-started<span class="nt">&lt;/artifactId&gt;</span>
          <span class="nt">&lt;version&gt;</span>1.0.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
      <span class="nt">&lt;/parent&gt;</span>

      <span class="nt">&lt;artifactId&gt;</span>org.fipro.inverter.integration.tests<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;name&gt;</span>Inverter Integration Tests<span class="nt">&lt;/name&gt;</span>

      <span class="nt">&lt;dependencies&gt;</span>
          <span class="nt">&lt;dependency&gt;</span>
              <span class="nt">&lt;groupId&gt;</span>org.osgi<span class="nt">&lt;/groupId&gt;</span>
              <span class="nt">&lt;artifactId&gt;</span>osgi.core<span class="nt">&lt;/artifactId&gt;</span>
          <span class="nt">&lt;/dependency&gt;</span>
          <span class="nt">&lt;dependency&gt;</span>
              <span class="nt">&lt;groupId&gt;</span>org.osgi<span class="nt">&lt;/groupId&gt;</span>
              <span class="nt">&lt;artifactId&gt;</span>osgi.annotation<span class="nt">&lt;/artifactId&gt;</span>
              <span class="nt">&lt;scope&gt;</span>provided<span class="nt">&lt;/scope&gt;</span>
          <span class="nt">&lt;/dependency&gt;</span>
          <span class="nt">&lt;dependency&gt;</span>
              <span class="nt">&lt;groupId&gt;</span>org.osgi<span class="nt">&lt;/groupId&gt;</span>
              <span class="nt">&lt;artifactId&gt;</span>org.osgi.namespace.service<span class="nt">&lt;/artifactId&gt;</span>
          <span class="nt">&lt;/dependency&gt;</span>

          <span class="nt">&lt;dependency&gt;</span>
              <span class="nt">&lt;groupId&gt;</span>org.junit.jupiter<span class="nt">&lt;/groupId&gt;</span>
              <span class="nt">&lt;artifactId&gt;</span>junit-jupiter<span class="nt">&lt;/artifactId&gt;</span>
              <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span>
          <span class="nt">&lt;/dependency&gt;</span>

          <span class="nt">&lt;dependency&gt;</span>
              <span class="nt">&lt;groupId&gt;</span>org.osgi<span class="nt">&lt;/groupId&gt;</span>
              <span class="nt">&lt;artifactId&gt;</span>org.osgi.test.common<span class="nt">&lt;/artifactId&gt;</span>
          <span class="nt">&lt;/dependency&gt;</span>
          <span class="nt">&lt;dependency&gt;</span>
              <span class="nt">&lt;groupId&gt;</span>org.osgi<span class="nt">&lt;/groupId&gt;</span>
              <span class="nt">&lt;artifactId&gt;</span>org.osgi.test.junit5<span class="nt">&lt;/artifactId&gt;</span>
          <span class="nt">&lt;/dependency&gt;</span>

          <span class="nt">&lt;dependency&gt;</span>
              <span class="nt">&lt;groupId&gt;</span>org.fipro.osgi.ds<span class="nt">&lt;/groupId&gt;</span>
              <span class="nt">&lt;artifactId&gt;</span>org.fipro.inverter.api<span class="nt">&lt;/artifactId&gt;</span>
              <span class="nt">&lt;version&gt;</span>${project.version}<span class="nt">&lt;/version&gt;</span>
          <span class="nt">&lt;/dependency&gt;</span>
          <span class="nt">&lt;dependency&gt;</span>
              <span class="nt">&lt;groupId&gt;</span>org.fipro.osgi.ds<span class="nt">&lt;/groupId&gt;</span>
              <span class="nt">&lt;artifactId&gt;</span>org.fipro.inverter.provider<span class="nt">&lt;/artifactId&gt;</span>
              <span class="nt">&lt;version&gt;</span>${project.version}<span class="nt">&lt;/version&gt;</span>
          <span class="nt">&lt;/dependency&gt;</span>
      <span class="nt">&lt;/dependencies&gt;</span>

      <span class="nt">&lt;build&gt;</span>
          <span class="nt">&lt;plugins&gt;</span>
              <span class="nt">&lt;plugin&gt;</span>
                  <span class="nt">&lt;groupId&gt;</span>org.apache.maven.plugins<span class="nt">&lt;/groupId&gt;</span>
                  <span class="nt">&lt;artifactId&gt;</span>maven-surefire-plugin<span class="nt">&lt;/artifactId&gt;</span>
                  <span class="c">&lt;!-- We let bnd-testing-maven-plugin do all the testing --&gt;</span>
                  <span class="nt">&lt;configuration&gt;</span>
                      <span class="nt">&lt;skipTests&gt;</span>true<span class="nt">&lt;/skipTests&gt;</span>
                  <span class="nt">&lt;/configuration&gt;</span>
              <span class="nt">&lt;/plugin&gt;</span>
              <span class="nt">&lt;plugin&gt;</span>
                  <span class="nt">&lt;groupId&gt;</span>biz.aQute.bnd<span class="nt">&lt;/groupId&gt;</span>
                  <span class="nt">&lt;artifactId&gt;</span>bnd-maven-plugin<span class="nt">&lt;/artifactId&gt;</span>
                  <span class="nt">&lt;executions&gt;</span>
                      <span class="nt">&lt;execution&gt;</span>
                          <span class="nt">&lt;id&gt;</span>test-jar<span class="nt">&lt;/id&gt;</span>
                          <span class="nt">&lt;goals&gt;</span>
                              <span class="nt">&lt;goal&gt;</span>test-jar<span class="nt">&lt;/goal&gt;</span>
                          <span class="nt">&lt;/goals&gt;</span>
                          <span class="nt">&lt;configuration&gt;</span>
                              <span class="nt">&lt;bnd&gt;</span><span class="cp">&lt;![CDATA[
                              -noextraheaders: true
                              -noimportjava: true
                              ]]&gt;</span><span class="nt">&lt;/bnd&gt;</span>
                              <span class="nt">&lt;testCases&gt;</span>junit5<span class="nt">&lt;/testCases&gt;</span>
                          <span class="nt">&lt;/configuration&gt;</span>
                      <span class="nt">&lt;/execution&gt;</span>
                  <span class="nt">&lt;/executions&gt;</span>
              <span class="nt">&lt;/plugin&gt;</span>
              <span class="nt">&lt;plugin&gt;</span>
                  <span class="nt">&lt;groupId&gt;</span>biz.aQute.bnd<span class="nt">&lt;/groupId&gt;</span>
                  <span class="nt">&lt;artifactId&gt;</span>bnd-resolver-maven-plugin<span class="nt">&lt;/artifactId&gt;</span>
              <span class="nt">&lt;/plugin&gt;</span>
              <span class="nt">&lt;plugin&gt;</span>
                  <span class="nt">&lt;groupId&gt;</span>biz.aQute.bnd<span class="nt">&lt;/groupId&gt;</span>
                  <span class="nt">&lt;artifactId&gt;</span>bnd-testing-maven-plugin<span class="nt">&lt;/artifactId&gt;</span>
              <span class="nt">&lt;/plugin&gt;</span>
          <span class="nt">&lt;/plugins&gt;</span>
      <span class="nt">&lt;/build&gt;</span>
  <span class="nt">&lt;/project&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Create a test class</p>

    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">org.fipro.inverter.integration.tests</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">static</span> <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span><span class="o">.</span><span class="na">assertNotNull</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.fipro.inverter.StringInverter</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.extension.ExtendWith</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.osgi.test.common.annotation.InjectService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.osgi.test.junit5.service.ServiceExtension</span><span class="o">;</span>

<span class="nd">@ExtendWith</span><span class="o">(</span><span class="nc">ServiceExtension</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">IntegrationTest</span> <span class="o">{</span>

  <span class="nd">@Test</span>
  <span class="kd">public</span> <span class="kt">void</span> <span class="nf">shouldInvertWithService</span><span class="o">(</span><span class="nd">@InjectService</span> <span class="nc">StringInverter</span> <span class="n">inverter</span><span class="o">)</span> <span class="o">{</span>
      <span class="n">assertNotNull</span><span class="o">(</span><span class="n">inverter</span><span class="o">,</span> <span class="s">"No StringInverter service found"</span><span class="o">);</span>
      <span class="n">assertEquals</span><span class="o">(</span><span class="s">"nospmiS"</span><span class="o">,</span> <span class="n">inverter</span><span class="o">.</span><span class="na">invert</span><span class="o">(</span><span class="s">"Simpson"</span><span class="o">));</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Create a <em>package-info.java</em> in the package where the test class resides<br />
This way we configure the requirement on a service implementation of type <code class="language-plaintext highlighter-rouge">org.fipro.inverter.StringInverter</code>, which is necessary to tell the resolver that the service provider implementation is needded for the test runtime.</p>

    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// We require a StringInverter service to test</span>
<span class="nd">@Requirement</span><span class="o">(</span>
    <span class="n">namespace</span> <span class="o">=</span> <span class="nc">ServiceNamespace</span><span class="o">.</span><span class="na">SERVICE_NAMESPACE</span><span class="o">,</span> 
    <span class="n">filter</span> <span class="o">=</span> <span class="s">"("</span> <span class="o">+</span> <span class="nc">ServiceNamespace</span><span class="o">.</span><span class="na">CAPABILITY_OBJECTCLASS_ATTRIBUTE</span> <span class="o">+</span> <span class="s">"=org.fipro.inverter.StringInverter)"</span><span class="o">)</span>
<span class="kn">package</span> <span class="nn">org.fipro.inverter.integration.tests</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.osgi.annotation.bundle.Requirement</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.osgi.namespace.service.ServiceNamespace</span><span class="o">;</span>
</code></pre></div>    </div>
  </li>
  <li>Create a <em>test.bndrun</em> test launch configuration<br />
Via the <code class="language-plaintext highlighter-rouge">-tester</code> header we configure the tester bundle for JUnit 5.<br />
Via <code class="language-plaintext highlighter-rouge">-runrequires</code> we specify the requirement on the generated test jar, and let the <code class="language-plaintext highlighter-rouge">bnd-resolver-maven-plugin</code> calculate the <code class="language-plaintext highlighter-rouge">-runbundles</code>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-tester: biz.aQute.tester.junit-platform

-runfw: org.eclipse.osgi
-resolve.effective: active

-runrequires: \
  bnd.identity;id='${project.artifactId}-tests'
</code></pre></div>    </div>
  </li>
</ul>

<p>Now you can execute the build via</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn clean verify
</code></pre></div></div>

<p>If everything is setup correctly, the build should run and execute the unit test and the integration test, and the build should succeed.</p>

<p>The sources of the Getting Started Tutorial are hosted on GitHub:</p>

<ul>
  <li><a href="https://github.com/fipro78/osgi-ds-getting-started-pde">OSGi DS Getting Started (PDE)</a><br />
This repository contains the sources in PDE project layout.</li>
  <li><a href="https://github.com/fipro78/osgi-ds-getting-started-bndtools">OSGi DS Getting Started (Bndtools)</a><br />
This repository contains the sources in Bndtools project layout using a Bndtools workspace.</li>
  <li><a href="https://github.com/fipro78/osgi-ds-getting-started-bnd-maven">OSGi DS Gettings Started (Bnd with Maven)</a><br />
This repository contains the sources in a Maven project layout that uses the bnd Maven plugins.</li>
</ul>

<p>They are updated for the contents of this <em>OSGi Component Testing Tutorial</em>.</p>]]></content><author><name>Dirk Fauth</name></author><category term="fipro" /><category term="eclipse" /><category term="java" /><category term="osgi" /><summary type="html"><![CDATA[In my last blog post I talked about Getting Started with OSGi Declarative Services. In this blog post I want to show how to test OSGi service components. This is an update to my 2016 version of this blog post about OSGi Component testing.]]></summary></entry></feed>