mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
10 Commits
fix/git-di
...
feature/vs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a79712bdb | ||
|
|
eef2a006f0 | ||
|
|
34645d7f16 | ||
|
|
6041f3927b | ||
|
|
4f0ae3a16b | ||
|
|
fc870199be | ||
|
|
dc51f3a569 | ||
|
|
246a9d8359 | ||
|
|
dbc02cc980 | ||
|
|
4dfb45bf4f |
@@ -41,6 +41,7 @@ class VSCodePlugin(Plugin):
|
||||
self.vscode_port = int(os.environ['VSCODE_PORT'])
|
||||
self.vscode_connection_token = str(uuid.uuid4())
|
||||
assert check_port_available(self.vscode_port)
|
||||
|
||||
cmd = (
|
||||
f"su - {username} -s /bin/bash << 'EOF'\n"
|
||||
f'sudo chown -R {username}:{username} /openhands/.openvscode-server\n'
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"workbench.colorTheme": "Default Dark Modern",
|
||||
"workbench.startupEditor": "none"
|
||||
"workbench.startupEditor": "none",
|
||||
"workbench.startupEditorAssociations": {
|
||||
"openhands-changes-view": "openhands-changes-viewer.openhands-changes-view"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,12 +90,10 @@ RUN if [ -z "${RELEASE_TAG}" ]; then \
|
||||
{% endmacro %}
|
||||
|
||||
{% macro install_vscode_extensions() %}
|
||||
# Install our custom extension
|
||||
RUN mkdir -p ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-hello-world && \
|
||||
cp -r /openhands/code/openhands/runtime/utils/vscode-extensions/hello-world/* ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-hello-world/
|
||||
|
||||
RUN mkdir -p ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-memory-monitor && \
|
||||
cp -r /openhands/code/openhands/runtime/utils/vscode-extensions/memory-monitor/* ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-memory-monitor/
|
||||
# Install our custom extensions
|
||||
# Copy all extensions from source to destination using a recursive approach
|
||||
# The extension folders are named to match their package names (openhands-*)
|
||||
RUN cp -r /openhands/code/openhands/runtime/utils/vscode-extensions/* ${OPENVSCODE_SERVER_ROOT}/extensions/
|
||||
|
||||
# Some extension dirs are removed because they trigger false positives in vulnerability scans.
|
||||
RUN rm -rf ${OPENVSCODE_SERVER_ROOT}/extensions/{handlebars,pug,json,diff,grunt,ini,npm}
|
||||
@@ -130,6 +128,7 @@ RUN /openhands/micromamba/bin/micromamba run -n openhands poetry cache clear --a
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
||||
/openhands/micromamba/bin/micromamba clean --all
|
||||
|
||||
{{ setup_vscode_server() }}
|
||||
{% endmacro %}
|
||||
|
||||
{% if build_from_scratch %}
|
||||
@@ -175,14 +174,13 @@ COPY ./code/pyproject.toml ./code/poetry.lock /openhands/code/
|
||||
COPY ./code/openhands /openhands/code/openhands
|
||||
RUN chmod a+rwx /openhands/code/openhands/__init__.py
|
||||
|
||||
{{ setup_vscode_server() }}
|
||||
{{ install_vscode_extensions() }}
|
||||
|
||||
# ================================================================
|
||||
# END: Build from versioned image
|
||||
# ================================================================
|
||||
{% if build_from_versioned %}
|
||||
{{ install_dependencies() }}
|
||||
{{ install_vscode_extensions() }}
|
||||
{% endif %}
|
||||
|
||||
# Install extra dependencies if specified
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# OpenHands Changes Viewer
|
||||
|
||||
A VSCode extension for viewing changes made by the OpenHands agent.
|
||||
|
||||
## Features
|
||||
|
||||
- Shows a list of changed files in the workspace
|
||||
- Displays the status of each file (Added, Modified, Deleted)
|
||||
- Allows viewing the diff of each file
|
||||
- Automatically refreshes the list of changes every 10 seconds
|
||||
- Opens automatically at VSCode startup
|
||||
|
||||
## Usage
|
||||
|
||||
The extension adds a new view container to the activity bar with a "Changes" icon. Click on it to see the list of changed files.
|
||||
|
||||
- Click on a file to view its diff
|
||||
- Click the refresh button to manually refresh the list of changes
|
||||
- Right-click on a file to see additional options
|
||||
|
||||
## Requirements
|
||||
|
||||
- VSCode 1.98.2 or higher
|
||||
- Git must be installed and available in the PATH
|
||||
|
||||
## Extension Settings
|
||||
|
||||
This extension does not contribute any settings.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- The diff view is basic and does not support all the features of the built-in diff editor
|
||||
- The extension may not work correctly in workspaces without Git
|
||||
|
||||
## Release Notes
|
||||
|
||||
### 0.1.0
|
||||
|
||||
Initial release of OpenHands Changes Viewer
|
||||
@@ -0,0 +1,113 @@
|
||||
const vscode = require('vscode');
|
||||
|
||||
/**
|
||||
* TreeDataProvider for the Changes view
|
||||
*/
|
||||
class ChangesProvider {
|
||||
/**
|
||||
* @param {import('./git-service').GitService} gitService
|
||||
*/
|
||||
constructor(gitService) {
|
||||
this.gitService = gitService;
|
||||
this._onDidChangeTreeData = new vscode.EventEmitter();
|
||||
this.onDidChangeTreeData = this._onDidChangeTreeData.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the tree view
|
||||
*/
|
||||
refresh() {
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tree item for the given element
|
||||
* @param {any} element
|
||||
* @returns {vscode.TreeItem}
|
||||
*/
|
||||
getTreeItem(element) {
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the children of the given element
|
||||
* @param {any} element
|
||||
* @returns {Promise<vscode.TreeItem[]>}
|
||||
*/
|
||||
async getChildren(element) {
|
||||
if (element) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const changes = await this.gitService.getChanges();
|
||||
|
||||
if (!changes || changes.length === 0) {
|
||||
return [new vscode.TreeItem('No changes detected', vscode.TreeItemCollapsibleState.None)];
|
||||
}
|
||||
|
||||
return changes.map(change => {
|
||||
const { path, status } = change;
|
||||
|
||||
// Create a tree item for each file
|
||||
const treeItem = new vscode.TreeItem(path, vscode.TreeItemCollapsibleState.None);
|
||||
|
||||
// Set the context value to 'file' so we can use it in the when clause
|
||||
treeItem.contextValue = 'file';
|
||||
|
||||
// Store the file path and status in the tree item
|
||||
treeItem.id = path;
|
||||
treeItem.tooltip = `${this.getStatusLabel(status)}: ${path}`;
|
||||
treeItem.command = {
|
||||
command: 'openhands-changes-viewer.viewDiff',
|
||||
title: 'View Diff',
|
||||
arguments: [change]
|
||||
};
|
||||
|
||||
// Set the icon based on the status
|
||||
treeItem.iconPath = this.getIconForStatus(status);
|
||||
|
||||
return treeItem;
|
||||
});
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(`Failed to get changes: ${error.message}`);
|
||||
return [new vscode.TreeItem(`Error: ${error.message}`, vscode.TreeItemCollapsibleState.None)];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable label for a git status
|
||||
* @param {string} status
|
||||
* @returns {string}
|
||||
*/
|
||||
getStatusLabel(status) {
|
||||
const statusMap = {
|
||||
'M': 'Modified',
|
||||
'A': 'Added',
|
||||
'D': 'Deleted',
|
||||
'R': 'Renamed',
|
||||
'U': 'Untracked'
|
||||
};
|
||||
|
||||
return statusMap[status] || status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the icon for a git status
|
||||
* @param {string} status
|
||||
* @returns {vscode.ThemeIcon}
|
||||
*/
|
||||
getIconForStatus(status) {
|
||||
const iconMap = {
|
||||
'M': new vscode.ThemeIcon('edit'),
|
||||
'A': new vscode.ThemeIcon('add'),
|
||||
'D': new vscode.ThemeIcon('trash'),
|
||||
'R': new vscode.ThemeIcon('arrow-right'),
|
||||
'U': new vscode.ThemeIcon('question')
|
||||
};
|
||||
|
||||
return iconMap[status] || new vscode.ThemeIcon('file');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ChangesProvider };
|
||||
@@ -0,0 +1,200 @@
|
||||
const vscode = require('vscode');
|
||||
const { ChangesProvider } = require('./changes-provider');
|
||||
const { GitService } = require('./git-service');
|
||||
|
||||
/**
|
||||
* @param {vscode.ExtensionContext} context
|
||||
*/
|
||||
function activate(context) {
|
||||
console.log('OpenHands Changes Viewer is now active');
|
||||
|
||||
// Initialize the Git service
|
||||
const gitService = new GitService();
|
||||
|
||||
// Create the tree data provider for the changes view
|
||||
const changesProvider = new ChangesProvider(gitService);
|
||||
|
||||
// Register the tree data provider for the changes view
|
||||
const treeView = vscode.window.createTreeView('openhands-changes-view', {
|
||||
treeDataProvider: changesProvider,
|
||||
showCollapseAll: true
|
||||
});
|
||||
|
||||
// Register the refresh command
|
||||
let refreshCommand = vscode.commands.registerCommand('openhands-changes-viewer.refreshChanges', () => {
|
||||
changesProvider.refresh();
|
||||
});
|
||||
|
||||
// Register the view diff command
|
||||
let viewDiffCommand = vscode.commands.registerCommand('openhands-changes-viewer.viewDiff', async (fileItem) => {
|
||||
if (fileItem) {
|
||||
const { path, status } = fileItem;
|
||||
|
||||
try {
|
||||
// Show a progress notification
|
||||
await vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: `Loading diff for ${path}`,
|
||||
cancellable: false
|
||||
}, async (progress) => {
|
||||
try {
|
||||
progress.report({ increment: 30, message: "Fetching file content..." });
|
||||
const diff = await gitService.getDiff(path);
|
||||
|
||||
progress.report({ increment: 30, message: "Processing diff..." });
|
||||
|
||||
// Create a temporary file for the diff
|
||||
// Use a safe filename for the URI
|
||||
const safeFileName = path.replace(/[\\/:*?"<>|]/g, '_');
|
||||
const uri = vscode.Uri.parse(`untitled:${safeFileName}.diff`);
|
||||
|
||||
// Determine if this is a new file, deleted file, or modified file
|
||||
let content = '';
|
||||
|
||||
if (status === 'A') {
|
||||
// New file - show only the new content
|
||||
content = diff.modified || '(Empty file)';
|
||||
} else if (status === 'D') {
|
||||
// Deleted file - show only the original content
|
||||
content = diff.original || '(Empty file)';
|
||||
} else {
|
||||
// For modified files, show a diff header
|
||||
content = `--- a/${path}\n+++ b/${path}\n\n${diff.original || '(Empty file)'}\n\n=== Modified ===\n\n${diff.modified || '(Empty file)'}`;
|
||||
}
|
||||
|
||||
progress.report({ increment: 20, message: "Creating document..." });
|
||||
|
||||
// Create a new document with the diff content
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
edit.createFile(uri, { overwrite: true });
|
||||
await vscode.workspace.applyEdit(edit);
|
||||
|
||||
const document = await vscode.workspace.openTextDocument(uri);
|
||||
const editor = await vscode.window.showTextDocument(document);
|
||||
|
||||
progress.report({ increment: 20, message: "Applying content..." });
|
||||
|
||||
// Insert the diff content
|
||||
const fullRange = new vscode.Range(
|
||||
0, 0,
|
||||
document.lineCount, 0
|
||||
);
|
||||
|
||||
await editor.edit(editBuilder => {
|
||||
editBuilder.replace(fullRange, content);
|
||||
});
|
||||
|
||||
// Set the language mode to help with syntax highlighting
|
||||
await vscode.languages.setTextDocumentLanguage(document, getLanguageFromPath(path));
|
||||
} catch (error) {
|
||||
throw error; // Re-throw to be caught by the outer try-catch
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error showing diff:', error);
|
||||
vscode.window.showErrorMessage(`Failed to show diff for ${path}: ${error.message}`);
|
||||
}
|
||||
} else {
|
||||
vscode.window.showErrorMessage('No file selected to view diff');
|
||||
}
|
||||
});
|
||||
|
||||
// Register the open changes view command
|
||||
let openChangesViewCommand = vscode.commands.registerCommand('openhands-changes-viewer.openChangesView', () => {
|
||||
vscode.commands.executeCommand('workbench.view.extension.openhands-changes');
|
||||
});
|
||||
|
||||
// Open the changes view when the extension is activated
|
||||
vscode.commands.executeCommand('workbench.view.extension.openhands-changes');
|
||||
|
||||
// Add commands to subscriptions
|
||||
context.subscriptions.push(refreshCommand);
|
||||
context.subscriptions.push(viewDiffCommand);
|
||||
context.subscriptions.push(openChangesViewCommand);
|
||||
context.subscriptions.push(treeView);
|
||||
|
||||
// Auto-refresh the changes view every 10 seconds
|
||||
const intervalId = setInterval(() => {
|
||||
changesProvider.refresh();
|
||||
}, 10000);
|
||||
|
||||
// Clean up the interval when the extension is deactivated
|
||||
context.subscriptions.push({ dispose: () => clearInterval(intervalId) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the language ID from a file path for syntax highlighting
|
||||
* @param {string} path
|
||||
* @returns {string}
|
||||
*/
|
||||
function getLanguageFromPath(path) {
|
||||
// Handle files without extensions or with special names
|
||||
if (!path.includes('.')) {
|
||||
// Check for common files without extensions
|
||||
const filename = path.split('/').pop().toLowerCase();
|
||||
if (['makefile', 'dockerfile', 'jenkinsfile'].includes(filename)) {
|
||||
return filename.toLowerCase();
|
||||
}
|
||||
if (filename === 'readme') return 'markdown';
|
||||
return 'plaintext';
|
||||
}
|
||||
|
||||
const extension = path.split('.').pop().toLowerCase();
|
||||
|
||||
const extensionMap = {
|
||||
'js': 'javascript',
|
||||
'ts': 'typescript',
|
||||
'tsx': 'typescriptreact',
|
||||
'jsx': 'javascriptreact',
|
||||
'py': 'python',
|
||||
'java': 'java',
|
||||
'c': 'c',
|
||||
'cpp': 'cpp',
|
||||
'h': 'c',
|
||||
'hpp': 'cpp',
|
||||
'cs': 'csharp',
|
||||
'go': 'go',
|
||||
'rs': 'rust',
|
||||
'php': 'php',
|
||||
'rb': 'ruby',
|
||||
'md': 'markdown',
|
||||
'json': 'json',
|
||||
'html': 'html',
|
||||
'htm': 'html',
|
||||
'css': 'css',
|
||||
'scss': 'scss',
|
||||
'less': 'less',
|
||||
'xml': 'xml',
|
||||
'yaml': 'yaml',
|
||||
'yml': 'yaml',
|
||||
'sh': 'shellscript',
|
||||
'bash': 'shellscript',
|
||||
'zsh': 'shellscript',
|
||||
'txt': 'plaintext',
|
||||
'sql': 'sql',
|
||||
'swift': 'swift',
|
||||
'kt': 'kotlin',
|
||||
'dart': 'dart',
|
||||
'vue': 'vue',
|
||||
'svelte': 'svelte',
|
||||
'tf': 'terraform',
|
||||
'tfvars': 'terraform',
|
||||
'graphql': 'graphql',
|
||||
'gql': 'graphql',
|
||||
'toml': 'toml',
|
||||
'ini': 'ini',
|
||||
'bat': 'bat',
|
||||
'ps1': 'powershell'
|
||||
};
|
||||
|
||||
return extensionMap[extension] || 'plaintext';
|
||||
}
|
||||
|
||||
function deactivate() {
|
||||
console.log('OpenHands Changes Viewer is now deactivated');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
activate,
|
||||
deactivate
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
const vscode = require('vscode');
|
||||
const { exec } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/**
|
||||
* Service for interacting with Git
|
||||
*/
|
||||
class GitService {
|
||||
constructor() {
|
||||
this.workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of changed files
|
||||
* @returns {Promise<Array<{path: string, status: string}>>}
|
||||
*/
|
||||
async getChanges() {
|
||||
if (!this.workspaceRoot) {
|
||||
throw new Error('No workspace folder is open');
|
||||
}
|
||||
|
||||
try {
|
||||
// Run git status to get the list of changed files
|
||||
// Use -z to handle filenames with special characters
|
||||
const { stdout } = await execAsync('git status --porcelain -z', { cwd: this.workspaceRoot });
|
||||
|
||||
if (!stdout) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Parse the output of git status with null-terminated lines
|
||||
const changes = [];
|
||||
const entries = stdout.split('\0');
|
||||
|
||||
for (let i = 0; i < entries.length - 1; i++) {
|
||||
const entry = entries[i];
|
||||
if (entry.length >= 2) {
|
||||
const status = entry.substring(0, 2).trim();
|
||||
const path = entry.substring(3);
|
||||
|
||||
// Skip empty paths
|
||||
if (!path) continue;
|
||||
|
||||
// Map the git status to our status codes
|
||||
let mappedStatus = 'M'; // Default to modified
|
||||
|
||||
if (status.includes('A') || status.includes('?')) {
|
||||
mappedStatus = 'A'; // Added or untracked
|
||||
} else if (status.includes('D')) {
|
||||
mappedStatus = 'D'; // Deleted
|
||||
} else if (status.includes('R')) {
|
||||
mappedStatus = 'R'; // Renamed
|
||||
}
|
||||
|
||||
changes.push({ path, status: mappedStatus });
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
} catch (error) {
|
||||
console.error('Error getting git changes:', error);
|
||||
throw new Error(`Failed to get git changes: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the diff for a file
|
||||
* @param {string} filePath
|
||||
* @returns {Promise<{original: string, modified: string}>}
|
||||
*/
|
||||
async getDiff(filePath) {
|
||||
if (!this.workspaceRoot) {
|
||||
throw new Error('No workspace folder is open');
|
||||
}
|
||||
|
||||
try {
|
||||
// Properly escape the file path for shell commands
|
||||
const escapedPath = filePath.replace(/'/g, "'\\''");
|
||||
|
||||
// Check if the file exists in the index
|
||||
const { stdout: fileStatus } = await execAsync(`git status --porcelain -- '${escapedPath}'`, {
|
||||
cwd: this.workspaceRoot
|
||||
});
|
||||
|
||||
const status = fileStatus.substring(0, 2).trim();
|
||||
let original = '';
|
||||
let modified = '';
|
||||
|
||||
if (status.includes('?') || status.includes('A')) {
|
||||
// New file - get the current content
|
||||
try {
|
||||
const { stdout: currentContent } = await execAsync(`cat '${escapedPath}'`, {
|
||||
cwd: this.workspaceRoot
|
||||
});
|
||||
original = '';
|
||||
modified = currentContent;
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Could not read new file: ${error.message}`);
|
||||
original = '';
|
||||
modified = '';
|
||||
}
|
||||
} else if (status.includes('D')) {
|
||||
// Deleted file - get the content from the index
|
||||
try {
|
||||
// Use a more reliable way to get the file from git
|
||||
const { stdout: indexContent } = await execAsync(`git show HEAD:'${escapedPath}'`, {
|
||||
cwd: this.workspaceRoot
|
||||
});
|
||||
original = indexContent;
|
||||
modified = '';
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Could not get deleted file content: ${error.message}`);
|
||||
original = '';
|
||||
modified = '';
|
||||
}
|
||||
} else {
|
||||
// Modified file - get both versions
|
||||
try {
|
||||
// Get the content from the index
|
||||
const { stdout: indexContent } = await execAsync(`git show HEAD:'${escapedPath}'`, {
|
||||
cwd: this.workspaceRoot
|
||||
});
|
||||
original = indexContent;
|
||||
} catch (error) {
|
||||
// File might not exist in the index yet
|
||||
console.warn(`Warning: Could not get original file content: ${error.message}`);
|
||||
original = '';
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the current content
|
||||
const { stdout: currentContent } = await execAsync(`cat '${escapedPath}'`, {
|
||||
cwd: this.workspaceRoot
|
||||
});
|
||||
modified = currentContent;
|
||||
} catch (error) {
|
||||
// File might not exist in the workspace
|
||||
console.warn(`Warning: Could not get modified file content: ${error.message}`);
|
||||
modified = '';
|
||||
}
|
||||
}
|
||||
|
||||
return { original, modified };
|
||||
} catch (error) {
|
||||
console.error('Error getting diff:', error);
|
||||
throw new Error(`Failed to get diff: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { GitService };
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "openhands-changes-viewer",
|
||||
"displayName": "OpenHands Changes Viewer",
|
||||
"description": "A VSCode extension for viewing changes made by the OpenHands agent",
|
||||
"version": "0.1.0",
|
||||
"publisher": "openhands",
|
||||
"engines": {
|
||||
"vscode": "^1.98.2"
|
||||
},
|
||||
"categories": [
|
||||
"Other",
|
||||
"SCM Providers"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onStartupFinished",
|
||||
"onCommand:openhands-changes-viewer.openChangesView"
|
||||
],
|
||||
"main": "./extension.js",
|
||||
"contributes": {
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "openhands-changes",
|
||||
"title": "OpenHands Changes",
|
||||
"icon": "resources/changes.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"openhands-changes": [
|
||||
{
|
||||
"id": "openhands-changes-view",
|
||||
"name": "Changes",
|
||||
"when": "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "openhands-changes-viewer.refreshChanges",
|
||||
"title": "Refresh Changes",
|
||||
"icon": "$(refresh)"
|
||||
},
|
||||
{
|
||||
"command": "openhands-changes-viewer.viewDiff",
|
||||
"title": "View Diff"
|
||||
},
|
||||
{
|
||||
"command": "openhands-changes-viewer.openChangesView",
|
||||
"title": "OpenHands: Show Changes View",
|
||||
"category": "OpenHands"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"view/title": [
|
||||
{
|
||||
"command": "openhands-changes-viewer.refreshChanges",
|
||||
"when": "view == openhands-changes-view",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "openhands-changes-viewer.viewDiff",
|
||||
"when": "view == openhands-changes-view && viewItem == file",
|
||||
"group": "inline"
|
||||
}
|
||||
]
|
||||
},
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "openhands-changes-viewer.openChangesView",
|
||||
"key": "ctrl+shift+c",
|
||||
"mac": "cmd+shift+c",
|
||||
"when": "editorTextFocus"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.5 1H2.5C1.67157 1 1 1.67157 1 2.5V13.5C1 14.3284 1.67157 15 2.5 15H13.5C14.3284 15 15 14.3284 15 13.5V2.5C15 1.67157 14.3284 1 13.5 1Z" stroke="#C5C5C5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 4H12" stroke="#C5C5C5" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M4 8H12" stroke="#C5C5C5" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M4 12H8" stroke="#C5C5C5" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M11 11L13 13M13 11L11 13" stroke="#C5C5C5" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 718 B |
@@ -0,0 +1,22 @@
|
||||
const { GitService } = require('./git-service');
|
||||
|
||||
async function testGitService() {
|
||||
const gitService = new GitService();
|
||||
|
||||
try {
|
||||
console.log('Getting changes...');
|
||||
const changes = await gitService.getChanges();
|
||||
console.log('Changes:', changes);
|
||||
|
||||
if (changes.length > 0) {
|
||||
const firstChange = changes[0];
|
||||
console.log(`Getting diff for ${firstChange.path}...`);
|
||||
const diff = await gitService.getDiff(firstChange.path);
|
||||
console.log('Diff:', diff);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
testGitService();
|
||||
Reference in New Issue
Block a user