Add settings UI with WSL configuration support

- Add settings button to webview UI
- Implement settings panel with WSL bridge configuration
- Add real-time settings updates via VS Code configuration API
- Support for WSL distro, node path, and claude path configuration
- Improve user experience by eliminating manual config file editing
This commit is contained in:
erwinh22
2025-06-19 15:10:04 -04:00
parent 39ff5bb4e4
commit a0bbc5764e
7 changed files with 349 additions and 27 deletions

View File

@@ -4,6 +4,21 @@ All notable changes to the "claude-code-chat" extension will be documented in th
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased] ## [0.0.8] - 2025-06-19
### Added
- WSL (Windows Subsystem for Linux) configuration support
- New setting: `claudeCodeChat.wsl.enabled` to enable WSL integration
- New setting: `claudeCodeChat.wsl.distro` to specify WSL distribution
- New setting: `claudeCodeChat.wsl.nodePath` to configure Node.js path in WSL
- New setting: `claudeCodeChat.wsl.claudePath` to configure Claude path in WSL
- Automatic detection of execution environment (native vs WSL)
- WSL support for Claude login terminal command
### Changed
- Claude execution now supports both native and WSL environments based on configuration
- Terminal login command adapts to WSL settings when enabled
## [0.0.7] - Previous Release
- Initial release - Initial release

View File

@@ -160,6 +160,27 @@ If you want to revert these changes, just click "Restore Checkpoint" to go back
| `Enter` | Send message | | `Enter` | Send message |
| `@` | Open file picker | | `@` | Open file picker |
### WSL Configuration (Windows Users)
If you're using Claude Code through WSL (Windows Subsystem for Linux), you can configure the extension to use WSL:
1. Open VS Code Settings (`Ctrl+,` or `Cmd+,`)
2. Search for "Claude Code Chat"
3. Configure these settings:
- **Claude Code Chat: WSL Enabled** - Enable WSL integration
- **Claude Code Chat: WSL Distro** - Your WSL distribution name (e.g., `Ubuntu`, `Debian`)
- **Claude Code Chat: WSL Node Path** - Path to Node.js in WSL (default: `/usr/bin/node`)
- **Claude Code Chat: WSL Claude Path** - Path to Claude in WSL (default: `/usr/local/bin/claude`)
Example configuration in `settings.json`:
```json
{
"claudeCodeChat.wsl.enabled": true,
"claudeCodeChat.wsl.distro": "Ubuntu",
"claudeCodeChat.wsl.nodePath": "/usr/bin/node",
"claudeCodeChat.wsl.claudePath": "/usr/local/bin/claude"
}
```
--- ---
## 🎯 **Pro Tips & Tricks** ## 🎯 **Pro Tips & Tricks**

55
TEST_SETTINGS.md Normal file
View File

@@ -0,0 +1,55 @@
# Settings Interface Test Plan
## Overview
Added a settings interface to the Claude Code Chat webview that allows users to configure WSL settings.
## Changes Made
### 1. UI Changes (ui.ts)
- Added a settings button (⚙️) in the header that's always visible
- Created a settings modal with WSL configuration options:
- Enable WSL Integration checkbox
- WSL Distribution input field
- Node.js Path in WSL input field
- Claude Path in WSL input field
- Added JavaScript functions:
- `toggleSettings()` - Shows/hides the settings modal
- `hideSettingsModal()` - Hides the settings modal
- `updateSettings()` - Sends settings changes to VS Code
- Event listeners for modal interaction
### 2. Extension Changes (extension.ts)
- Added message handlers:
- `getSettings` - Retrieves current settings from VS Code configuration
- `updateSettings` - Updates VS Code configuration with new settings
- Added methods:
- `_sendCurrentSettings()` - Sends current settings to webview
- `_updateSettings()` - Updates VS Code configuration
### 3. Configuration (package.json)
- Already has WSL configuration properties defined:
- `claudeCodeChat.wsl.enabled`
- `claudeCodeChat.wsl.distro`
- `claudeCodeChat.wsl.nodePath`
- `claudeCodeChat.wsl.claudePath`
## Testing Steps
1. Open VS Code with the extension
2. Open Claude Code Chat (Ctrl+Shift+C)
3. Click the settings button (⚙️) in the header
4. Verify the settings modal appears
5. Check that current WSL settings are loaded
6. Toggle "Enable WSL Integration" and verify:
- WSL options show/hide accordingly
- Settings are saved when changed
7. Modify WSL settings and verify they persist
8. Close and reopen the settings to confirm values are saved
## Features
- Settings button is always visible in the header
- Modal design matches the existing tools modal
- Real-time show/hide of WSL options based on enabled state
- Settings persist across sessions
- Success/error notifications when saving settings

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "claude-code-chat", "name": "claude-code-chat",
"version": "0.0.6", "version": "0.0.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "claude-code-chat", "name": "claude-code-chat",
"version": "0.0.6", "version": "0.0.7",
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"devDependencies": { "devDependencies": {
"@types/mocha": "^10.0.10", "@types/mocha": "^10.0.10",

View File

@@ -2,7 +2,7 @@
"name": "claude-code-chat", "name": "claude-code-chat",
"displayName": "Claude Code Chat", "displayName": "Claude Code Chat",
"description": "Beautiful Claude Code Chat Interface for VS Code", "description": "Beautiful Claude Code Chat Interface for VS Code",
"version": "0.0.7", "version": "0.0.8",
"publisher": "AndrePimenta", "publisher": "AndrePimenta",
"author": "Andre Pimenta", "author": "Andre Pimenta",
"repository": { "repository": {
@@ -47,6 +47,9 @@
], ],
"icon": "icon.png", "icon": "icon.png",
"main": "./out/extension.js", "main": "./out/extension.js",
"activationEvents": [
"onStartupFinished"
],
"contributes": { "contributes": {
"commands": [ "commands": [
{ {
@@ -142,6 +145,31 @@
"icon": "icon.png" "icon": "icon.png"
} }
] ]
},
"configuration": {
"title": "Claude Code Chat",
"properties": {
"claudeCodeChat.wsl.enabled": {
"type": "boolean",
"default": false,
"description": "Enable WSL integration for running Claude"
},
"claudeCodeChat.wsl.distro": {
"type": "string",
"default": "Ubuntu",
"description": "WSL distribution name (e.g., Ubuntu, Debian)"
},
"claudeCodeChat.wsl.nodePath": {
"type": "string",
"default": "/usr/bin/node",
"description": "Path to Node.js in the WSL distribution"
},
"claudeCodeChat.wsl.claudePath": {
"type": "string",
"default": "/usr/local/bin/claude",
"description": "Path to Claude executable in the WSL distribution"
}
}
} }
}, },
"scripts": { "scripts": {

View File

@@ -7,9 +7,11 @@ import html from './ui';
const exec = util.promisify(cp.exec); const exec = util.promisify(cp.exec);
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
console.log('Claude Code Chat extension is being activated!');
const provider = new ClaudeChatProvider(context.extensionUri, context); const provider = new ClaudeChatProvider(context.extensionUri, context);
const disposable = vscode.commands.registerCommand('claude-code-chat.openChat', () => { const disposable = vscode.commands.registerCommand('claude-code-chat.openChat', () => {
console.log('Claude Code Chat command executed!');
provider.show(); provider.show();
}); });
@@ -33,6 +35,7 @@ export function activate(context: vscode.ExtensionContext) {
statusBarItem.show(); statusBarItem.show();
context.subscriptions.push(disposable, loadConversationDisposable, statusBarItem); context.subscriptions.push(disposable, loadConversationDisposable, statusBarItem);
console.log('Claude Code Chat extension activation completed successfully!');
} }
export function deactivate() { } export function deactivate() { }
@@ -197,6 +200,12 @@ class ClaudeChatProvider {
case 'stopRequest': case 'stopRequest':
this._stopClaudeProcess(); this._stopClaudeProcess();
return; return;
case 'getSettings':
this._sendCurrentSettings();
return;
case 'updateSettings':
this._updateSettings(message.settings);
return;
} }
}, },
null, null,
@@ -252,7 +261,7 @@ class ClaudeChatProvider {
await this._createBackupCommit(message); await this._createBackupCommit(message);
} }
catch (e) { catch (e) {
console.log("error", e) console.log("error", e);
} }
// Show loading indicator // Show loading indicator
@@ -281,15 +290,40 @@ class ClaudeChatProvider {
console.log('Claude command args:', args); console.log('Claude command args:', args);
const claudeProcess = cp.spawn('claude', args, { // Get configuration
cwd: cwd, const config = vscode.workspace.getConfiguration('claudeCodeChat');
stdio: ['pipe', 'pipe', 'pipe'], const wslEnabled = config.get<boolean>('wsl.enabled', false);
env: { const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
...process.env, const nodePath = config.get<string>('wsl.nodePath', '/usr/bin/node');
FORCE_COLOR: '0', const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
NO_COLOR: '1'
} let claudeProcess: cp.ChildProcess;
});
if (wslEnabled) {
// Use WSL
console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath });
claudeProcess = cp.spawn('wsl', ['-d', wslDistro, nodePath, '--no-warnings', '--enable-source-maps', claudePath, ...args], {
cwd: cwd,
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
FORCE_COLOR: '0',
NO_COLOR: '1'
}
});
} else {
// Use native claude command
console.log('Using native Claude command');
claudeProcess = cp.spawn('claude', args, {
cwd: cwd,
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
FORCE_COLOR: '0',
NO_COLOR: '1'
}
});
}
// Store process reference for potential termination // Store process reference for potential termination
this._currentClaudeProcess = claudeProcess; this._currentClaudeProcess = claudeProcess;
@@ -582,9 +616,19 @@ class ClaudeChatProvider {
type: 'loginRequired' type: 'loginRequired'
}); });
// Get configuration to check if WSL is enabled
const config = vscode.workspace.getConfiguration('claudeCodeChat');
const wslEnabled = config.get<boolean>('wsl.enabled', false);
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
// Open terminal and run claude login // Open terminal and run claude login
const terminal = vscode.window.createTerminal('Claude Login'); const terminal = vscode.window.createTerminal('Claude Login');
terminal.sendText('claude'); if (wslEnabled) {
terminal.sendText(`wsl -d ${wslDistro} ${claudePath}`);
} else {
terminal.sendText('claude');
}
terminal.show(); terminal.show();
// Show info message // Show info message
@@ -597,7 +641,7 @@ class ClaudeChatProvider {
private async _initializeBackupRepo(): Promise<void> { private async _initializeBackupRepo(): Promise<void> {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) return; if (!workspaceFolder) {return;}
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) { if (!storagePath) {
@@ -630,7 +674,7 @@ class ClaudeChatProvider {
private async _createBackupCommit(userMessage: string): Promise<void> { private async _createBackupCommit(userMessage: string): Promise<void> {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder || !this._backupRepoPath) return; if (!workspaceFolder || !this._backupRepoPath) {return;}
const workspacePath = workspaceFolder.uri.fsPath; const workspacePath = workspaceFolder.uri.fsPath;
const now = new Date(); const now = new Date();
@@ -739,10 +783,10 @@ class ClaudeChatProvider {
private async _initializeConversations(): Promise<void> { private async _initializeConversations(): Promise<void> {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) return; if (!workspaceFolder) {return;}
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) return; if (!storagePath) {return;}
this._conversationsPath = path.join(storagePath, 'conversations'); this._conversationsPath = path.join(storagePath, 'conversations');
@@ -765,7 +809,7 @@ class ClaudeChatProvider {
} }
if (message.type === 'sessionInfo') { if (message.type === 'sessionInfo') {
message.data.sessionId message.data.sessionId;
} }
// Send to UI // Send to UI
@@ -783,7 +827,7 @@ class ClaudeChatProvider {
} }
private async _saveCurrentConversation(): Promise<void> { private async _saveCurrentConversation(): Promise<void> {
if (!this._conversationsPath || this._currentConversation.length === 0) return; if (!this._conversationsPath || this._currentConversation.length === 0) {return;}
try { try {
// Create filename from first user message and timestamp // Create filename from first user message and timestamp
@@ -1009,12 +1053,12 @@ class ClaudeChatProvider {
} }
private async _loadConversationHistory(filename: string): Promise<void> { private async _loadConversationHistory(filename: string): Promise<void> {
console.log("_loadConversationHistory") console.log("_loadConversationHistory");
if (!this._conversationsPath) return; if (!this._conversationsPath) {return;}
try { try {
const filePath = path.join(this._conversationsPath, filename); const filePath = path.join(this._conversationsPath, filename);
console.log("filePath", filePath) console.log("filePath", filePath);
let conversationData; let conversationData;
try { try {
@@ -1025,7 +1069,7 @@ class ClaudeChatProvider {
return; return;
} }
console.log("conversationData", conversationData) console.log("conversationData", conversationData);
// Load conversation into current state // Load conversation into current state
this._currentConversation = conversationData.messages || []; this._currentConversation = conversationData.messages || [];
this._conversationStartTime = conversationData.startTime; this._conversationStartTime = conversationData.startTime;
@@ -1069,7 +1113,37 @@ class ClaudeChatProvider {
} }
private _getHtmlForWebview(): string { private _getHtmlForWebview(): string {
return html return html;
}
private _sendCurrentSettings(): void {
const config = vscode.workspace.getConfiguration('claudeCodeChat');
const settings = {
'wsl.enabled': config.get<boolean>('wsl.enabled', false),
'wsl.distro': config.get<string>('wsl.distro', 'Ubuntu'),
'wsl.nodePath': config.get<string>('wsl.nodePath', '/usr/bin/node'),
'wsl.claudePath': config.get<string>('wsl.claudePath', '/usr/local/bin/claude')
};
this._panel?.webview.postMessage({
type: 'settingsData',
data: settings
});
}
private async _updateSettings(settings: { [key: string]: any }): Promise<void> {
const config = vscode.workspace.getConfiguration('claudeCodeChat');
try {
for (const [key, value] of Object.entries(settings)) {
await config.update(key, value, vscode.ConfigurationTarget.Global);
}
vscode.window.showInformationMessage('Settings updated successfully');
} catch (error) {
console.error('Failed to update settings:', error);
vscode.window.showErrorMessage('Failed to update settings');
}
} }
public dispose() { public dispose() {

131
src/ui.ts
View File

@@ -727,7 +727,7 @@ const html = `<!DOCTYPE html>
background-color: var(--vscode-editor-background); background-color: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border); border: 1px solid var(--vscode-panel-border);
border-radius: 4px; border-radius: 4px;
width: 450px; width: 500px;
max-height: 600px; max-height: 600px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -797,6 +797,17 @@ const html = `<!DOCTYPE html>
opacity: 0.7; opacity: 0.7;
} }
.settings-group {
margin-bottom: 20px;
}
.settings-group h3 {
margin: 0 0 12px 0;
font-size: 13px;
font-weight: 600;
color: var(--vscode-foreground);
}
.status { .status {
padding: 8px 12px; padding: 8px 12px;
background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%); background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%);
@@ -1071,6 +1082,7 @@ const html = `<!DOCTYPE html>
</div> </div>
<div style="display: flex; gap: 8px; align-items: center;"> <div style="display: flex; gap: 8px; align-items: center;">
<div id="sessionStatus" class="session-status" style="display: none;">No session</div> <div id="sessionStatus" class="session-status" style="display: none;">No session</div>
<button class="btn outlined" id="settingsBtn" onclick="toggleSettings()" title="Settings">⚙️</button>
<button class="btn outlined" id="historyBtn" onclick="toggleConversationHistory()" style="display: none;">📚 History</button> <button class="btn outlined" id="historyBtn" onclick="toggleConversationHistory()" style="display: none;">📚 History</button>
<button class="btn primary" id="newSessionBtn" onclick="newSession()" style="display: none;">New Chat</button> <button class="btn primary" id="newSessionBtn" onclick="newSession()" style="display: none;">New Chat</button>
</div> </div>
@@ -1216,6 +1228,57 @@ const html = `<!DOCTYPE html>
</div> </div>
</div> </div>
<!-- Settings modal -->
<div id="settingsModal" class="tools-modal" style="display: none;">
<div class="tools-modal-content">
<div class="tools-modal-header">
<span>Claude Code Chat Settings</span>
<button class="tools-close-btn" onclick="hideSettingsModal()">✕</button>
</div>
<div class="tools-list">
<h3 style="margin-top: 0; margin-bottom: 16px; font-size: 14px; font-weight: 600;">WSL Configuration</h3>
<div class="settings-group">
<div class="tool-item">
<input type="checkbox" id="wsl-enabled" onchange="updateSettings()">
<label for="wsl-enabled">Enable WSL Integration</label>
</div>
<div id="wslOptions" style="margin-left: 24px; margin-top: 12px;">
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: var(--vscode-descriptionForeground);">WSL Distribution</label>
<input type="text" id="wsl-distro" class="file-search-input" style="width: 100%;" placeholder="Ubuntu" onchange="updateSettings()">
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: var(--vscode-descriptionForeground);">Node.js Path in WSL</label>
<input type="text" id="wsl-node-path" class="file-search-input" style="width: 100%;" placeholder="/usr/bin/node" onchange="updateSettings()">
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 4px 0 0 0;">
Find your node installation path in WSL by running: <code style="background: var(--vscode-textCodeBlock-background); padding: 2px 4px; border-radius: 3px;">which node</code>
</p>
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: var(--vscode-descriptionForeground);">Claude Path in WSL</label>
<input type="text" id="wsl-claude-path" class="file-search-input" style="width: 100%;" placeholder="/usr/local/bin/claude" onchange="updateSettings()">
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 4px 0 0 0;">
Find your claude installation path in WSL by running: <code style="background: var(--vscode-textCodeBlock-background); padding: 2px 4px; border-radius: 3px;">which claude</code>
</p>
</div>
</div>
</div>
<div style="margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--vscode-panel-border);">
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 0;">
WSL integration allows you to run Claude Code from within Windows Subsystem for Linux.
This is useful if you have Claude installed in WSL instead of Windows.
</p>
</div>
</div>
</div>
</div>
<script> <script>
const vscode = acquireVsCodeApi(); const vscode = acquireVsCodeApi();
const messagesDiv = document.getElementById('messages'); const messagesDiv = document.getElementById('messages');
@@ -2281,6 +2344,72 @@ const html = `<!DOCTYPE html>
listDiv.appendChild(item); listDiv.appendChild(item);
}); });
} }
// Settings functions
function toggleSettings() {
const settingsModal = document.getElementById('settingsModal');
if (settingsModal.style.display === 'none') {
// Request current settings from VS Code
vscode.postMessage({
type: 'getSettings'
});
settingsModal.style.display = 'flex';
} else {
hideSettingsModal();
}
}
function hideSettingsModal() {
document.getElementById('settingsModal').style.display = 'none';
}
function updateSettings() {
const wslEnabled = document.getElementById('wsl-enabled').checked;
const wslDistro = document.getElementById('wsl-distro').value;
const wslNodePath = document.getElementById('wsl-node-path').value;
const wslClaudePath = document.getElementById('wsl-claude-path').value;
// Update WSL options visibility
document.getElementById('wslOptions').style.display = wslEnabled ? 'block' : 'none';
// Send settings to VS Code immediately
vscode.postMessage({
type: 'updateSettings',
settings: {
'wsl.enabled': wslEnabled,
'wsl.distro': wslDistro || 'Ubuntu',
'wsl.nodePath': wslNodePath || '/usr/bin/node',
'wsl.claudePath': wslClaudePath || '/usr/local/bin/claude'
}
});
}
// Close settings modal when clicking outside
document.getElementById('settingsModal').addEventListener('click', (e) => {
if (e.target === document.getElementById('settingsModal')) {
hideSettingsModal();
}
});
// Add settings message handler to window message event
const originalMessageHandler = window.onmessage;
window.addEventListener('message', event => {
const message = event.data;
if (message.type === 'settingsData') {
// Update UI with current settings
document.getElementById('wsl-enabled').checked = message.data['wsl.enabled'] || false;
document.getElementById('wsl-distro').value = message.data['wsl.distro'] || 'Ubuntu';
document.getElementById('wsl-node-path').value = message.data['wsl.nodePath'] || '/usr/bin/node';
document.getElementById('wsl-claude-path').value = message.data['wsl.claudePath'] || '/usr/local/bin/claude';
// Show/hide WSL options
document.getElementById('wslOptions').style.display = message.data['wsl.enabled'] ? 'block' : 'none';
}
});
</script> </script>
</body> </body>
</html>`; </html>`;