Documentation
Customization/UI Extensions

UI Extensions

ThinkCode provides a robust framework for extending its user interface, allowing developers to create custom panels, views, and interactions that enhance the development experience. This guide will walk you through creating effective UI extensions for ThinkCode.

UI Extension Fundamentals

What Are UI Extensions?

UI extensions allow you to add new visual elements and interactions to the ThinkCode interface. These can range from simple information panels to complex interactive tools that integrate with ThinkCode's AI capabilities.

Types of UI Extensions

ThinkCode supports several types of UI extensions:

  • WebView Panels: Custom panels that display web content
  • TreeViews: Hierarchical tree-based views for structured data
  • Status Bar Items: Items that appear in the ThinkCode status bar
  • Commands: Custom actions that users can trigger
  • Menus: Custom menu items in ThinkCode's menus
  • Editor Decorations: Visual enhancements to the code editor

Extension Integration Points

UI extensions can integrate with ThinkCode at various points:

  • Activity Bar: Add new tabs to the side bar
  • Side Bar: Create custom views in the side panel
  • Editor Area: Enhance the code editor itself
  • Status Bar: Add information or actions to the status bar
  • Command Palette: Register custom commands
  • Context Menus: Add items to right-click menus

Creating WebView Panels

WebView panels are one of the most versatile UI extensions, allowing you to create custom interfaces using web technologies (HTML, CSS, JavaScript).

Basic WebView Panel

Here's how to create a simple WebView panel:

import * as vscode from 'vscode';
import { registerExtension, WebviewProvider } from 'thinkcode-extension-api';
 
export class MyCustomPanel implements WebviewProvider {
  id = 'my.custom.panel';
  title = 'My Custom Panel';
  
  // Create and return a new webview panel
  createWebviewPanel(context: vscode.ExtensionContext): vscode.WebviewPanel {
    const panel = vscode.window.createWebviewPanel(
      'myCustomView',
      'My Custom View',
      vscode.ViewColumn.Two,
      {
        enableScripts: true,
        retainContextWhenHidden: true
      }
    );
    
    // Set panel HTML content
    panel.webview.html = this.getWebviewContent();
    
    // Handle messages from the webview
    panel.webview.onDidReceiveMessage(message => {
      console.log('Received message:', message);
      
      // Respond to the webview
      panel.webview.postMessage({ command: 'response', text: 'Message received!' });
    });
    
    return panel;
  }
  
  // Generate the HTML content for the webview
  private getWebviewContent(): string {
    return `
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>My Custom Panel</title>
        <style>
          body {
            font-family: Arial, sans-serif;
            padding: 20px;
          }
          button {
            padding: 8px 16px;
            background: #0078D4;
            color: white;
            border: none;
            border-radius: 2px;
            cursor: pointer;
          }
        </style>
      </head>
      <body>
        <h1>My Custom Panel</h1>
        <p>This is a custom panel created with ThinkCode's UI extension API.</p>
        <button id="sendMessage">Send Message to Extension</button>
        <div id="response"></div>
        
        <script>
          // Get a reference to the VS Code webview API
          const vscode = acquireVsCodeApi();
          
          // Handle button click
          document.getElementById('sendMessage').addEventListener('click', () => {
            // Send a message to the extension
            vscode.postMessage({
              command: 'hello',
              text: 'Hello from the webview!'
            });
          });
          
          // Handle messages from the extension
          window.addEventListener('message', event => {
            const message = event.data;
            document.getElementById('response').innerText = message.text;
          });
        </script>
      </body>
      </html>
    `;
  }
}
 
// Register the extension with the WebView panel
export function activate(context: vscode.ExtensionContext) {
  registerExtension({
    id: 'my.custom.ui.extension',
    name: 'My Custom UI Extension',
    version: '1.0.0',
    webviews: [new MyCustomPanel()]
  });
  
  // Register a command to open the panel
  context.subscriptions.push(
    vscode.commands.registerCommand('mycustompanel.open', () => {
      new MyCustomPanel().createWebviewPanel(context);
    })
  );
}

Advanced WebView Features

Resource Loading

Load resources (images, scripts, stylesheets) in your WebView:

// Get the path to a resource in your extension
const scriptPath = panel.webview.asWebviewUri(
  vscode.Uri.joinPath(context.extensionUri, 'media', 'script.js')
);
 
// Use in HTML
panel.webview.html = `
  <!DOCTYPE html>
  <html>
  <head>
    <script src="${scriptPath}"></script>
  </head>
  <body>
    {/* Content */}
  </body>
  </html>
`;

State Persistence

Maintain WebView state between sessions:

// In your WebView provider
private _state = { count: 0 };
 
// When creating the WebView
panel.webview.onDidReceiveMessage(message => {
  if (message.command === 'updateState') {
    this._state = message.state;
    // Optionally persist to storage
    context.globalState.update('myWebviewState', this._state);
  }
});
 
// Restore state when creating the WebView
const savedState = context.globalState.get('myWebviewState');
if (savedState) {
  this._state = savedState;
  panel.webview.postMessage({ command: 'setState', state: this._state });
}

Creating TreeViews

TreeViews provide a hierarchical display of information, ideal for showing project structure, test results, or other organized data.

Basic TreeView Implementation

import * as vscode from 'vscode';
import { registerExtension } from 'thinkcode-extension-api';
 
// Define tree data provider
class CustomTreeDataProvider implements vscode.TreeDataProvider<TreeItem> {
  private _onDidChangeTreeData = new vscode.EventEmitter<TreeItem | undefined>();
  readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
  
  // Root data items
  private data: TreeItem[] = [
    new TreeItem('Section 1', [
      new TreeItem('Item 1.1'),
      new TreeItem('Item 1.2')
    ]),
    new TreeItem('Section 2', [
      new TreeItem('Item 2.1'),
      new TreeItem('Item 2.2')
    ])
  ];
  
  // Required TreeDataProvider methods
  getTreeItem(element: TreeItem): vscode.TreeItem {
    return element;
  }
  
  getChildren(element?: TreeItem): Thenable<TreeItem[]> {
    if (element) {
      return Promise.resolve(element.children || []);
    } else {
      return Promise.resolve(this.data);
    }
  }
  
  // Method to refresh the tree view
  refresh(): void {
    this._onDidChangeTreeData.fire(undefined);
  }
}
 
// Define tree item class
class TreeItem extends vscode.TreeItem {
  constructor(
    public readonly label: string,
    public readonly children?: TreeItem[]
  ) {
    super(
      label,
      children ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None
    );
  }
}
 
// Register the extension
export function activate(context: vscode.ExtensionContext) {
  // Create the tree data provider
  const treeDataProvider = new CustomTreeDataProvider();
  
  // Register the tree view
  const treeView = vscode.window.createTreeView('myCustomTreeView', {
    treeDataProvider,
    showCollapseAll: true
  });
  
  // Register a command to refresh the tree
  context.subscriptions.push(
    vscode.commands.registerCommand('myTreeView.refresh', () => {
      treeDataProvider.refresh();
    })
  );
  
  context.subscriptions.push(treeView);
}

TreeView with Icons and Context Actions

Enhance your TreeView with icons and context menu actions:

class EnhancedTreeItem extends vscode.TreeItem {
  constructor(
    public readonly label: string,
    public readonly children?: EnhancedTreeItem[],
    public readonly type: 'folder' | 'file' | 'task' = 'file'
  ) {
    super(
      label,
      children ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None
    );
    
    // Set icon based on type
    if (type === 'folder') {
      this.iconPath = new vscode.ThemeIcon('folder');
    } else if (type === 'file') {
      this.iconPath = new vscode.ThemeIcon('file');
    } else if (type === 'task') {
      this.iconPath = new vscode.ThemeIcon('checklist');
    }
    
    // Add context menu actions
    this.contextValue = type;
    
    // Add a command that executes when this item is clicked
    this.command = {
      command: 'myTreeView.itemClicked',
      title: 'Item Clicked',
      arguments: [this]
    };
  }
}
 
// In your activate function, register context menu commands
context.subscriptions.push(
  vscode.commands.registerCommand('myTreeView.itemClicked', (item: EnhancedTreeItem) => {
    vscode.window.showInformationMessage(`Clicked on ${item.label}`);
  }),
  
  vscode.commands.registerCommand('myTreeView.deleteItem', (item: EnhancedTreeItem) => {
    vscode.window.showInformationMessage(`Deleting ${item.label}`);
    // Implement deletion logic
  })
);

In your package.json, register context menu actions:

"contributes": {
  "views": {
    "explorer": [
      {
        "id": "myCustomTreeView",
        "name": "My Tree View"
      }
    ]
  },
  "menus": {
    "view/item/context": [
      {
        "command": "myTreeView.deleteItem",
        "when": "view == myCustomTreeView && viewItem == file",
        "group": "inline"
      }
    ]
  }
}

Status Bar Extensions

Status bar items provide quick information and actions in ThinkCode's status bar.

Basic Status Bar Item

import * as vscode from 'vscode';
 
export function activate(context: vscode.ExtensionContext) {
  // Create a status bar item
  const statusBarItem = vscode.window.createStatusBarItem(
    vscode.StatusBarAlignment.Right,
    100  // Priority (higher number = more left)
  );
  
  // Configure the status bar item
  statusBarItem.text = "$(rocket) ThinkCode";
  statusBarItem.tooltip = "Click to open ThinkCode dashboard";
  statusBarItem.command = "thinkcode.openDashboard";
  
  // Show the status bar item
  statusBarItem.show();
  
  // Register the associated command
  context.subscriptions.push(
    vscode.commands.registerCommand("thinkcode.openDashboard", () => {
      vscode.window.showInformationMessage("Opening ThinkCode dashboard");
      // Implement dashboard opening logic
    })
  );
  
  // Add to subscriptions to ensure proper disposal
  context.subscriptions.push(statusBarItem);
}

Dynamic Status Bar Updates

Update the status bar based on events or state changes:

// Create status bar with live updates
export class AIStatusProvider {
  private statusBarItem: vscode.StatusBarItem;
  private aiStatus: 'idle' | 'thinking' | 'ready' = 'idle';
  
  constructor() {
    this.statusBarItem = vscode.window.createStatusBarItem(
      vscode.StatusBarAlignment.Right,
      200
    );
    
    // Update initially
    this.updateStatusBar();
    
    // Listen for AI status changes
    events.on('ai.statusChanged', (status: 'idle' | 'thinking' | 'ready') => {
      this.aiStatus = status;
      this.updateStatusBar();
    });
  }
  
  private updateStatusBar() {
    switch (this.aiStatus) {
      case 'idle':
        this.statusBarItem.text = "$(brain) AI: Idle";
        this.statusBarItem.backgroundColor = undefined;
        break;
      case 'thinking':
        this.statusBarItem.text = "$(sync~spin) AI: Thinking";
        this.statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground');
        break;
      case 'ready':
        this.statusBarItem.text = "$(check) AI: Ready";
        this.statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.prominentBackground');
        break;
    }
    
    this.statusBarItem.show();
  }
  
  dispose() {
    this.statusBarItem.dispose();
  }
}

Custom Commands and Menus

Commands and menus allow users to interact with your extension features.

Registering Custom Commands

export function activate(context: vscode.ExtensionContext) {
  // Register a simple command
  context.subscriptions.push(
    vscode.commands.registerCommand('thinkcode.helloWorld', () => {
      vscode.window.showInformationMessage('Hello from ThinkCode extension!');
    })
  );
  
  // Command with parameters
  context.subscriptions.push(
    vscode.commands.registerCommand('thinkcode.analyzeCode', async () => {
      const editor = vscode.window.activeTextEditor;
      if (editor) {
        const document = editor.document;
        const selection = editor.selection;
        const text = document.getText(selection);
        
        if (text.length > 0) {
          // Perform code analysis
          const analysis = await analyzeCode(text);
          
          // Show results
          vscode.window.showInformationMessage(`Analysis: ${analysis.summary}`);
        } else {
          vscode.window.showWarningMessage('No code selected for analysis');
        }
      }
    })
  );
}

Adding Menu Items

In your package.json, add menu contributions:

"contributes": {
  "commands": [
    {
      "command": "thinkcode.analyzeCode",
      "title": "Analyze Selected Code",
      "category": "ThinkCode"
    },
    {
      "command": "thinkcode.generateTest",
      "title": "Generate Test for Function",
      "category": "ThinkCode"
    }
  ],
  "menus": {
    "editor/context": [
      {
        "command": "thinkcode.analyzeCode",
        "when": "editorHasSelection",
        "group": "thinkcode"
      }
    ],
    "explorer/context": [
      {
        "command": "thinkcode.generateTest",
        "when": "resourceExtname == .js || resourceExtname == .ts",
        "group": "thinkcode"
      }
    ],
    "commandPalette": [
      {
        "command": "thinkcode.analyzeCode",
        "when": "editorHasSelection"
      },
      {
        "command": "thinkcode.generateTest",
        "when": "editorLangId == javascript || editorLangId == typescript"
      }
    ]
  }
}

Editor Decorations

Editor decorations allow you to enhance the code editor with visual elements like highlights, tooltips, and inline content.

Basic Editor Decorations

import * as vscode from 'vscode';
 
export function activate(context: vscode.ExtensionContext) {
  // Create a decoration type for highlighting
  const highlightDecoration = vscode.window.createTextEditorDecorationType({
    backgroundColor: new vscode.ThemeColor('editor.findMatchHighlightBackground'),
    border: '1px solid',
    borderColor: new vscode.ThemeColor('editor.findMatchHighlightBorder')
  });
  
  // Create a decoration type for inline information
  const infoDecoration = vscode.window.createTextEditorDecorationType({
    after: {
      margin: '0 0 0 1em',
      textDecoration: 'none',
      color: new vscode.ThemeColor('editorCodeLens.foreground')
    }
  });
  
  // Register a command to highlight code
  context.subscriptions.push(
    vscode.commands.registerCommand('thinkcode.highlightImportantCode', () => {
      const editor = vscode.window.activeTextEditor;
      if (editor) {
        // Find patterns to highlight (e.g., function declarations)
        const text = editor.document.getText();
        const functionRegex = /function\s+(\w+)\s*\(/g;
        
        const highlightRanges: vscode.Range[] = [];
        const infoRanges: vscode.DecorationOptions[] = [];
        
        let match;
        while ((match = functionRegex.exec(text)) !== null) {
          const startPos = editor.document.positionAt(match.index);
          const endPos = editor.document.positionAt(match.index + match[0].length);
          const range = new vscode.Range(startPos, endPos);
          
          highlightRanges.push(range);
          
          // Add info about the function after its declaration
          infoRanges.push({
            range: new vscode.Range(endPos, endPos),
            renderOptions: {
              after: {
                contentText: `  // Function: ${match[1]}`
              }
            }
          });
        }
        
        // Apply the decorations
        editor.setDecorations(highlightDecoration, highlightRanges);
        editor.setDecorations(infoDecoration, infoRanges);
      }
    })
  );
  
  // Clean up decorations when the extension is deactivated
  context.subscriptions.push(highlightDecoration);
  context.subscriptions.push(infoDecoration);
}

AI-Enhanced Decorations

Combine editor decorations with ThinkCode's AI capabilities:

// Register a command to analyze and annotate code
context.subscriptions.push(
  vscode.commands.registerCommand('thinkcode.analyzeAndAnnotate', async () => {
    const editor = vscode.window.activeTextEditor;
    if (editor) {
      // Show a status message
      vscode.window.withProgress(
        {
          location: vscode.ProgressLocation.Notification,
          title: "Analyzing code with AI...",
          cancellable: true
        },
        async (progress, token) => {
          // Get the entire document text
          const document = editor.document;
          const text = document.getText();
          
          // Use ThinkCode's AI to analyze the code
          const analysis = await thinkCodeApi.analyzeCode(text, {
            complexity: true,
            security: true,
            performance: true
          });
          
          // Create decorations based on the analysis
          const complexityDecorations: vscode.DecorationOptions[] = [];
          const securityDecorations: vscode.DecorationOptions[] = [];
          
          // Process complexity insights
          for (const insight of analysis.insights.complexity) {
            const range = new vscode.Range(
              document.positionAt(insight.startOffset),
              document.positionAt(insight.endOffset)
            );
            
            complexityDecorations.push({
              range,
              hoverMessage: new vscode.MarkdownString(`**Complexity Issue:** ${insight.message}\n\n${insight.suggestion}`)
            });
          }
          
          // Process security insights
          for (const insight of analysis.insights.security) {
            const range = new vscode.Range(
              document.positionAt(insight.startOffset),
              document.positionAt(insight.endOffset)
            );
            
            securityDecorations.push({
              range,
              hoverMessage: new vscode.MarkdownString(`⚠️ **Security Issue:** ${insight.message}\n\n${insight.suggestion}`),
              renderOptions: {
                after: {
                  contentText: '  ⚠️',
                  color: 'var(--vscode-editorError-foreground)'
                }
              }
            });
          }
          
          // Apply the decorations
          editor.setDecorations(complexityDecorationType, complexityDecorations);
          editor.setDecorations(securityDecorationType, securityDecorations);
          
          return Promise.resolve();
        }
      );
    }
  })
);

Best Practices for UI Extensions

Performance Considerations

  • Lazy Loading: Load resources only when needed
  • Efficient Updates: Minimize UI updates and DOM operations
  • Asynchronous Operations: Don't block the UI thread
  • Resource Disposal: Clean up resources when no longer needed

User Experience Guidelines

  • Consistency: Follow ThinkCode's design patterns and UI conventions
  • Simplicity: Focus on core functionality without cluttering the UI
  • Accessibility: Ensure your UI works well with screen readers and keyboard navigation
  • Responsiveness: Provide feedback for long-running operations

Testing UI Extensions

  • Test in different themes (light, dark)
  • Test with various screen sizes and resolutions
  • Test with keyboard-only navigation
  • Test with screen readers

Troubleshooting

Common issues and solutions:

  • WebView Not Appearing: Check view registration and container visibility
  • Style Issues: Ensure CSS is compatible with ThinkCode's themes
  • Communication Problems: Verify message posting and handlers
  • Resource Loading Failures: Check paths and URI conversions

Resources

Next Steps

After mastering UI extensions, consider exploring: