diff --git a/package.json b/package.json index 9487e5b..df44389 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "wizgit-in-vscode", "displayName": "WizGIT in VS Code", "description": "A VS Code extension to interact with WizGIT API for repository management.", - "version": "0.0.3", - "publisher": "TerenceCarrera", + "version": "0.0.4", + "publisher": "Terence Carrera", "icon": "resources/wizgit-marketplace-icon.png", "license": "MIT", "repository": { @@ -105,6 +105,48 @@ "title": "Open Pull Request", "category": "WizGIT", "icon": "$(link-external)" + }, + { + "command": "wizgit-in-vscode.searchRepositories", + "title": "Search Repositories", + "category": "WizGIT", + "icon": "$(telescope)" + }, + { + "command": "wizgit-in-vscode.searchIssues", + "title": "Search Issues", + "category": "WizGIT", + "icon": "$(search)" + }, + { + "command": "wizgit-in-vscode.searchPullRequests", + "title": "Search Pull Requests", + "category": "WizGIT", + "icon": "$(search)" + }, + { + "command": "wizgit-in-vscode.openRecentRepositories", + "title": "Open Recent Repositories", + "category": "WizGIT", + "icon": "$(history)" + }, + { + "command": "wizgit-in-vscode.exportIssues", + "title": "Export Issues", + "category": "WizGIT", + "icon": "$(save)" + }, + { + "command": "wizgit-in-vscode.exportPullRequests", + "title": "Export Pull Requests", + "category": "WizGIT", + "icon": "$(save)" + }, + { + "command": "wizgit-in-vscode.showNotifications", + "title": "Show Notifications", + "category": "WizGIT", + "icon": "$(bell)" } ], "menus": { @@ -159,6 +201,32 @@ } ] }, + "keybindings": [ + { + "command": "wizgit-in-vscode.searchRepositories", + "key": "ctrl+shift+g r", + "mac": "cmd+shift+g r", + "when": "wizgit:enabled" + }, + { + "command": "wizgit-in-vscode.searchIssues", + "key": "ctrl+shift+g i", + "mac": "cmd+shift+g i", + "when": "wizgit:enabled" + }, + { + "command": "wizgit-in-vscode.searchPullRequests", + "key": "ctrl+shift+g p", + "mac": "cmd+shift+g p", + "when": "wizgit:enabled" + }, + { + "command": "wizgit-in-vscode.openRecentRepositories", + "key": "ctrl+shift+g t", + "mac": "cmd+shift+g t", + "when": "wizgit:enabled" + } + ], "configuration": { "title": "WizGIT", "properties": { diff --git a/src/extension.ts b/src/extension.ts index 4e0b1aa..0fb76bc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,6 +5,13 @@ import * as fs from 'fs'; import * as child_process from 'child_process'; import { promisify } from 'util'; +// Import new feature modules +import { searchRepositories, searchIssues, searchPullRequests, showFilterQuickPick } from './features/search'; +import { showRepositoryQuickPick, addToRecent } from './features/favorites'; +import { createStatusBarItem, registerStatusBarClickHandler } from './features/statusBar'; +import { getUnreadNotifications, showNotificationModal } from './features/notifications'; +import { exportIssues, exportPullRequests, showExportDialog, saveToFile } from './features/export'; + const execAsync = promisify(child_process.exec); // Interfaces for API responses @@ -838,6 +845,322 @@ export function activate(context: vscode.ExtensionContext) { vscode.window.showInformationMessage('WizGIT credentials cleared successfully.'); }); + // Register Search Repositories command + let searchReposDisposable = vscode.commands.registerCommand('wizgit-in-vscode.searchRepositories', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + const query = await vscode.window.showInputBox({ + prompt: 'Search repositories', + ignoreFocusOut: true + }); + + if (query) { + try { + const result = await searchRepositories(credentials.apiEndpoint, credentials.token, query); + const repos = result.data || []; + + if (repos.length === 0) { + vscode.window.showInformationMessage('No repositories found'); + return; + } + + const selected = await vscode.window.showQuickPick( + repos.map((r: any) => ({ + label: r.name, + description: r.full_name, + detail: r.description, + data: r + })), + { placeHolder: 'Select a repository' } + ) as any; + + if (selected) { + await addToRecent(context, `${selected.data.id}`, selected.data.name, + selected.data.full_name.split('/')[0], selected.data.full_name.split('/')[1]); + vscode.window.showInformationMessage(`Selected: ${selected.data.full_name}`); + + // Prompt user to open in VS Code or Browser + const action = await vscode.window.showInformationMessage( + `Open repository ${selected.data.name} in:`, + 'VS Code', + 'Browser' + ); + + if (action === 'VS Code') { + await cloneAndOpenRepository(selected.data.clone_url, selected.data.name); + } else if (action === 'Browser') { + vscode.env.openExternal(vscode.Uri.parse(selected.data.html_url)); + } + } + } catch (error) { + vscode.window.showErrorMessage(`Search failed: ${error}`); + } + } + }); + + // Register Search Issues command + let searchIssuesDisposable = vscode.commands.registerCommand('wizgit-in-vscode.searchIssues', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + const repoInfo = await getWorkspaceRepoInfo(); + if (!repoInfo) { + vscode.window.showErrorMessage('No WizGIT repository detected in workspace'); + return; + } + + try { + const filterOptions = await showFilterQuickPick(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, 'issues'); + + const searchQuery = filterOptions?.state ? `state:${filterOptions.state}` : ''; + + const result = await searchIssues(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, { + query: searchQuery, + ...filterOptions, + page: 1, + limit: 50 + }); + + const issues = result.data || []; + if (issues.length === 0) { + vscode.window.showInformationMessage('No issues found'); + return; + } + + const selected = await vscode.window.showQuickPick( + issues.map((i: any) => ({ + label: `#${i.number} - ${i.title}`, + description: i.state, + detail: i.body?.substring(0, 50), + data: i + })), + { placeHolder: 'Select an issue' } + ) as any; + + if (selected) { + await openIssueViewer(context, selected.data, `${repoInfo.owner}/${repoInfo.repo}`); + } + } catch (error) { + vscode.window.showErrorMessage(`Search failed: ${error}`); + } + }); + + // Register Search Pull Requests command + let searchPRsDisposable = vscode.commands.registerCommand('wizgit-in-vscode.searchPullRequests', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + const repoInfo = await getWorkspaceRepoInfo(); + if (!repoInfo) { + vscode.window.showErrorMessage('No WizGIT repository detected in workspace'); + return; + } + + try { + const filterOptions = await showFilterQuickPick(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, 'pulls'); + + const result = await searchPullRequests(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, { + query: '', + ...filterOptions, + page: 1, + limit: 50 + }); + + const prs = result.data || []; + if (prs.length === 0) { + vscode.window.showInformationMessage('No pull requests found'); + return; + } + + const selected = await vscode.window.showQuickPick( + prs.map((p: any) => ({ + label: `#${p.number} - ${p.title}`, + description: p.state, + detail: `${p.head.ref} → ${p.base.ref}`, + data: p + })), + { placeHolder: 'Select a pull request' } + ) as any; + + if (selected) { + await openPullRequestViewer(context, selected.data, `${repoInfo.owner}/${repoInfo.repo}`); + } + } catch (error) { + vscode.window.showErrorMessage(`Search failed: ${error}`); + } + }); + + // Register Open Recent Repositories command + let recentReposDisposable = vscode.commands.registerCommand('wizgit-in-vscode.openRecentRepositories', async () => { + const selected = await showRepositoryQuickPick(context); + if (selected) { + // Prompt user to open in VS Code or Browser + const action = await vscode.window.showInformationMessage( + `Open repository ${selected.name} in:`, + 'VS Code', + 'Browser' + ); + if (action === 'VS Code' && selected.cloneUrl) { + // Clone the repository first, then open it + await cloneAndOpenRepository(selected.cloneUrl, selected.name); + } else if (action === 'Browser' && selected.htmlUrl) { + vscode.env.openExternal(vscode.Uri.parse(selected.htmlUrl)); + } + } + }); + + // Register Export Issues command + let exportIssuesDisposable = vscode.commands.registerCommand('wizgit-in-vscode.exportIssues', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + const repoInfo = await getWorkspaceRepoInfo(); + if (!repoInfo) { + vscode.window.showErrorMessage('No WizGIT repository detected in workspace'); + return; + } + + try { + const result = await searchIssues(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, { + query: '', + page: 1, + limit: 100 + }); + + const issues = result.data || []; + if (issues.length === 0) { + vscode.window.showInformationMessage('No issues found to export'); + return; + } + + const options = await showExportDialog('issues'); + if (!options) return; + + const content = await exportIssues(issues, options); + if (content) { + const filename = `issues-export-${repoInfo.repo}-${Date.now()}.${options.format}`; + await saveToFile(content, filename, options.format); + } + } catch (error) { + vscode.window.showErrorMessage(`Export failed: ${error}`); + } + }); + + // Register Export Pull Requests command + let exportPRsDisposable = vscode.commands.registerCommand('wizgit-in-vscode.exportPullRequests', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + const repoInfo = await getWorkspaceRepoInfo(); + if (!repoInfo) { + vscode.window.showErrorMessage('No WizGIT repository detected in workspace'); + return; + } + + try { + const result = await searchPullRequests(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, { + query: '', + page: 1, + limit: 100 + }); + + const prs = result.data || []; + if (prs.length === 0) { + vscode.window.showInformationMessage('No pull requests found to export'); + return; + } + + const options = await showExportDialog('pullRequests'); + if (!options) return; + + const content = await exportPullRequests(prs, options); + if (content) { + const filename = `prs-export-${repoInfo.repo}-${Date.now()}.${options.format}`; + await saveToFile(content, filename, options.format); + } + } catch (error) { + vscode.window.showErrorMessage(`Export failed: ${error}`); + } + }); + + // Register Show Notifications command + let notificationsDisposable = vscode.commands.registerCommand('wizgit-in-vscode.showNotifications', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + try { + const notifications = await getUnreadNotifications(credentials.apiEndpoint, credentials.token); + + if (notifications.length === 0) { + vscode.window.showInformationMessage('No unread notifications'); + return; + } + + const selected = await vscode.window.showQuickPick( + notifications.map(n => ({ + label: `${n.subject.title}`, + description: n.repository.name, + detail: n.reason, + data: n + })), + { placeHolder: 'Select a notification' } + ); + + if (selected) { + await showNotificationModal(selected.data); + } + } catch (error) { + vscode.window.showErrorMessage(`Failed to fetch notifications: ${error}`); + } + }); + + // Create and register status bar (subscriptions handled by registerStatusBarClickHandler) + createStatusBarItem(context); + registerStatusBarClickHandler(context, async (action) => { + switch (action) { + case 'search-repos': + await vscode.commands.executeCommand('wizgit-in-vscode.searchRepositories'); + break; + case 'search-issues': + await vscode.commands.executeCommand('wizgit-in-vscode.searchIssues'); + break; + case 'search-prs': + await vscode.commands.executeCommand('wizgit-in-vscode.searchPullRequests'); + break; + case 'recent-repos': + await vscode.commands.executeCommand('wizgit-in-vscode.openRecentRepositories'); + break; + case 'configure': + await vscode.commands.executeCommand('wizgit-in-vscode.configure'); + break; + } + }); + context.subscriptions.push( disposable, createIssueDisposable, @@ -847,7 +1170,14 @@ export function activate(context: vscode.ExtensionContext) { refreshDisposable, cloneDisposable, openIssueDisposable, - openPRDisposable + openPRDisposable, + searchReposDisposable, + searchIssuesDisposable, + searchPRsDisposable, + recentReposDisposable, + exportIssuesDisposable, + exportPRsDisposable, + notificationsDisposable ); } @@ -1011,6 +1341,8 @@ async function createIssueWebview(context: vscode.ExtensionContext) { panel.webview.html = getIssueWebviewContent(credentials, repoInfo, branches); panel.webview.onDidReceiveMessage(async message => { + console.log('Issue webview received message:', message.command, message.data); + switch (message.command) { case 'createIssue': await handleCreateIssue(message.data, panel, context); @@ -1025,6 +1357,11 @@ async function createIssueWebview(context: vscode.ExtensionContext) { ]); panel.webview.html = getIssueWebviewContent(updatedCredentials, updatedRepoInfo, updatedBranches); break; + case 'openUrl': + if (message.url) { + vscode.env.openExternal(vscode.Uri.parse(message.url)); + } + break; } }, undefined, context.subscriptions); } @@ -1168,11 +1505,8 @@ function getIssueWebviewContent(credentials?: { apiEndpoint: string, token: stri