Skip to content

Commit c5d651b

Browse files
authored
Merge pull request #14 from KebanFiru/feat/branch_relative_features
Enhance README and update version to 3.0.0; implement branch-aware st…
2 parents 2752e25 + d60185a commit c5d651b

6 files changed

Lines changed: 204 additions & 50 deletions

File tree

README.md

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
# CodeCount - VS Code Extension
22

33
<!-- Badges -->
4-
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5-
[![VS Code](https://img.shields.io/badge/VS%20Code-1.108.2+-blue)](https://code.visualstudio.com/)
64
[![VS Marketplace](https://img.shields.io/visual-studio-marketplace/v/KebanFiru.CodeLineCounter?label=VS%20Marketplace&color=0078d7)](https://marketplace.visualstudio.com/items?itemName=KebanFiru.CodeLineCounter)
7-
[![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-blue)](https://www.typescriptlang.org/)
85

96
> Quickly analyze your project's codebase with comprehensive metrics, Git analytics, and interactive dashboards.
107
@@ -17,24 +14,39 @@
1714
### Code Metrics
1815
- **Workspace Analysis** - Total line counts for entire projects
1916
- **File-by-File Breakdown** - Statistics for individual files
20-
- **Language Distribution** - Analyze code across file types with expandable lists
17+
- **Branch-Aware Language Distribution** - Analyze code by file type with branch-specific stats on feature branches
2118
- **Comment Detection** - Separate counts for comment and blank lines
2219
- **Gitignore Support** - Automatically respects `.gitignore` patterns
2320

2421
### Git Analytics (Repository View)
25-
- **Contributor Statistics** - Track contributions with added/deleted line counts
22+
- **Branch-Aware Contributor Statistics** - Track contributions with smart branch detection:
23+
- Feature branches show all branch contributors
24+
- Main branch shows complete repository history
2625
- **Commit Trends** - Visualize commits, additions, and deletions over time
2726
- **Monthly Activity** - Monitor development activity by month
2827
- **Work Patterns** - Understand coding patterns by weekday and hour
2928
- **Repository Metrics** - Total contributors, commits, and lines changed
3029

3130
### Interactive Dashboard
32-
- **Real-Time Statistics Panel** - View metrics in VS Code sidebar
33-
- **Expandable Lists** - Top 3 preview with "Show all" toggle for contributors and languages
34-
- **Responsive Charts** - Full-width visualizations using Chart.js v4.4.0
35-
- **Dark Mode Support** - Optimized for VS Code themes with white text on dark backgrounds
31+
- **Real-Time Statistics Panel** - View metrics in VS Code sidebar with instant updates
32+
- **Expandable Lists** - Top 3 preview with "Show all/Show less" toggle for:
33+
- Contributors by lines changed
34+
- Languages by line count
35+
- **Interactive Charts** - Fully responsive visualizations using Chart.js v4.4.0:
36+
- Hover support with cursor feedback
37+
- Tooltips with detailed information
38+
- Horizontal bar charts for better readability
39+
- **Dark Mode Support** - Optimized for all VS Code themes with white text contrast
3640
- **Performance Optimized** - Instant analysis even for large projects
3741

42+
## Recent Improvements (v2.0.0)
43+
44+
- **Branch-aware statistics** - Contributors and languages now adapt to current branch context
45+
- **Enhanced chart interactivity** - Improved hover states and tooltip visibility
46+
- **Better data visualization** - Horizontal bar charts for cleaner contributor display
47+
- **Refined UI/UX** - Simplified panels, better contrast, improved readability
48+
- **Bug fixes** - Fixed chart rendering, toggle functionality, and text color consistency
49+
3850
## Commands
3951

4052
Access CodeCount commands via the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`):
@@ -67,10 +79,13 @@ Access CodeCount commands via the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P
6779
- Select the command you want to run
6880

6981
#### Dashboard Features
70-
- **Expandable Lists** - Click "Show all" to expand contributors or languages
71-
- **Interactive Charts** - Hover over visualizations for detailed information
72-
- **Full-Width Analytics** - View commit trends, monthly activity, and work patterns
73-
- **Real-Time Updates** - Click refresh to update statistics
82+
- **Branch Context Awareness** - Statistics adapt based on your current branch:
83+
- On feature branches: Shows contributors and languages specific to that branch
84+
- On main/master: Displays full repository history
85+
- **Expandable Lists** - Click "Show all" / "Show less" to toggle between preview and full views
86+
- **Interactive Charts** - Hover over any visualization for detailed tooltips and data points
87+
- **Full-Width Analytics** - View commit trends, monthly activity, and work patterns across the repository
88+
- **Real-Time Updates** - Click refresh button to update statistics when branch or files change
7489

7590
## Architecture
7691

@@ -83,12 +98,25 @@ Access CodeCount commands via the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P
8398

8499
## Configuration
85100

101+
### Git Integration
86102
CodeCount respects your project's `.gitignore` file, automatically excluding:
87103
- `node_modules/`
88104
- `dist/` and `build/`
89105
- `.git/` directories
90106
- And any patterns defined in `.gitignore`
91107

108+
### Branch-Aware Analytics
109+
110+
**Feature Branches:** When you're on a feature branch (not main/master), CodeCount intelligently shows:
111+
- Contributors who have commits on your branch
112+
- Languages and files modified in your branch
113+
- Branch-specific statistics
114+
115+
**Main Branch:** When on main or master, you see:
116+
- Complete repository history with all contributors
117+
- Cumulative language distribution across entire project
118+
- Full commit history and analytics
119+
92120
## License
93121

94122
This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "CodeLineCounter",
33
"displayName": "CodeLineCounter",
44
"description": "A tool for learn how much line of code your project contains",
5-
"version": "2.0.0",
5+
"version": "3.0.0",
66
"publisher": "KebanFiru",
77
"license": "MIT",
88
"homepage": "https://github.com/KebanFiru/CodeCount#readme",

src/services/StatsService.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,44 @@ export class StatsService {
2929
return { hasWorkspace: false, totalFiles: 0, filteredFiles: 0, stats: [] };
3030
}
3131

32+
// Detect if on feature branch and get branch-specific file list
33+
let branchFiles: Set<string> | undefined;
34+
const rootPath = await this.getGitRoot();
35+
if (rootPath) {
36+
try {
37+
const currentBranch = (await this.execGit(['rev-parse', '--abbrev-ref', 'HEAD'], rootPath)).trim();
38+
const isMainBranch = currentBranch === 'main' || currentBranch === 'master' || currentBranch === 'origin/main' || currentBranch === 'origin/master';
39+
40+
// For feature branches, get files changed since merge base with main
41+
if (!isMainBranch) {
42+
try {
43+
// Find the merge base and get files changed since then
44+
let mergeBase = '';
45+
try {
46+
mergeBase = (await this.execGit(['merge-base', 'HEAD', 'main'], rootPath)).trim();
47+
} catch {
48+
mergeBase = (await this.execGit(['merge-base', 'HEAD', 'master'], rootPath)).trim();
49+
}
50+
51+
if (mergeBase) {
52+
const filesOutput = await this.execGit(['log', `${mergeBase}..HEAD`, '--name-only', '--pretty='], rootPath);
53+
branchFiles = new Set(
54+
filesOutput
55+
.split(/\r?\n/)
56+
.map(line => line.trim())
57+
.filter(line => line.length > 0)
58+
);
59+
}
60+
} catch {
61+
// Fallback to all files if branch diff fails
62+
branchFiles = undefined;
63+
}
64+
}
65+
} catch {
66+
// If git operations fail, use all files
67+
}
68+
}
69+
3270
const files = await vscode.workspace.findFiles(
3371
'**/*',
3472
'**/{node_modules,out,dist,.git}/**'
@@ -42,8 +80,18 @@ export class StatsService {
4280
return { hasWorkspace: true, totalFiles: files.length, filteredFiles: 0, stats: [] };
4381
}
4482

83+
// Filter to branch-specific files if on feature branch
84+
let filesToCount = filtered;
85+
if (branchFiles && branchFiles.size > 0) {
86+
const workspaceRoot = vscode.workspace.workspaceFolders![0].uri.fsPath;
87+
filesToCount = filtered.filter(uri => {
88+
const relativePath = path.relative(workspaceRoot, uri.fsPath).replace(/\\/g, '/');
89+
return branchFiles.has(relativePath);
90+
});
91+
}
92+
4593
const stats = new Map<string, number>();
46-
for (const uri of filtered) {
94+
for (const uri of filesToCount) {
4795
try {
4896
const document = await vscode.workspace.openTextDocument(uri);
4997
const languageId = document.languageId || 'unknown';
@@ -61,7 +109,7 @@ export class StatsService {
61109
return {
62110
hasWorkspace: true,
63111
totalFiles: files.length,
64-
filteredFiles: filtered.length,
112+
filteredFiles: branchFiles && branchFiles.size > 0 ? filesToCount.length : filtered.length,
65113
stats: entries
66114
};
67115
}
@@ -521,6 +569,7 @@ export class StatsService {
521569

522570
private async getContributorStatsFromGit(rootPath: string): Promise<ContributorStat[]> {
523571
// Use author email as the aggregation key to avoid duplicate names
572+
// Always show the full branch history (main/feature/etc.)
524573
const output = await this.execGit(
525574
// Include full history reachable from HEAD (include merges)
526575
['log', 'HEAD', '--numstat', `--pretty=${GIT_AUTHOR_PREFIX}%an|%aE`],

src/views/StatsWebviewPanel.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
renderLanguageCharts,
88
renderLanguageTable,
99
renderContributorsChart,
10-
renderIgnoredFilesSection,
1110
renderRepoAnalytics
1211
} from './webview/templates';
1312

@@ -106,8 +105,6 @@ export class StatsWebviewPanel {
106105
${renderContributorsChart(contributorResult)}
107106
${renderRepoAnalytics(repoAnalytics)}
108107
</div>
109-
110-
${renderIgnoredFilesSection(languageResult)}
111108
</div>
112109
113110
<script>

src/views/webview/components/contributors.ts

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export const renderContributorsChart = (
5050

5151
const barLabels = result.stats.slice(0, 10).map(s => escapeHtml(s.name));
5252
const barData = result.stats.slice(0, 10).map(s => s.added + s.deleted);
53+
const axisColor = '#ffffff';
5354

5455
const branchLabel = result.branch && result.branch !== 'all' ? `<div class="muted" style="font-size:12px;margin-top:4px;">Branch: ${escapeHtml(result.branch)}</div>` : '';
5556

@@ -70,45 +71,75 @@ export const renderContributorsChart = (
7071
</div>` : ''}
7172
</div>
7273
<div>
73-
<div class="chart-container compact">
74-
<canvas id="contributorsAuthorBar" height="260"></canvas>
74+
<div class="chart-container" style="height:320px;">
75+
<canvas id="contributorsAuthorBar"></canvas>
7576
</div>
7677
</div>
7778
</div>
7879
<script>
80+
const contributorsAxisColor = '#ffffff';
81+
const contributorsGridColor = 'rgba(255,255,255,0.12)';
7982
const labels = ${JSON.stringify(barLabels)};
8083
const data = ${JSON.stringify(barData)};
8184
const ctx = document.getElementById('contributorsAuthorBar');
8285
if (ctx) {
83-
new Chart(ctx, {
84-
type: 'bar',
85-
data: { labels, datasets: [{ label: 'Lines Changed', data, backgroundColor: ${JSON.stringify(colors.slice(0, 10))} }] },
86-
options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { display: false } }, scales: { x: { ticks: { color: 'var(--fg)' } }, y: { ticks: { color: 'var(--fg)' }, beginAtZero: true } } }
87-
});
86+
try {
87+
new Chart(ctx, {
88+
type: 'bar',
89+
data: { labels, datasets: [{ label: 'Lines Changed', data, backgroundColor: ${JSON.stringify(colors.slice(0, 10))} }] },
90+
options: {
91+
indexAxis: 'y',
92+
responsive: true,
93+
maintainAspectRatio: false,
94+
interaction: { mode: 'nearest', intersect: true },
95+
layout: { padding: { left: 10, right: 10, top: 8, bottom: 8 } },
96+
plugins: {
97+
legend: { display: false },
98+
tooltip: {
99+
enabled: true,
100+
titleColor: contributorsAxisColor,
101+
bodyColor: contributorsAxisColor
102+
}
103+
},
104+
onHover: (_, activeElements, chart) => {
105+
chart.canvas.style.cursor = activeElements.length > 0 ? 'pointer' : 'default';
106+
},
107+
scales: {
108+
x: {
109+
beginAtZero: true,
110+
ticks: { color: contributorsAxisColor, font: { size: 11 } },
111+
grid: { color: contributorsGridColor }
112+
},
113+
y: {
114+
ticks: { color: contributorsAxisColor, font: { size: 11 }, autoSkip: false },
115+
grid: { display: false }
116+
}
117+
}
118+
}
119+
});
120+
} catch {
121+
// Keep toggle interaction available even if chart initialization fails.
122+
}
88123
}
89124
90-
// Use delegated click handling and guard for DOM readiness
91-
document.addEventListener('click', (ev) => {
92-
const target = ev.target;
93-
if (!(target instanceof HTMLElement)) return;
94-
if (target.id !== 'contributors-toggle') return;
125+
const contributorsToggleBtn = document.getElementById('contributors-toggle');
126+
const contributorsPreview = document.getElementById('contributors-list');
127+
const contributorsAll = document.getElementById('contributors-all');
128+
if (contributorsToggleBtn && contributorsPreview && contributorsAll) {
129+
contributorsToggleBtn.addEventListener('click', () => {
130+
if (contributorsAll.style.display === 'none' || getComputedStyle(contributorsAll).display === 'none') {
131+
contributorsPreview.style.display = 'none';
132+
contributorsAll.style.display = 'block';
133+
contributorsToggleBtn.textContent = 'Show less contributors';
134+
} else {
135+
contributorsPreview.style.display = 'block';
136+
contributorsAll.style.display = 'none';
137+
contributorsToggleBtn.textContent = 'Show all contributors';
138+
}
95139
96-
const allEl = document.getElementById('contributors-all');
97-
const listEl = document.getElementById('contributors-list');
98-
if (!allEl || !listEl) return;
99-
100-
if (allEl.style.display === 'none' || getComputedStyle(allEl).display === 'none') {
101-
listEl.style.display = 'none';
102-
allEl.style.display = 'block';
103-
target.textContent = 'Show fewer contributors';
104-
} else {
105-
listEl.style.display = 'block';
106-
allEl.style.display = 'none';
107-
target.textContent = 'Show all contributors';
108-
}
109-
110-
window.__codecountRequestLayout?.();
111-
});
140+
window.__codecountRequestLayout?.();
141+
});
142+
}
112143
</script>
113144
</div>
114145
`;

0 commit comments

Comments
 (0)