Skip to content

Commit 0a945f1

Browse files
committed
Track AI vs Human line additions and deletions
1 parent 2927634 commit 0a945f1

File tree

4 files changed

+125
-34
lines changed

4 files changed

+125
-34
lines changed

src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,7 @@ export interface Heartbeat {
3131
project_root_count?: number;
3232
language?: string;
3333
category?: 'debugging' | 'ai coding' | 'building' | 'code reviewing';
34+
ai_line_changes?: number;
35+
human_line_changes?: number;
3436
is_unsaved_entity?: boolean;
3537
}

src/utils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,21 @@ export class Utils {
174174
}
175175
}
176176
}
177+
178+
interface FileSelection {
179+
selection: vscode.Position;
180+
lastHeartbeatAt: number;
181+
}
182+
183+
export interface FileSelectionMap {
184+
[key: string]: FileSelection;
185+
}
186+
187+
export interface Lines {
188+
[fileName: string]: number;
189+
}
190+
191+
export interface LineCounts {
192+
ai: Lines;
193+
human: Lines;
194+
}

src/wakatime.ts

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,7 @@ import { Options, Setting } from './options';
1616
import { Dependencies } from './dependencies';
1717
import { Desktop } from './desktop';
1818
import { Logger } from './logger';
19-
import { Utils } from './utils';
20-
21-
interface FileSelection {
22-
selection: vscode.Position;
23-
lastHeartbeatAt: number;
24-
}
25-
26-
interface FileSelectionMap {
27-
[key: string]: FileSelection;
28-
}
19+
import { FileSelectionMap, LineCounts, Lines, Utils } from './utils';
2920

3021
export class WakaTime {
3122
private agentName: string;
@@ -40,9 +31,9 @@ export class WakaTime {
4031
private lastCompile: boolean = false;
4132
private lastAICodeGenerating: boolean = false;
4233
private dedupe: FileSelectionMap = {};
43-
private debounceTimeoutId: any = null;
34+
private debounceId: any = null;
4435
private debounceMs = 50;
45-
private AIDebounceTimeoutId: any = null;
36+
private AIDebounceId: any = null;
4637
private AIdebounceMs = 1000;
4738
private AIdebounceCount = 0;
4839
private AIrecentPastes: number[] = [];
@@ -67,6 +58,8 @@ export class WakaTime {
6758
private isMetricsEnabled: boolean = false;
6859
private heartbeats: Heartbeat[] = [];
6960
private lastSent: number = 0;
61+
private linesInFiles: Lines = {};
62+
private lineChanges: LineCounts = { ai: {}, human: {} };
7063

7164
constructor(extensionPath: string, logger: Logger) {
7265
this.extensionPath = extensionPath;
@@ -449,28 +442,33 @@ export class WakaTime {
449442
}
450443

451444
private onDebuggingChanged(): void {
445+
this.updateLineNumbers();
452446
this.onEvent(false);
453447
}
454448

455449
private onDidStartDebugSession(): void {
456450
this.isDebugging = true;
451+
this.updateLineNumbers();
457452
this.onEvent(false);
458453
}
459454

460455
private onDidTerminateDebugSession(): void {
461456
this.isDebugging = false;
457+
this.updateLineNumbers();
462458
this.onEvent(false);
463459
}
464460

465461
private onDidStartTask(e: vscode.TaskStartEvent): void {
466462
if (e.execution.task.isBackground) return;
467463
if (e.execution.task.detail && e.execution.task.detail.indexOf('watch') !== -1) return;
468464
this.isCompiling = true;
465+
this.updateLineNumbers();
469466
this.onEvent(false);
470467
}
471468

472469
private onDidEndTask(): void {
473470
this.isCompiling = false;
471+
this.updateLineNumbers();
474472
this.onEvent(false);
475473
}
476474

@@ -479,6 +477,7 @@ export class WakaTime {
479477
if (Utils.isAIChatSidebar(e.textEditor?.document?.uri)) {
480478
this.isAICodeGenerating = true;
481479
}
480+
this.updateLineNumbers();
482481
this.onEvent(false);
483482
}
484483

@@ -497,43 +496,71 @@ export class WakaTime {
497496
this.AIrecentPastes = [];
498497
if (this.isAICodeGenerating) {
499498
this.AIdebounceCount++;
500-
clearTimeout(this.AIDebounceTimeoutId);
501-
this.AIDebounceTimeoutId = setTimeout(() => {
499+
clearTimeout(this.AIDebounceId);
500+
this.AIDebounceId = setTimeout(() => {
502501
if (this.AIdebounceCount > 1) {
503502
this.isAICodeGenerating = false;
504503
}
505504
}, this.AIdebounceMs);
506505
}
507506
} else if (this.isAICodeGenerating) {
508507
this.AIdebounceCount = 0;
509-
clearTimeout(this.AIDebounceTimeoutId);
508+
clearTimeout(this.AIDebounceId);
509+
this.updateLineNumbers();
510510
}
511+
511512
if (!this.isAICodeGenerating) return;
513+
512514
this.onEvent(false);
513515
}
514516

515517
private onChangeTab(_e: vscode.TextEditor | undefined): void {
518+
this.updateLineNumbers();
516519
this.onEvent(false);
517520
}
518521

519522
private onSave(_e: vscode.TextDocument | undefined): void {
523+
this.updateLineNumbers();
520524
this.onEvent(true);
521525
}
522526

523527
private onChangeNotebook(_e: vscode.NotebookDocumentChangeEvent): void {
528+
this.updateLineNumbers();
524529
this.onEvent(false);
525530
}
526531

527532
private onSaveNotebook(_e: vscode.NotebookDocument | undefined): void {
533+
this.updateLineNumbers();
528534
this.onEvent(true);
529535
}
530536

537+
private updateLineNumbers(): void {
538+
const doc = vscode.window.activeTextEditor?.document;
539+
if (!doc) return;
540+
const file = Utils.getFocusedFile(doc);
541+
if (!file) return;
542+
543+
const current = doc.lineCount;
544+
if (this.linesInFiles[file] === undefined) {
545+
this.linesInFiles[file] = current;
546+
}
547+
548+
const prev = this.linesInFiles[file] ?? current;
549+
const delta = current - prev;
550+
551+
const changes = this.isAICodeGenerating ? this.lineChanges.ai : this.lineChanges.human;
552+
changes[file] = (changes[file] ?? 0) + delta;
553+
554+
this.linesInFiles[file] = current;
555+
}
556+
531557
private onEvent(isWrite: boolean): void {
532558
if (Date.now() - this.lastSent > SEND_BUFFER_SECONDS * 1000) {
533559
this.sendHeartbeats();
534560
}
535-
clearTimeout(this.debounceTimeoutId);
536-
this.debounceTimeoutId = setTimeout(() => {
561+
562+
clearTimeout(this.debounceId);
563+
this.debounceId = setTimeout(() => {
537564
if (this.disabled) return;
538565
const editor = vscode.window.activeTextEditor;
539566
if (editor) {
@@ -603,8 +630,12 @@ export class WakaTime {
603630
lineno: selection.line + 1,
604631
cursorpos: selection.character + 1,
605632
lines_in_file: doc.lineCount,
633+
ai_line_changes: this.lineChanges.ai[file],
634+
human_line_changes: this.lineChanges.human[file],
606635
};
607636

637+
this.lineChanges = { ai: {}, human: {} };
638+
608639
if (isDebugging) {
609640
heartbeat.category = 'debugging';
610641
} else if (isCompiling) {
@@ -665,6 +696,13 @@ export class WakaTime {
665696
args.push('--category', heartbeat.category);
666697
}
667698

699+
if (heartbeat.ai_line_changes) {
700+
args.push('--ai-line-changes', String(heartbeat.ai_line_changes));
701+
}
702+
if (heartbeat.human_line_changes) {
703+
args.push('--human-line-changes', String(heartbeat.human_line_changes));
704+
}
705+
668706
if (this.isMetricsEnabled) args.push('--metrics');
669707

670708
const apiKey = await this.options.getApiKey();

src/web/wakatime.ts

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,7 @@ import {
1010

1111
import { Logger } from './logger';
1212
import { Memento } from 'vscode';
13-
import { Utils } from '../utils';
14-
15-
interface FileSelection {
16-
selection: vscode.Position;
17-
lastHeartbeatAt: number;
18-
}
19-
20-
interface FileSelectionMap {
21-
[key: string]: FileSelection;
22-
}
13+
import { FileSelectionMap, LineCounts, Lines, Utils } from '../utils';
2314

2415
export class WakaTime {
2516
private agentName: string;
@@ -34,9 +25,9 @@ export class WakaTime {
3425
private lastCompile: boolean = false;
3526
private lastAICodeGenerating: boolean = false;
3627
private dedupe: FileSelectionMap = {};
37-
private debounceTimeoutId: any = null;
28+
private debounceId: any = null;
3829
private debounceMs = 50;
39-
private AIDebounceTimeoutId: any = null;
30+
private AIDebounceId: any = null;
4031
private AIdebounceMs = 1000;
4132
private AIdebounceCount = 0;
4233
private AIrecentPastes: number[] = [];
@@ -57,6 +48,8 @@ export class WakaTime {
5748
private lastApiKeyPrompted: number = 0;
5849
private heartbeats: Heartbeat[] = [];
5950
private lastSent: number = 0;
51+
private linesInFiles: Lines = {};
52+
private lineChanges: LineCounts = { ai: {}, human: {} };
6053

6154
constructor(logger: Logger, config: Memento) {
6255
this.logger = logger;
@@ -364,33 +357,39 @@ export class WakaTime {
364357
}
365358

366359
private onDebuggingChanged(): void {
360+
this.updateLineNumbers();
367361
this.onEvent(false);
368362
}
369363

370364
private onDidStartDebugSession(): void {
371365
this.isDebugging = true;
366+
this.updateLineNumbers();
372367
this.onEvent(false);
373368
}
374369

375370
private onDidTerminateDebugSession(): void {
376371
this.isDebugging = false;
372+
this.updateLineNumbers();
377373
this.onEvent(false);
378374
}
379375

380376
private onDidStartTask(e: vscode.TaskStartEvent): void {
381377
if (e.execution.task.isBackground) return;
382378
if (e.execution.task.detail && e.execution.task.detail.indexOf('watch') !== -1) return;
383379
this.isCompiling = true;
380+
this.updateLineNumbers();
384381
this.onEvent(false);
385382
}
386383

387384
private onDidEndTask(): void {
388385
this.isCompiling = false;
386+
this.updateLineNumbers();
389387
this.onEvent(false);
390388
}
391389

392390
private onChangeSelection(e: vscode.TextEditorSelectionChangeEvent): void {
393391
if (e.kind === vscode.TextEditorSelectionChangeKind.Command) return;
392+
this.updateLineNumbers();
394393
this.onEvent(false);
395394
}
396395

@@ -409,43 +408,68 @@ export class WakaTime {
409408
this.AIrecentPastes = [];
410409
if (this.isAICodeGenerating) {
411410
this.AIdebounceCount++;
412-
clearTimeout(this.AIDebounceTimeoutId);
413-
this.AIDebounceTimeoutId = setTimeout(() => {
411+
clearTimeout(this.AIDebounceId);
412+
this.AIDebounceId = setTimeout(() => {
414413
if (this.AIdebounceCount > 1) {
415414
this.isAICodeGenerating = false;
416415
}
417416
}, this.AIdebounceMs);
418417
}
419418
} else if (this.isAICodeGenerating) {
420419
this.AIdebounceCount = 0;
421-
clearTimeout(this.AIDebounceTimeoutId);
420+
clearTimeout(this.AIDebounceId);
421+
this.updateLineNumbers();
422422
}
423423
if (!this.isAICodeGenerating) return;
424424
this.onEvent(false);
425425
}
426426

427427
private onChangeTab(_e: vscode.TextEditor | undefined): void {
428+
this.updateLineNumbers();
428429
this.onEvent(false);
429430
}
430431

431432
private onSave(_e: vscode.TextDocument | undefined): void {
433+
this.updateLineNumbers();
432434
this.onEvent(true);
433435
}
434436

435437
private onChangeNotebook(_e: vscode.NotebookDocumentChangeEvent): void {
438+
this.updateLineNumbers();
436439
this.onEvent(false);
437440
}
438441

439442
private onSaveNotebook(_e: vscode.NotebookDocument | undefined): void {
443+
this.updateLineNumbers();
440444
this.onEvent(true);
441445
}
442446

447+
private updateLineNumbers(): void {
448+
const doc = vscode.window.activeTextEditor?.document;
449+
if (!doc) return;
450+
const file = Utils.getFocusedFile(doc);
451+
if (!file) return;
452+
453+
const current = doc.lineCount;
454+
if (this.linesInFiles[file] === undefined) {
455+
this.linesInFiles[file] = current;
456+
}
457+
458+
const prev = this.linesInFiles[file] ?? current;
459+
const delta = current - prev;
460+
461+
const changes = this.isAICodeGenerating ? this.lineChanges.ai : this.lineChanges.human;
462+
changes[file] = (changes[file] ?? 0) + delta;
463+
464+
this.linesInFiles[file] = current;
465+
}
466+
443467
private onEvent(isWrite: boolean): void {
444468
if (Date.now() - this.lastSent > SEND_BUFFER_SECONDS * 1000) {
445469
this.sendHeartbeats();
446470
}
447-
clearTimeout(this.debounceTimeoutId);
448-
this.debounceTimeoutId = setTimeout(() => {
471+
clearTimeout(this.debounceId);
472+
this.debounceId = setTimeout(() => {
449473
if (this.disabled) return;
450474
const editor = vscode.window.activeTextEditor;
451475
if (editor) {
@@ -515,6 +539,8 @@ export class WakaTime {
515539
lines_in_file: doc.lineCount,
516540
};
517541

542+
this.lineChanges = { ai: {}, human: {} };
543+
518544
if (isDebugging) {
519545
heartbeat.category = 'debugging';
520546
} else if (isCompiling) {
@@ -525,6 +551,13 @@ export class WakaTime {
525551
heartbeat.category = 'code reviewing';
526552
}
527553

554+
if (heartbeat.ai_line_changes) {
555+
heartbeat.ai_line_changes = this.lineChanges.ai[file];
556+
}
557+
if (heartbeat.human_line_changes) {
558+
heartbeat.human_line_changes = this.lineChanges.human[file];
559+
}
560+
528561
const project = this.getProjectName();
529562
if (project) heartbeat.alternate_project = project;
530563

0 commit comments

Comments
 (0)