feat: Add export functionality for issues and pull requests

- Implemented exportIssues and exportPullRequests functions to export data in markdown, CSV, and JSON formats.
- Created helper functions for generating markdown and CSV formats for issues and pull requests.
- Added showExportDialog for user interaction to select export options.
- Implemented saveToFile function to handle file saving.

feat: Implement favorites and recent repositories management

- Added functionality to add, remove, and check favorites for repositories.
- Implemented recent repositories tracking with a limit on the number of recent entries.
- Created a quick pick interface to show favorites and recent repositories.

feat: Introduce notifications management

- Implemented functions to fetch, mark as read, and manage notifications.
- Created a NotificationProvider class to display notifications in a tree view.
- Added functionality to show notifications in a modal with quick actions.

feat: Implement search functionality for repositories, issues, and pull requests

- Added searchRepositories, searchIssues, and searchPullRequests functions with filtering options.
- Implemented getLabels and getCollaborators functions for additional filtering capabilities.
- Created a showFilterQuickPick function for user interaction to select filters.

feat: Enhance status bar with shortcuts and dynamic updates

- Added default keyboard shortcuts for various commands.
- Implemented createStatusBarItem and updateStatusBar functions to manage status bar display.
- Created a showStatusMenu function for quick actions related to the extension.
This commit is contained in:
2025-12-17 16:06:52 +08:00
parent 4b67c7f14a
commit 239ddbd362
7 changed files with 1756 additions and 70 deletions

229
src/features/export.ts Normal file
View File

@@ -0,0 +1,229 @@
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
export interface ExportOptions {
format: 'markdown' | 'csv' | 'json';
includeDetails: boolean;
includeComments: boolean;
filename?: string;
}
/**
* Export issues to file
*/
export async function exportIssues(
issues: any[],
options: ExportOptions
): Promise<string | null> {
try {
let content = '';
if (options.format === 'markdown') {
content = generateMarkdownIssues(issues, options.includeDetails, options.includeComments);
} else if (options.format === 'csv') {
content = generateCSVIssues(issues);
} else if (options.format === 'json') {
content = JSON.stringify(issues, null, 2);
}
return content;
} catch (error) {
console.error('Export issues error:', error);
return null;
}
}
/**
* Export pull requests to file
*/
export async function exportPullRequests(
pullRequests: any[],
options: ExportOptions
): Promise<string | null> {
try {
let content = '';
if (options.format === 'markdown') {
content = generateMarkdownPullRequests(pullRequests, options.includeDetails);
} else if (options.format === 'csv') {
content = generateCSVPullRequests(pullRequests);
} else if (options.format === 'json') {
content = JSON.stringify(pullRequests, null, 2);
}
return content;
} catch (error) {
console.error('Export PRs error:', error);
return null;
}
}
/**
* Generate markdown format for issues
*/
function generateMarkdownIssues(
issues: any[],
includeDetails: boolean = false,
_includeComments: boolean = false
): string {
let markdown = '# Issues Export\n\n';
markdown += `Generated: ${new Date().toISOString()}\n\n`;
issues.forEach(issue => {
markdown += `## #${issue.number} - ${issue.title}\n\n`;
markdown += `**State:** ${issue.state}\n`;
markdown += `**Author:** ${issue.user?.login || 'Unknown'}\n`;
if (issue.assignees && issue.assignees.length > 0) {
markdown += `**Assignees:** ${issue.assignees.map((a: any) => a.login).join(', ')}\n`;
}
if (issue.labels && issue.labels.length > 0) {
markdown += `**Labels:** ${issue.labels.map((l: any) => l.name).join(', ')}\n`;
}
markdown += `**Created:** ${new Date(issue.created_at).toLocaleDateString()}\n`;
if (includeDetails && issue.body) {
markdown += `\n### Description\n\n${issue.body}\n`;
}
markdown += `\n[View on WizGIT](${issue.html_url})\n\n---\n\n`;
});
return markdown;
}
/**
* Generate markdown format for pull requests
*/
function generateMarkdownPullRequests(
pullRequests: any[],
includeDetails: boolean = false
): string {
let markdown = '# Pull Requests Export\n\n';
markdown += `Generated: ${new Date().toISOString()}\n\n`;
pullRequests.forEach(pr => {
markdown += `## #${pr.number} - ${pr.title}\n\n`;
markdown += `**State:** ${pr.state}\n`;
markdown += `**Author:** ${pr.user?.login || 'Unknown'}\n`;
markdown += `**Base:** ${pr.base?.ref || 'Unknown'} ← **Head:** ${pr.head?.ref || 'Unknown'}\n`;
markdown += `**Mergeable:** ${pr.mergeable ? 'Yes' : 'No'}\n`;
markdown += `**Created:** ${new Date(pr.created_at).toLocaleDateString()}\n`;
if (includeDetails && pr.body) {
markdown += `\n### Description\n\n${pr.body}\n`;
}
markdown += `\n[View on WizGIT](${pr.html_url})\n\n---\n\n`;
});
return markdown;
}
/**
* Generate CSV format for issues
*/
function generateCSVIssues(issues: any[]): string {
const headers = ['ID', 'Number', 'Title', 'State', 'Author', 'Assignees', 'Labels', 'Created', 'Updated'];
const rows = issues.map(issue => [
issue.id,
issue.number,
`"${issue.title}"`,
issue.state,
issue.user?.login || '',
issue.assignees?.map((a: any) => a.login).join(';') || '',
issue.labels?.map((l: any) => l.name).join(';') || '',
new Date(issue.created_at).toLocaleDateString(),
new Date(issue.updated_at).toLocaleDateString()
]);
return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
}
/**
* Generate CSV format for pull requests
*/
function generateCSVPullRequests(pullRequests: any[]): string {
const headers = ['ID', 'Number', 'Title', 'State', 'Author', 'Base', 'Head', 'Mergeable', 'Created'];
const rows = pullRequests.map(pr => [
pr.id,
pr.number,
`"${pr.title}"`,
pr.state,
pr.user?.login || '',
pr.base?.ref || '',
pr.head?.ref || '',
pr.mergeable ? 'Yes' : 'No',
new Date(pr.created_at).toLocaleDateString()
]);
return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
}
/**
* Show export dialog and save file
*/
export async function showExportDialog(dataType: 'issues' | 'pullRequests'): Promise<ExportOptions | null> {
const formatOptions = ['Markdown', 'CSV', 'JSON'];
const selectedFormat = await vscode.window.showQuickPick(
formatOptions,
{ placeHolder: 'Select export format' }
);
if (!selectedFormat) return null;
const includeDetailsResponse = await vscode.window.showQuickPick(
['Yes', 'No'],
{ placeHolder: 'Include full details/descriptions?' }
);
const includeDetails = includeDetailsResponse === 'Yes';
let _includeComments = false;
if (dataType === 'issues') {
const includeCommentsResponse = await vscode.window.showQuickPick(
['Yes', 'No'],
{ placeHolder: 'Include comments?' }
);
_includeComments = includeCommentsResponse === 'Yes';
}
return {
format: selectedFormat.toLowerCase() as 'markdown' | 'csv' | 'json',
includeDetails,
includeComments: _includeComments
};
}
/**
* Save content to file
*/
export async function saveToFile(
content: string,
defaultFilename: string,
_format: string
): Promise<string | null> {
const uri = await vscode.window.showSaveDialog({
defaultUri: vscode.Uri.file(defaultFilename),
filters: {
'All Files': ['*'],
'Markdown': ['md'],
'CSV': ['csv'],
'JSON': ['json']
}
});
if (!uri) return null;
try {
fs.writeFileSync(uri.fsPath, content);
vscode.window.showInformationMessage(`Exported to ${path.basename(uri.fsPath)}`);
return uri.fsPath;
} catch (error) {
vscode.window.showErrorMessage(`Failed to save file: ${error}`);
return null;
}
}

172
src/features/favorites.ts Normal file
View File

@@ -0,0 +1,172 @@
import * as vscode from 'vscode';
export interface RepoReference {
id: string;
name: string;
owner: string;
repo: string;
cloneUrl?: string;
htmlUrl?: string;
timestamp: number;
lastAccessed?: number;
}
const FAVORITES_KEY = 'wizgit.favorites';
const RECENT_KEY = 'wizgit.recent';
const MAX_RECENT = 10;
/**
* Add a repository to favorites
*/
export async function addFavorite(
context: vscode.ExtensionContext,
repoId: string,
name: string,
owner: string,
repo: string
): Promise<void> {
const favorites = getFavorites(context);
if (!favorites.find(f => f.id === repoId)) {
favorites.push({
id: repoId,
name,
owner,
repo,
timestamp: Date.now()
});
await context.globalState.update(FAVORITES_KEY, favorites);
}
}
/**
* Remove a repository from favorites
*/
export async function removeFavorite(
context: vscode.ExtensionContext,
repoId: string
): Promise<void> {
const favorites = getFavorites(context);
const filtered = favorites.filter(f => f.id !== repoId);
await context.globalState.update(FAVORITES_KEY, filtered);
}
/**
* Get all favorites
*/
export function getFavorites(context: vscode.ExtensionContext): RepoReference[] {
return context.globalState.get(FAVORITES_KEY, []);
}
/**
* Check if a repository is favorited
*/
export function isFavorite(context: vscode.ExtensionContext, repoId: string): boolean {
return getFavorites(context).some(f => f.id === repoId);
}
/**
* Add a repository to recent list
*/
export async function addToRecent(
context: vscode.ExtensionContext,
repoId: string,
name: string,
owner: string,
repo: string
): Promise<void> {
const recent = getRecent(context);
// Remove if already exists
const filtered = recent.filter(r => r.id !== repoId);
// Add to beginning
filtered.unshift({
id: repoId,
name,
owner,
repo,
timestamp: Date.now(),
lastAccessed: Date.now()
});
// Keep only last MAX_RECENT
filtered.splice(MAX_RECENT);
await context.globalState.update(RECENT_KEY, filtered);
}
/**
* Get recent repositories
*/
export function getRecent(context: vscode.ExtensionContext): RepoReference[] {
return context.globalState.get(RECENT_KEY, []);
}
/**
* Clear recent repositories
*/
export async function clearRecent(context: vscode.ExtensionContext): Promise<void> {
await context.globalState.update(RECENT_KEY, []);
}
/**
* Show favorites and recent repos in a quick pick
*/
export async function showRepositoryQuickPick(context: vscode.ExtensionContext): Promise<RepoReference | undefined> {
const favorites = getFavorites(context);
const recent = getRecent(context);
const items: vscode.QuickPickItem[] = [];
if (favorites.length > 0) {
items.push({ label: '$(star-full) Favorites', kind: vscode.QuickPickItemKind.Separator });
items.push(...favorites.map(f => ({
label: `$(repo) ${f.name}`,
description: `${f.owner}/${f.repo}`,
detail: `Added ${new Date(f.timestamp).toLocaleDateString()}`,
data: f
})));
}
if (recent.length > 0) {
items.push({ label: '$(history) Recent', kind: vscode.QuickPickItemKind.Separator });
items.push(...recent.map(r => ({
label: `$(repo) ${r.name}`,
description: `${r.owner}/${r.repo}`,
detail: `Accessed ${new Date(r.lastAccessed || r.timestamp).toLocaleDateString()}`,
data: r
})));
}
if (items.length === 0) {
vscode.window.showInformationMessage('No favorites or recent repositories yet');
return undefined;
}
const selected = await vscode.window.showQuickPick(items, {
placeHolder: 'Select a repository'
});
return selected ? (selected as any).data : undefined;
}
/**
* Toggle favorite status for a repository
*/
export async function toggleFavorite(
context: vscode.ExtensionContext,
repoId: string,
name: string,
owner: string,
repo: string
): Promise<boolean> {
if (isFavorite(context, repoId)) {
await removeFavorite(context, repoId);
return false;
} else {
await addFavorite(context, repoId, name, owner, repo);
return true;
}
}

View File

@@ -0,0 +1,231 @@
import * as vscode from 'vscode';
import fetch from 'node-fetch';
export interface NotificationData {
id: number;
type: string;
repository: {
name: string;
full_name: string;
};
subject: {
title: string;
type: 'Issue' | 'PullRequest' | 'Commit';
url: string;
};
reason: string;
unread: boolean;
updated_at: string;
url: string;
}
/**
* Fetch notifications for the current user
*/
export async function getNotifications(
apiEndpoint: string,
token: string,
page: number = 1,
limit: number = 30
): Promise<NotificationData[]> {
try {
const url = new URL(`${apiEndpoint}/notifications`);
url.searchParams.append('page', page.toString());
url.searchParams.append('limit', limit.toString());
const response = await fetch(url.toString(), {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch notifications: ${response.statusText}`);
}
return (await response.json()) as NotificationData[];
} catch (error) {
console.error('Get notifications error:', error);
return [];
}
}
/**
* Fetch unread notifications only
*/
export async function getUnreadNotifications(
apiEndpoint: string,
token: string
): Promise<NotificationData[]> {
try {
const notifications = await getNotifications(apiEndpoint, token);
return notifications.filter(n => n.unread);
} catch (error) {
console.error('Get unread notifications error:', error);
return [];
}
}
/**
* Mark notification as read
*/
export async function markNotificationAsRead(
apiEndpoint: string,
token: string,
notificationId: number
): Promise<boolean> {
try {
const response = await fetch(`${apiEndpoint}/notifications/${notificationId}`, {
method: 'PATCH',
headers: {
'Authorization': `token ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ status: 'read' })
});
return response.ok;
} catch (error) {
console.error('Mark notification as read error:', error);
return false;
}
}
/**
* Mark all notifications as read
*/
export async function markAllNotificationsAsRead(
apiEndpoint: string,
token: string
): Promise<boolean> {
try {
const response = await fetch(`${apiEndpoint}/notifications`, {
method: 'PUT',
headers: {
'Authorization': `token ${token}`,
'Content-Type': 'application/json'
}
});
return response.ok;
} catch (error) {
console.error('Mark all notifications as read error:', error);
return false;
}
}
/**
* Get mentions (notifications mentioning the current user)
*/
export async function getMentions(
apiEndpoint: string,
token: string
): Promise<NotificationData[]> {
try {
const notifications = await getUnreadNotifications(apiEndpoint, token);
return notifications.filter(n =>
n.reason === 'mention' || n.reason === '@mention'
);
} catch (error) {
console.error('Get mentions error:', error);
return [];
}
}
/**
* Show notifications in a tree view
*/
export class NotificationProvider implements vscode.TreeDataProvider<NotificationItem> {
private _onDidChangeTreeData: vscode.EventEmitter<NotificationItem | undefined | null | void> =
new vscode.EventEmitter<NotificationItem | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<NotificationItem | undefined | null | void> =
this._onDidChangeTreeData.event;
private notifications: NotificationData[] = [];
private apiEndpoint: string = '';
private token: string = '';
constructor(_context: vscode.ExtensionContext) { }
setCredentials(apiEndpoint: string, token: string): void {
this.apiEndpoint = apiEndpoint;
this.token = token;
}
async refresh(): Promise<void> {
if (this.apiEndpoint && this.token) {
this.notifications = await getUnreadNotifications(this.apiEndpoint, this.token);
this._onDidChangeTreeData.fire();
}
}
getTreeItem(element: NotificationItem): vscode.TreeItem {
return element;
}
async getChildren(): Promise<NotificationItem[]> {
if (this.notifications.length === 0) {
return [new NotificationItem('No unread notifications', vscode.TreeItemCollapsibleState.None)];
}
return this.notifications.map(n =>
new NotificationItem(
`${n.subject.title}`,
vscode.TreeItemCollapsibleState.None,
{
title: 'Open Notification',
command: 'wizgit.openNotification',
arguments: [n]
},
n
)
);
}
}
export class NotificationItem extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly command?: vscode.Command,
public readonly data?: NotificationData
) {
super(label, collapsibleState);
if (data) {
this.description = `${data.repository.name}`;
this.iconPath = this.getIconForType(data.subject.type);
this.tooltip = `${data.reason}: ${data.subject.title}`;
}
}
private getIconForType(type: string): vscode.ThemeIcon {
switch (type) {
case 'Issue':
return new vscode.ThemeIcon('issues');
case 'PullRequest':
return new vscode.ThemeIcon('git-pull-request');
case 'Commit':
return new vscode.ThemeIcon('git-commit');
default:
return new vscode.ThemeIcon('bell');
}
}
}
/**
* Show notifications in a modal with quick actions
*/
export async function showNotificationModal(notification: NotificationData): Promise<void> {
const actions = ['Open in Browser', 'Mark as Read', 'Cancel'];
const selected = await vscode.window.showInformationMessage(
`${notification.subject.title}\n\nFrom: ${notification.repository.name}\nReason: ${notification.reason}`,
...actions
);
if (selected === 'Open in Browser') {
vscode.env.openExternal(vscode.Uri.parse(notification.url));
}
}

265
src/features/search.ts Normal file
View File

@@ -0,0 +1,265 @@
import * as vscode from 'vscode';
import fetch from 'node-fetch';
export interface SearchOptions {
query: string;
state?: 'open' | 'closed';
labels?: string[];
assignee?: string;
sort?: 'created' | 'updated' | 'popularity';
order?: 'asc' | 'desc';
}
export interface FilterOptions {
state?: 'open' | 'closed';
labels?: string[];
assignee?: string;
}
/**
* Search repositories across WizGIT
*/
export async function searchRepositories(
apiEndpoint: string,
token: string,
query: string,
page: number = 1,
limit: number = 30
): Promise<any> {
try {
const url = new URL(`${apiEndpoint}/repos/search`);
url.searchParams.append('q', query);
url.searchParams.append('page', page.toString());
url.searchParams.append('limit', limit.toString());
const response = await fetch(url.toString(), {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Search failed: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Repository search error:', error);
throw error;
}
}
/**
* Search issues in a repository with filters
*/
export async function searchIssues(
apiEndpoint: string,
token: string,
owner: string,
repo: string,
options: SearchOptions & { page?: number; limit?: number }
): Promise<any> {
try {
const url = new URL(`${apiEndpoint}/repos/${owner}/${repo}/issues`);
url.searchParams.append('page', (options.page || 1).toString());
url.searchParams.append('limit', (options.limit || 30).toString());
if (options.query) {
url.searchParams.append('q', options.query);
}
if (options.state) {
url.searchParams.append('state', options.state);
}
if (options.sort) {
url.searchParams.append('sort', options.sort);
}
if (options.order) {
url.searchParams.append('order', options.order);
}
if (options.labels && options.labels.length > 0) {
url.searchParams.append('labels', options.labels.join(','));
}
if (options.assignee) {
url.searchParams.append('assignee', options.assignee);
}
const response = await fetch(url.toString(), {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Search failed: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Issue search error:', error);
throw error;
}
}
/**
* Search pull requests with filters
*/
export async function searchPullRequests(
apiEndpoint: string,
token: string,
owner: string,
repo: string,
options: SearchOptions & { page?: number; limit?: number }
): Promise<any> {
try {
const url = new URL(`${apiEndpoint}/repos/${owner}/${repo}/pulls`);
url.searchParams.append('page', (options.page || 1).toString());
url.searchParams.append('limit', (options.limit || 30).toString());
if (options.query) {
url.searchParams.append('q', options.query);
}
if (options.state) {
url.searchParams.append('state', options.state);
}
if (options.sort) {
url.searchParams.append('sort', options.sort);
}
if (options.order) {
url.searchParams.append('order', options.order);
}
if (options.assignee) {
url.searchParams.append('assignee', options.assignee);
}
const response = await fetch(url.toString(), {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Search failed: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('PR search error:', error);
throw error;
}
}
/**
* Get available labels for filtering
*/
export async function getLabels(
apiEndpoint: string,
token: string,
owner: string,
repo: string
): Promise<any[]> {
try {
const response = await fetch(`${apiEndpoint}/repos/${owner}/${repo}/labels`, {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch labels: ${response.statusText}`);
}
return (await response.json()) as any[];
} catch (error) {
console.error('Get labels error:', error);
return [];
}
}
/**
* Get collaborators for assignee filtering
*/
export async function getCollaborators(
apiEndpoint: string,
token: string,
owner: string,
repo: string
): Promise<any[]> {
try {
const response = await fetch(`${apiEndpoint}/repos/${owner}/${repo}/collaborators`, {
headers: {
'Authorization': `token ${token}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch collaborators: ${response.statusText}`);
}
return (await response.json()) as any[];
} catch (error) {
console.error('Get collaborators error:', error);
return [];
}
}
/**
* Show a quick filter picker for issues/PRs
*/
export async function showFilterQuickPick(
apiEndpoint: string,
token: string,
owner: string,
repo: string,
type: 'issues' | 'pulls'
): Promise<FilterOptions | null> {
const filterOptions: FilterOptions = {};
// State filter
const stateSelection = await vscode.window.showQuickPick(
['All', 'Open', 'Closed'],
{ placeHolder: 'Filter by state (optional)' }
);
if (stateSelection && stateSelection !== 'All') {
filterOptions.state = stateSelection === 'Open' ? 'open' : 'closed';
}
// Assignee filter
const collaborators = await getCollaborators(apiEndpoint, token, owner, repo);
if (collaborators.length > 0) {
const assigneeItems = ['No filter', ...collaborators.map(c => c.login)];
const assigneeSelection = await vscode.window.showQuickPick(
assigneeItems,
{ placeHolder: 'Filter by assignee (optional)' }
);
if (assigneeSelection && assigneeSelection !== 'No filter') {
filterOptions.assignee = assigneeSelection;
}
}
// Labels filter (only for issues)
if (type === 'issues') {
const labels = await getLabels(apiEndpoint, token, owner, repo);
if (labels.length > 0) {
const labelItems = labels.map(l => l.name);
const labelSelection = await vscode.window.showQuickPick(
labelItems,
{ placeHolder: 'Filter by label (optional)', canPickMany: true }
);
if (labelSelection && labelSelection.length > 0) {
filterOptions.labels = labelSelection;
}
}
}
return Object.keys(filterOptions).length > 0 ? filterOptions : null;
}

160
src/features/statusBar.ts Normal file
View File

@@ -0,0 +1,160 @@
import * as vscode from 'vscode';
export interface ShortcutCommand {
name: string;
shortcut: string;
description: string;
}
export const DEFAULT_SHORTCUTS: ShortcutCommand[] = [
{
name: 'wizgit.searchRepositories',
shortcut: 'ctrl+shift+g r',
description: 'Search repositories'
},
{
name: 'wizgit.searchIssues',
shortcut: 'ctrl+shift+g i',
description: 'Search issues'
},
{
name: 'wizgit.searchPullRequests',
shortcut: 'ctrl+shift+g p',
description: 'Search pull requests'
},
{
name: 'wizgit.openRecentRepos',
shortcut: 'ctrl+shift+g t',
description: 'Open recent repositories'
}
];
/**
* Register keyboard shortcuts
*/
export async function registerKeybindings(context: vscode.ExtensionContext): Promise<void> {
// Shortcuts will be defined in package.json keybindings
// This function ensures the extension recognizes them
context.subscriptions.push(
vscode.commands.registerCommand('wizgit.openKeybindings', async () => {
await vscode.commands.executeCommand('workbench.action.openGlobalKeybindings', '@ext:terencecarrera.wizgit-in-vscode');
})
);
}
/**
* Create or update status bar item
*/
export function createStatusBarItem(context: vscode.ExtensionContext): vscode.StatusBarItem {
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
statusBarItem.command = 'wizgit.showStatusMenu';
statusBarItem.tooltip = 'WizGIT Status';
updateStatusBar(statusBarItem, {
configured: false,
hasRepositories: false,
notificationCount: 0
});
context.subscriptions.push(statusBarItem);
return statusBarItem;
}
export interface StatusBarData {
configured: boolean;
hasRepositories: boolean;
notificationCount: number;
}
/**
* Update status bar display
*/
export function updateStatusBar(statusBarItem: vscode.StatusBarItem, data: StatusBarData): void {
if (!data.configured) {
statusBarItem.text = '$(warning) WizGIT Not Configured';
statusBarItem.show();
return;
}
let text = '$(git-branch) WizGIT';
if (data.hasRepositories) {
text += ' ✓';
}
if (data.notificationCount > 0) {
text += ` (${data.notificationCount})`;
}
statusBarItem.text = text;
statusBarItem.show();
}
export interface StatusMenuItem extends vscode.QuickPickItem {
id?: string;
}
/**
* Show status menu with quick actions
*/
export async function showStatusMenu(): Promise<string | undefined> {
const quickPick = vscode.window.createQuickPick<StatusMenuItem>();
const items: StatusMenuItem[] = [
{
label: '$(telescope) Search Repositories',
description: 'Find and navigate repositories',
id: 'search-repos'
},
{
label: '$(issues) Search Issues',
description: 'Find issues across repositories',
id: 'search-issues'
},
{
label: '$(git-pull-request) Search Pull Requests',
description: 'Find pull requests',
id: 'search-prs'
},
{
label: '$(history) Recent Repositories',
description: 'Open recently accessed repositories',
id: 'recent-repos'
},
{
label: '$(gear) Settings',
description: 'Configure WizGIT',
id: 'configure'
}
];
quickPick.items = items;
return new Promise((resolve) => {
quickPick.onDidChangeSelection((selection) => {
resolve(selection[0]?.id);
quickPick.hide();
});
quickPick.onDidHide(() => {
resolve(undefined);
quickPick.dispose();
});
quickPick.show();
});
}
/**
* Register status bar click handler
*/
export function registerStatusBarClickHandler(
context: vscode.ExtensionContext,
callback: (action: string) => Promise<void>
): void {
context.subscriptions.push(
vscode.commands.registerCommand('wizgit.showStatusMenu', async () => {
const action = await showStatusMenu();
if (action) {
await callback(action);
}
})
);
}