diff --git a/.gitignore b/.gitignore index 8e5962e..ee8ad9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ out -node_modules \ No newline at end of file +node_modules +.vscode-test/ diff --git a/package.json b/package.json index 99de66f..6eea977 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,11 @@ "Other" ], "activationEvents": [ - "onCommand:codestats.sayHello" + "*" ], "main": "./out/src/code-stats", "contributes": { - "commands": [{ - "command": "codestats.sayHello", - "title": "Code::Stats" - }] + "commands": [] }, "scripts": { "vscode:prepublish": "tsc -p ./", diff --git a/src/code-stats.ts b/src/code-stats.ts index a948039..27c6e11 100644 --- a/src/code-stats.ts +++ b/src/code-stats.ts @@ -1,34 +1,17 @@ "use strict"; -// the module 'vscode' contains the VS Code extensibility API -// import the module and reference it with the alias vscode in your code below -import * as vscode from "vscode"; +import { ExtensionContext } from "vscode"; +import { XpCounter } from "./xp-counter"; -// this method is called when your extension is activated -// your extension is activated the very first time the command is executed -export function activate(context: vscode.ExtensionContext) { +export function activate(context: ExtensionContext): void { + console.log("Activating code-stats-vscode"); - // use the console to output diagnostic information (console.log) and errors (console.error) - // this line of code will only be executed once when your extension is activated - console.log("Congratulations, your extension \"code-stats-vscode\" is now active!"); + let controller: XpCounter = new XpCounter(); - // the command has been defined in the package.json file - // now provide the implementation of the command with registerCommand - // the commandId parameter must match the command field in package.json - let disposable: vscode.Disposable = vscode.commands.registerCommand("codestats.sayHello", () => { - var editor: vscode.TextEditor = vscode.window.activeTextEditor; - if (!editor) { - return; - } - - var selection: vscode.Selection = editor.selection; - var text: string = editor.document.getText(selection); - - vscode.window.showInformationMessage("Selected characters: " + text.length); - }); - - context.subscriptions.push(disposable); + // add to a list of disposables which are disposed when this extension is deactivated. + context.subscriptions.push(controller); } // this method is called when your extension is deactivated -export function deactivate() { -} \ No newline at end of file +export function deactivate(): void { + console.log("Deactivating code-stats-vscode"); +} diff --git a/src/pulse.ts b/src/pulse.ts new file mode 100644 index 0000000..837bc23 --- /dev/null +++ b/src/pulse.ts @@ -0,0 +1,28 @@ +export class Pulse { + xps: Map < string, number > ; + + constructor() { + console.log("Creating Pulse"); + this.xps = new Map < string, number > (); + } + + public getXP(language: string): number { + let xp: number = this.xps.get(language); + + if (xp === null || xp === undefined) { + return 0; + } else { + return xp; + } + } + + public addXP(language: string, amount: number): void { + let xp: number = this.getXP(language); + + xp += amount; + + this.xps.set(language, xp); + + // console.log(`Added ${amount} of XP to ${language}, total is now ${xp}`); + } +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..0ad6013 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,20 @@ +// converted to ts from https://github.com/Nicd/code-stats-atom/blob/master/lib/utils.js +export function getISOTimestamp(date: Date): string { + const offset: number = -date.getTimezoneOffset(); + const prefix: string = (offset >= 0) ? "+" : "-"; + + function pad(num: number): string { + const norm: number = Math.abs(Math.floor(num)); + + return ((norm < 10) ? "0" : "") + norm; + } + + return date.getFullYear() + + "-" + pad(date.getMonth() + 1) + + "-" + pad(date.getDate()) + + "T" + pad(date.getHours()) + + ":" + pad(date.getMinutes()) + + ":" + pad(date.getSeconds()) + + prefix + pad(offset / 60) + + pad(offset % 60); +} \ No newline at end of file diff --git a/src/xp-counter.ts b/src/xp-counter.ts new file mode 100644 index 0000000..60b6377 --- /dev/null +++ b/src/xp-counter.ts @@ -0,0 +1,72 @@ +import { Disposable, workspace, window, StatusBarItem, TextDocument, StatusBarAlignment, TextDocumentChangeEvent, Range } from "vscode"; +import { Pulse } from "./pulse"; + +export class XpCounter { + private combinedDisposable: Disposable; + private statusBarItem: StatusBarItem; + private pulse: Pulse; + private languages: Array = ["typescript", "javascript"]; + private changeCount: number = 0; + + constructor() { + console.log(`Supported languages for Code::Stats are ${this.languages}`); + this.pulse = new Pulse(); + + if (!this.statusBarItem) { + this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left); + } + + let subscriptions: Disposable[] = []; + workspace.onDidChangeTextDocument(this.onTextDocumentChanged, this, subscriptions); + this.combinedDisposable = Disposable.from(...subscriptions); + } + + dispose(): void { + this.combinedDisposable.dispose(); + this.statusBarItem.dispose(); + } + + private onTextDocumentChanged(event: TextDocumentChangeEvent): void { + let changeCount: number = 0; + for (let change of event.contentChanges) { + changeCount += this.determineChangeCount(change.range); + } + this.updateXpCount(event.document, changeCount); + } + + private determineChangeCount(range: Range): number { + if (range === null || range === undefined ) { + return 0; + } + // console.log(`L${range.start.line}C${range.start.character} to L${range.end.line}C${range.end.character}`); + if (range.start.line === range.end.line) { + if (range.start.character === range.end.character) { + return 1; + } else { + console.log(`L${range.start.line}C${range.start.character} to L${range.end.line}C${range.end.character}`); + return range.end.character - range.start.character; + } + } + // todo detect multiline changes + return 1; + } + + public updateXpCount(document: TextDocument, changeCount: number): void { + // only update xp if one of supported languages + if (this.isSupportedLanguage(document.languageId)) { + this.pulse.addXP(document.languageId, changeCount); + // this.statusBarItem.text = this.changeCount !== 1 ? + // ` C::S ${this.changeCount} Words` : "$(pencil) C::S 1 Word"; + this.statusBarItem.text = `C::S $(pencil) ${this.pulse.getXP(document.languageId)}`; + this.statusBarItem.show(); + } else { + this.statusBarItem.hide(); + } + } + + private isSupportedLanguage(language: string): boolean { + // todo: check supported languages + return true; + } + +} diff --git a/test/extension.test.ts b/test/extension.test.ts index 5ffe5a5..eab3d0d 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -1,22 +1,53 @@ -// -// Note: This example test is leveraging the Mocha test framework. -// Please refer to their documentation on https://mochajs.org/ for help. -// +import * as assert from "assert"; -// The module 'assert' provides assertion methods from node -import * as assert from 'assert'; +// tslint:disable-next-line:no-unused-variable +import * as vscode from "vscode"; +// tslint:disable-next-line:no-unused-variable +import * as codestats from "../src/code-stats"; +import { Pulse } from "../src/pulse"; -// You can import and use all API from the 'vscode' module -// as well as import your extension to test it -import * as vscode from 'vscode'; -import * as myExtension from '../src/code-stats'; +suite("code-stats-vscode extension tests", () => { + test("Initialized Pulse is empty", () => { + let pulse: Pulse = new Pulse(); + const language: string = "typescript"; -// Defines a Mocha test suite to group tests of similar kind together -suite("Extension Tests", () => { + let initialXP: number = pulse.getXP(language); - // Defines a Mocha unit test - test("Something 1", () => { - assert.equal(-1, [1, 2, 3].indexOf(5)); - assert.equal(-1, [1, 2, 3].indexOf(0)); + assert.equal(initialXP, 0); + }); + + test("Add XP to Pulse", () => { + let pulse: Pulse = new Pulse(); + + const language1: string = "typescript"; + const language2: string = "javascript"; + const language3: string = "coffeescript"; + const addedXP: number = 1000; + + let xp1: number = pulse.getXP(language1); + let xp2: number = pulse.getXP(language2); + let xp3: number = pulse.getXP(language3); + assert.equal(xp1, 0); + assert.equal(xp2, 0); + assert.equal(xp3, 0); + + pulse.addXP(language1, addedXP); + xp1 = pulse.getXP(language1); + xp2 = pulse.getXP(language2); + xp3 = pulse.getXP(language3); + + assert.equal(xp1, addedXP); + assert.equal(xp2, 0); + assert.equal(xp3, 0); + + pulse.addXP(language1, addedXP); + pulse.addXP(language2, addedXP); + xp1 = pulse.getXP(language1); + xp2 = pulse.getXP(language2); + xp3 = pulse.getXP(language3); + + assert.equal(xp1, 2 * addedXP); + assert.equal(xp2, addedXP); + assert.equal(xp3, 0); }); }); \ No newline at end of file