Skip to content

Commit 11bd9f1

Browse files
committed
Add stats tracking features and enhance Gitignore handling
- Introduced StatsService and StatsTreeProvider for tracking language and contributor statistics. - Added StatsWebviewPanel for displaying statistics in a webview. - Implemented commands for refreshing stats and opening contributor files. - Enhanced GitignoreService to handle missing .gitignore files more gracefully. - Created new types for contributor file statistics and history.
1 parent 0f71438 commit 11bd9f1

6 files changed

Lines changed: 916 additions & 3 deletions

File tree

src/extension.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1+
import * as path from 'path';
12
import * as vscode from 'vscode';
23
import { LineCounter } from './services/LineCounter';
34
import { GitignoreService } from './services/GitignoreService';
45
import { WorkspaceLineCounter } from './services/WorkspaceLineCounter';
56
import { LineCounterByFileFormat } from './services/LineCounterByFileFormat';
7+
import { StatsTreeProvider } from './views/StatsTreeProvider';
8+
import { StatsService } from './services/StatsService';
69

710
export function activate(context: vscode.ExtensionContext) {
811
const lineCounter = new LineCounter();
912
const gitignoreService = new GitignoreService();
1013
const workspaceLineCounter = new WorkspaceLineCounter(lineCounter, gitignoreService);
1114
const lineCounterByFileFormat = new LineCounterByFileFormat(workspaceLineCounter);
15+
const statsService = new StatsService(lineCounter, gitignoreService);
16+
const statsProvider = new StatsTreeProvider(statsService);
1217

1318
const fileline = vscode.commands.registerCommand('codecount.countLines', () => {
1419
const editor = vscode.window.activeTextEditor;
@@ -66,9 +71,81 @@ export function activate(context: vscode.ExtensionContext) {
6671
);
6772
});
6873

74+
const refreshStats = vscode.commands.registerCommand('codecount.refreshStats', () => {
75+
statsProvider.refresh();
76+
});
77+
78+
const openContributorFile = vscode.commands.registerCommand(
79+
'codecount.openContributorFile',
80+
async (args?: { author?: string; filePath?: string }) => {
81+
const author = args?.author;
82+
const filePath = args?.filePath;
83+
if (!author || !filePath) {
84+
return;
85+
}
86+
const rootPath = await statsService.getGitRootPath();
87+
if (!rootPath) {
88+
vscode.window.showWarningMessage('Git repository not found.');
89+
return;
90+
}
91+
92+
const absolutePath = vscode.Uri.file(path.join(rootPath, filePath));
93+
try {
94+
const document = await vscode.workspace.openTextDocument(absolutePath);
95+
await vscode.window.showTextDocument(document, { preview: false });
96+
} catch {
97+
vscode.window.showWarningMessage('File not found in workspace.');
98+
return;
99+
}
100+
101+
const history = await statsService.getContributorFileHistory(author, filePath);
102+
if (!history.available) {
103+
vscode.window.showWarningMessage('Git repository not found.');
104+
return;
105+
}
106+
if (history.entries.length === 0) {
107+
vscode.window.showInformationMessage('No history found for this contributor.');
108+
return;
109+
}
110+
111+
const pick = await vscode.window.showQuickPick(
112+
history.entries.map((entry) => ({
113+
label: `${entry.date} ${entry.hash}`,
114+
hash: entry.hash
115+
})),
116+
{
117+
placeHolder: 'Select a commit to view changes'
118+
}
119+
);
120+
if (!pick) {
121+
return;
122+
}
123+
124+
const gitPath = path.join(rootPath, filePath);
125+
const encode = (ref: string) =>
126+
vscode.Uri.parse(
127+
`git:${gitPath}?${encodeURIComponent(JSON.stringify({ path: gitPath, ref }))}`
128+
);
129+
130+
const left = encode(`${pick.hash}^`);
131+
const right = encode(pick.hash);
132+
await vscode.commands.executeCommand(
133+
'vscode.diff',
134+
left,
135+
right,
136+
`Changes by ${author}: ${filePath} (${pick.hash})`
137+
);
138+
}
139+
);
140+
69141
context.subscriptions.push(fileline);
70142
context.subscriptions.push(fileslines);
71143
context.subscriptions.push(fileslinesByExtension);
144+
context.subscriptions.push(refreshStats);
145+
context.subscriptions.push(openContributorFile);
146+
context.subscriptions.push(
147+
vscode.window.registerTreeDataProvider('codecount.stats', statsProvider)
148+
);
72149
}
73150

74151
// This method is called when your extension is deactivated

src/services/GitignoreService.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ export class GitignoreService {
2222
try {
2323
const data = await vscode.workspace.fs.readFile(gitignoreUri);
2424
content = data.toString();
25-
} catch {
26-
// .gitignore not found or not readable; ignore silently
25+
}
26+
catch {
27+
// .gitignore not found
2728
}
2829

2930
const rules = this.parseGitignore(content);
@@ -33,18 +34,22 @@ export class GitignoreService {
3334
}
3435

3536
private parseGitignore(content: string): GitignoreRule[] {
37+
3638
const rules: GitignoreRule[] = [];
3739
const lines = content.split(/\r?\n/);
40+
3841
for (const rawLine of lines) {
3942
if (!rawLine || rawLine.trim().length === 0) {
4043
continue;
4144
}
4245

4346
let line = rawLine;
4447
let negate = false;
48+
4549
if (line.startsWith('\\#')) {
4650
line = line.slice(1);
47-
} else if (line.startsWith('#')) {
51+
}
52+
else if (line.startsWith('#')) {
4853
continue;
4954
}
5055

@@ -67,8 +72,10 @@ export class GitignoreService {
6772
}
6873

6974
private matchGitignore(rules: GitignoreRule[], relativePath: string): boolean {
75+
7076
const normalized = relativePath.replace(/\\/g, '/');
7177
let ignored = false;
78+
7279
for (const rule of rules) {
7380
const matches = rule.isDirectory
7481
? rule.regex.test(normalized + '/')

0 commit comments

Comments
 (0)