├── .gitignore ├── icon-sctt.png ├── .eslintrc.json ├── .vscode └── settings.json ├── out ├── utils.js.map ├── utils.js ├── statusBar.js.map ├── extension.js.map ├── statusBar.js ├── database.js.map ├── extension.js ├── database.js ├── summaryView.js.map ├── timeTracker.js.map └── timeTracker.js ├── tsconfig.json ├── src ├── utils.ts.md ├── summaryView.ts.md ├── statusBar.ts.md ├── timeTracker.ts.md ├── extension.ts.md ├── notificationStatusBar.ts ├── database.ts.md ├── logger.ts ├── statusBar.ts ├── utils.ts ├── database.ts ├── healthNotifications.ts.md ├── healthNotifications.ts └── extension.ts ├── .vscodeignore ├── webpack.config.js ├── .github └── workflows │ ├── repo-stats.yml │ └── build-and-publish.yml ├── LICENSE ├── test_language_detection.js ├── docs ├── README.md ├── js │ └── main.js ├── css │ └── style.css └── index.html ├── GITHUB_PAGES_SETUP.md ├── CONTRIBUTING.md ├── package.json ├── TECHNICAL.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | **/*.code-workspace 4 | dist -------------------------------------------------------------------------------- /icon-sctt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twentyTwo/vsc-ext-coding-time-tracker/HEAD/icon-sctt.png -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": "latest", 5 | "sourceType": "module" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "simpleCodingTimeTracker.health.eyeRestInterval": 20, 3 | "simpleCodingTimeTracker.health.stretchInterval": 100, 4 | "simpleCodingTimeTracker.inactivityTimeout": 2.5, 5 | "simpleCodingTimeTracker.focusTimeout": 3 6 | } -------------------------------------------------------------------------------- /out/utils.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAAA,SAAgB,UAAU,CAAC,OAAe;IACtC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACtC,OAAO,GAAG,KAAK,KAAK,IAAI,GAAG,CAAC;AAChC,CAAC;AAJD,gCAIC;AAED,wCAAwC"} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": ["es6", "dom", "dom.iterable"], 7 | "sourceMap": true, 8 | "rootDir": "src", 9 | "strict": true, 10 | "types": ["node", "vscode"] 11 | }, 12 | "exclude": ["node_modules", ".vscode-test"] 13 | } -------------------------------------------------------------------------------- /out/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.formatTime = void 0; 4 | function formatTime(minutes) { 5 | const hours = Math.floor(minutes / 60); 6 | const mins = Math.round(minutes % 60); 7 | return `${hours}h ${mins}m`; 8 | } 9 | exports.formatTime = formatTime; 10 | // Add other utility functions as needed 11 | //# sourceMappingURL=utils.js.map -------------------------------------------------------------------------------- /src/utils.ts.md: -------------------------------------------------------------------------------- 1 | # `utils.ts` Documentation 2 | 3 | This file provides utility functions for the Coding Time Tracker extension. These helpers support common operations used throughout the extension. 4 | 5 | ## Overview 6 | - **Purpose:** Encapsulate reusable logic and helper functions. 7 | - **Main Responsibilities:** 8 | - Date and time formatting 9 | - String manipulation 10 | - Other general-purpose utilities 11 | 12 | ## Usage 13 | - Imported by other modules to avoid code duplication and improve maintainability. 14 | 15 | --- 16 | 17 | **See the source code in `src/utils.ts` for implementation details.** 18 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | # Development directories 2 | .vscode/** 3 | .vscode-test/** 4 | src/** 5 | out/test/** 6 | test/** 7 | scripts/** 8 | 9 | # Source control 10 | .git/** 11 | .github/** 12 | .gitignore 13 | 14 | # Configuration files 15 | .yarnrc 16 | tsconfig.json 17 | .eslintrc.json 18 | webpack.config.js 19 | 20 | # Build artifacts 21 | **/*.map 22 | **/*.ts 23 | **/*.vsix 24 | 25 | # Documentation (except README) 26 | CONTRIBUTING.md 27 | TECHNICAL.md 28 | 29 | # Dependencies (except type definitions) 30 | node_modules/** 31 | !node_modules/@types/ 32 | 33 | # Keep these files 34 | !LICENSE 35 | !README.md 36 | !package.json 37 | !icon-sctt.png 38 | !out/src/**/*.js 39 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | 6 | /**@type {import('webpack').Configuration}*/ 7 | module.exports = { 8 | target: 'node', 9 | mode: 'production', 10 | entry: path.resolve(__dirname, 'src', 'extension.ts'), 11 | output: { 12 | path: path.resolve(__dirname, 'dist'), 13 | filename: 'extension.js', 14 | libraryTarget: 'commonjs2' 15 | }, 16 | externals: { 17 | vscode: 'commonjs vscode' 18 | }, 19 | resolve: { 20 | extensions: ['.ts', '.js'] 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.ts$/, 26 | exclude: /node_modules/, 27 | use: [ 28 | { 29 | loader: 'ts-loader' 30 | } 31 | ] 32 | } 33 | ] 34 | }, 35 | devtool: 'nosources-source-map', 36 | infrastructureLogging: { 37 | level: "log", 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /.github/workflows/repo-stats.yml: -------------------------------------------------------------------------------- 1 | name: Repository Statistics 2 | run-name: 📊 Collect Repository Metrics 3 | 4 | on: 5 | schedule: 6 | # Runs every 2nd day at 02:00 UTC (less busy time) 7 | - cron: '0 2 */2 * *' 8 | workflow_dispatch: # Allow manual trigger for testing 9 | 10 | permissions: 11 | contents: write # Needed to write stats data 12 | pull-requests: read 13 | issues: read 14 | 15 | jobs: 16 | repo-stats: 17 | name: Generate Repository Statistics 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v4 23 | 24 | - name: Generate repository statistics 25 | uses: jgehrcke/github-repo-stats@RELEASE 26 | with: 27 | repository: ${{ github.repository }} 28 | ghtoken: ${{ secrets.REPO_STATS_TOKEN }} # Use custom PAT instead 29 | databranch: stats-data -------------------------------------------------------------------------------- /src/summaryView.ts.md: -------------------------------------------------------------------------------- 1 | # `summaryView.ts` Documentation 2 | 3 | This file implements the summary view for the Coding Time Tracker extension. It provides a UI for users to review their tracked coding time, broken down by date, project, and branch. 4 | 5 | ## Overview 6 | - **Purpose:** Display aggregated time tracking data in a user-friendly format. 7 | - **Main Responsibilities:** 8 | - Render a webview or panel with summary statistics 9 | - Allow filtering by date, project, or branch 10 | - Update the view in response to data changes 11 | 12 | ## Key Concepts 13 | - **Summary View:** 14 | - Uses VS Code's webview or custom editor API 15 | - Presents daily, project, and branch summaries 16 | - **Interactivity:** 17 | - Users can filter or refresh the data 18 | 19 | ## Usage 20 | - Invoked from extension commands or status bar actions to show the user's coding time summary. 21 | 22 | --- 23 | 24 | **See the source code in `src/summaryView.ts` for implementation details.** 25 | -------------------------------------------------------------------------------- /src/statusBar.ts.md: -------------------------------------------------------------------------------- 1 | # `statusBar.ts` Documentation 2 | 3 | This file manages the VS Code status bar item for the Coding Time Tracker extension. It displays the current tracking status and provides quick access to extension commands. 4 | 5 | ## Overview 6 | - **Purpose:** Show time tracking status and provide user interaction via the status bar. 7 | - **Main Responsibilities:** 8 | - Create and update a status bar item 9 | - Display current tracked time, project, and branch 10 | - Handle user clicks to trigger extension commands 11 | 12 | ## Key Concepts 13 | - **Status Bar Item:** 14 | - Created using VS Code's API 15 | - Updated in real-time as tracking state changes 16 | - **User Interaction:** 17 | - Clicking the status bar item can start/stop tracking or open the summary view 18 | 19 | ## Usage 20 | - Used by the main extension entry point to keep users informed and provide quick actions. 21 | 22 | --- 23 | 24 | **See the source code in `src/statusBar.ts` for implementation details.** 25 | -------------------------------------------------------------------------------- /src/timeTracker.ts.md: -------------------------------------------------------------------------------- 1 | # `timeTracker.ts` Documentation 2 | 3 | This file contains the core logic for tracking coding time in the Coding Time Tracker extension. It manages timers, detects activity, and records time entries. 4 | 5 | ## Overview 6 | - **Purpose:** Track active coding time and record it to the database. 7 | - **Main Responsibilities:** 8 | - Start and stop time tracking sessions 9 | - Detect user activity and workspace/project/branch changes 10 | - Periodically save tracked time to the database 11 | 12 | ## Key Concepts 13 | - **Timer Management:** 14 | - Uses intervals or timeouts to track elapsed time 15 | - Pauses/resumes based on user activity 16 | - **Activity Detection:** 17 | - Monitors editor events to determine if the user is actively coding 18 | - **Data Recording:** 19 | - Writes time entries to the database at regular intervals or on session end 20 | 21 | ## Usage 22 | - Called by extension commands to start/stop tracking and to handle activity events. 23 | 24 | --- 25 | 26 | **See the source code in `src/timeTracker.ts` for implementation details.** 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Simple Coding Time Tracker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/extension.ts.md: -------------------------------------------------------------------------------- 1 | # `extension.ts` Documentation 2 | 3 | This file is the main entry point for the VS Code Coding Time Tracker extension. It registers commands, initializes the database, and sets up the extension's UI components and event listeners. 4 | 5 | ## Overview 6 | - **Purpose:** Bootstrap the extension, handle activation/deactivation, and wire up commands and UI. 7 | - **Main Responsibilities:** 8 | - Register extension commands (start/stop tracking, show summary, clear data, etc.) 9 | - Initialize the `Database` and other core modules 10 | - Set up status bar and summary view 11 | - Listen for relevant VS Code events (e.g., workspace changes) 12 | 13 | ## Key Concepts 14 | - **Activation:** 15 | - The `activate` function is called when the extension is activated. 16 | - Initializes the database, status bar, and summary view. 17 | - Registers all extension commands with VS Code. 18 | - **Deactivation:** 19 | - The `deactivate` function is called when the extension is deactivated. 20 | - Used for cleanup if necessary. 21 | 22 | ## Commands Registered 23 | - Start/stop time tracking 24 | - Show summary view 25 | - Clear all tracked data 26 | - Other utility commands as needed 27 | 28 | ## Usage 29 | - This file should not contain business logic; it delegates to other modules (e.g., `database.ts`, `statusBar.ts`, `summaryView.ts`, `timeTracker.ts`). 30 | 31 | --- 32 | 33 | **See the source code in `src/extension.ts` for implementation details.** 34 | -------------------------------------------------------------------------------- /test_language_detection.js: -------------------------------------------------------------------------------- 1 | // Simple test script to verify language detection functionality 2 | // This can be removed after testing 3 | 4 | const { detectLanguageFromFile, detectLanguageFromLanguageId } = require('./dist/extension.js'); 5 | 6 | // Test file extension detection 7 | console.log('Testing file extension detection:'); 8 | console.log('test.js:', detectLanguageFromFile('test.js')); // Should be JavaScript 9 | console.log('test.ts:', detectLanguageFromFile('test.ts')); // Should be TypeScript 10 | console.log('test.py:', detectLanguageFromFile('test.py')); // Should be Python 11 | console.log('test.java:', detectLanguageFromFile('test.java')); // Should be Java 12 | console.log('test.cpp:', detectLanguageFromFile('test.cpp')); // Should be C++ 13 | console.log('test.html:', detectLanguageFromFile('test.html')); // Should be HTML 14 | console.log('test.css:', detectLanguageFromFile('test.css')); // Should be CSS 15 | console.log('test.unknown:', detectLanguageFromFile('test.unknown')); // Should be Other 16 | console.log('package.json:', detectLanguageFromFile('package.json')); // Should be JSON 17 | 18 | // Test VS Code language ID detection 19 | console.log('\nTesting VS Code language ID detection:'); 20 | console.log('javascript:', detectLanguageFromLanguageId('javascript')); // Should be JavaScript 21 | console.log('typescript:', detectLanguageFromLanguageId('typescript')); // Should be TypeScript 22 | console.log('python:', detectLanguageFromLanguageId('python')); // Should be Python 23 | console.log('csharp:', detectLanguageFromLanguageId('csharp')); // Should be C# 24 | console.log('unknown:', detectLanguageFromLanguageId('unknown')); // Should be Other 25 | -------------------------------------------------------------------------------- /out/statusBar.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"statusBar.js","sourceRoot":"","sources":["../src/statusBar.ts"],"names":[],"mappings":";;;AAAA,iCAAiC;AAEjC,mCAAqC;AAErC,MAAa,SAAS;IAMlB,YAAY,WAAwB;QAF5B,sBAAiB,GAAG,IAAI,MAAM,CAAC,YAAY,EAAQ,CAAC;QAGxD,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC5F,IAAI,CAAC,aAAa,CAAC,OAAO,GAAG,qCAAqC,CAAC;QACnE,IAAI,CAAC,aAAa,CAAC,eAAe,GAAG,IAAI,MAAM,CAAC,UAAU,CAAC,iCAAiC,CAAC,CAAC;QAC9F,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,sBAAsB;IACjG,CAAC;IAEO,eAAe;QACnB,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC;QACpD,IAAI,CAAC,aAAa,CAAC,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9D,IAAI,CAAC,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;IACvD,CAAC;IAEO,UAAU,CAAC,OAAe;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;QAC7C,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC1H,CAAC;IAEO,cAAc;QAClB,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC;QACtD,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC;QAExD,OAAO;aACF,IAAA,kBAAU,EAAC,WAAW,CAAC;cACtB,IAAA,kBAAU,EAAC,YAAY,CAAC;YAC1B,IAAA,kBAAU,EAAC,YAAY,CAAC;sBACd,CAAC;IACnB,CAAC;IAED,UAAU,CAAC,QAAoB;QAC3B,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC;IAED,OAAO;QACH,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACnC,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;IACrC,CAAC;CACJ;AAlDD,8BAkDC"} -------------------------------------------------------------------------------- /out/extension.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":";;;AAAA,iCAAiC;AACjC,+CAA4C;AAC5C,2CAAwC;AACxC,yCAAsC;AACtC,+CAAoD;AAEpD,SAAgB,QAAQ,CAAC,OAAgC;IACrD,MAAM,QAAQ,GAAG,IAAI,mBAAQ,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,IAAI,yBAAW,CAAC,QAAQ,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,IAAI,qBAAS,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,WAAW,GAAG,IAAI,iCAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;IAE5E,gCAAgC;IAChC,IAAI,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,qCAAqC,EAAE,GAAG,EAAE;QACzF,WAAW,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,uCAAuC;IACvC,IAAI,oBAAoB,GAAG,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAClG,WAAW,CAAC,UAAU,EAAE,CAAC;QACzB,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,+CAA+C,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,4CAA4C;IAC5C,IAAI,wBAAwB,GAAG,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAC1G,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,0EAA0E,EAAE,KAAK,EAAE,IAAI,CAAC;aACpH,IAAI,CAAC,SAAS,CAAC,EAAE;YACd,IAAI,SAAS,KAAK,KAAK,EAAE;gBACrB,WAAW,CAAC,cAAc,EAAE,CAAC;gBAC7B,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,2CAA2C,CAAC,CAAC;aACrF;QACL,CAAC,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACvC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACjD,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IACrD,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtC,2DAA2D;IAC3D,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE;QAC7B,WAAW,CAAC,aAAa,EAAE,CAAC;KAC/B;IAED,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAqB,EAAE,EAAE;QAC3D,IAAI,CAAC,CAAC,OAAO,EAAE;YACX,WAAW,CAAC,aAAa,EAAE,CAAC;SAC/B;aAAM;YACH,WAAW,CAAC,YAAY,EAAE,CAAC;SAC9B;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,GAAG,EAAE;QACxC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE;YAC7B,WAAW,CAAC,aAAa,EAAE,CAAC;SAC/B;IACL,CAAC,CAAC,CAAC;IAEH,kDAAkD;IAClD,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE;QACtB,WAAW,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;AACP,CAAC;AAzDD,4BAyDC;AAED,SAAgB,UAAU,KAAI,CAAC;AAA/B,gCAA+B"} -------------------------------------------------------------------------------- /out/statusBar.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.StatusBar = void 0; 4 | const vscode = require("vscode"); 5 | const utils_1 = require("./utils"); 6 | class StatusBar { 7 | constructor(timeTracker) { 8 | this.onDidClickEmitter = new vscode.EventEmitter(); 9 | this.timeTracker = timeTracker; 10 | this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100); 11 | this.statusBarItem.command = 'simpleCodingTimeTracker.showSummary'; 12 | this.statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); 13 | this.statusBarItem.show(); 14 | this.updateStatusBar(); 15 | this.updateInterval = setInterval(() => this.updateStatusBar(), 1000); // Update every second 16 | } 17 | updateStatusBar() { 18 | const todayTotal = this.timeTracker.getTodayTotal(); 19 | this.statusBarItem.text = `💻 ${this.formatTime(todayTotal)}`; 20 | this.statusBarItem.tooltip = this.getTooltipText(); 21 | } 22 | formatTime(minutes) { 23 | const hours = Math.floor(minutes / 60); 24 | const mins = Math.floor(minutes % 60); 25 | const secs = Math.floor((minutes * 60) % 60); 26 | return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; 27 | } 28 | getTooltipText() { 29 | const weeklyTotal = this.timeTracker.getWeeklyTotal(); 30 | const monthlyTotal = this.timeTracker.getMonthlyTotal(); 31 | const allTimeTotal = this.timeTracker.getAllTimeTotal(); 32 | return `Total Coding Time: 33 | This week: ${(0, utils_1.formatTime)(weeklyTotal)} 34 | This month: ${(0, utils_1.formatTime)(monthlyTotal)} 35 | All Time: ${(0, utils_1.formatTime)(allTimeTotal)} 36 | Click to show summary`; 37 | } 38 | onDidClick(listener) { 39 | return this.onDidClickEmitter.event(listener); 40 | } 41 | dispose() { 42 | clearInterval(this.updateInterval); 43 | this.statusBarItem.dispose(); 44 | this.onDidClickEmitter.dispose(); 45 | } 46 | } 47 | exports.StatusBar = StatusBar; 48 | //# sourceMappingURL=statusBar.js.map -------------------------------------------------------------------------------- /out/database.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /out/extension.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.deactivate = exports.activate = void 0; 4 | const vscode = require("vscode"); 5 | const timeTracker_1 = require("./timeTracker"); 6 | const statusBar_1 = require("./statusBar"); 7 | const database_1 = require("./database"); 8 | const summaryView_1 = require("./summaryView"); 9 | function activate(context) { 10 | const database = new database_1.Database(context); 11 | const timeTracker = new timeTracker_1.TimeTracker(database); 12 | const statusBar = new statusBar_1.StatusBar(timeTracker); 13 | const summaryView = new summaryView_1.SummaryViewProvider(context, database, timeTracker); 14 | // Register the existing command 15 | let disposable = vscode.commands.registerCommand('simpleCodingTimeTracker.showSummary', () => { 16 | summaryView.show(); 17 | }); 18 | // Register the new reset timer command 19 | let resetTimerDisposable = vscode.commands.registerCommand('simpleCodingTimeTracker.resetTimer', () => { 20 | timeTracker.resetTimer(); 21 | vscode.window.showInformationMessage('Coding time tracker has been reset for today.'); 22 | }); 23 | // Register the new reset all timers command 24 | let resetAllTimersDisposable = vscode.commands.registerCommand('simpleCodingTimeTracker.resetAllTimers', () => { 25 | vscode.window.showWarningMessage('Are you sure you want to reset all timers? This action cannot be undone.', 'Yes', 'No') 26 | .then(selection => { 27 | if (selection === 'Yes') { 28 | timeTracker.resetAllTimers(); 29 | vscode.window.showInformationMessage('All coding time trackers have been reset.'); 30 | } 31 | }); 32 | }); 33 | context.subscriptions.push(disposable); 34 | context.subscriptions.push(resetTimerDisposable); 35 | context.subscriptions.push(resetAllTimersDisposable); 36 | context.subscriptions.push(timeTracker); 37 | context.subscriptions.push(statusBar); 38 | // Start tracking immediately if VS Code is already focused 39 | if (vscode.window.state.focused) { 40 | timeTracker.startTracking(); 41 | } 42 | vscode.window.onDidChangeWindowState((e) => { 43 | if (e.focused) { 44 | timeTracker.startTracking(); 45 | } 46 | else { 47 | timeTracker.stopTracking(); 48 | } 49 | }); 50 | vscode.workspace.onDidOpenTextDocument(() => { 51 | if (vscode.window.state.focused) { 52 | timeTracker.startTracking(); 53 | } 54 | }); 55 | // Refresh summary view when status bar is clicked 56 | statusBar.onDidClick(() => { 57 | summaryView.show(); 58 | }); 59 | } 60 | exports.activate = activate; 61 | function deactivate() { } 62 | exports.deactivate = deactivate; 63 | //# sourceMappingURL=extension.js.map -------------------------------------------------------------------------------- /src/notificationStatusBar.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export class NotificationStatusBar implements vscode.Disposable { 4 | private statusBarItem: vscode.StatusBarItem; 5 | private readonly commandId = 'simpleCodingTimeTracker.toggleNotifications'; 6 | private statusBarReference?: any; // Reference to main status bar for updates 7 | 8 | constructor() { 9 | // Create status bar item with priority 99 to appear immediately to the right of time tracker (priority 100) 10 | // Use very specific priority to minimize gap 11 | this.statusBarItem = vscode.window.createStatusBarItem( 12 | vscode.StatusBarAlignment.Left, 13 | 99.9999 14 | ); 15 | 16 | this.statusBarItem.command = this.commandId; 17 | this.updateStatusBar(); 18 | this.statusBarItem.show(); 19 | } 20 | 21 | private updateStatusBar(): void { 22 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 23 | const isEnabled = config.get('health.enableNotifications', false); 24 | 25 | // Use minimal text with no extra spaces 26 | this.statusBarItem.text = isEnabled ? '🔔' : '🔕'; 27 | // this.statusBarItem.text = isEnabled ? '$(bell)' : '$(bell-dot)'; 28 | this.statusBarItem.tooltip = `Health Notifications: ${isEnabled ? 'ON' : 'OFF'} (Click to toggle)`; 29 | 30 | // Keep same background as time tracker for unified appearance 31 | this.statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); 32 | } 33 | 34 | public async toggle(): Promise { 35 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 36 | const currentEnabled = config.get('health.enableNotifications', false); 37 | 38 | // Toggle the setting 39 | await config.update('health.enableNotifications', !currentEnabled, vscode.ConfigurationTarget.Global); 40 | 41 | // Update the main status bar display 42 | if (this.statusBarReference) { 43 | await this.statusBarReference.updateNow(); 44 | } 45 | 46 | // Show brief feedback message 47 | const status = !currentEnabled ? 'enabled' : 'disabled'; 48 | const icon = !currentEnabled ? '🔔' : '🔕'; 49 | vscode.window.showInformationMessage(`${icon} Health notifications ${status}`); 50 | } 51 | 52 | public refresh(): void { 53 | this.updateStatusBar(); 54 | // Also update the main status bar when configuration changes 55 | if (this.statusBarReference) { 56 | this.statusBarReference.updateNow(); 57 | } 58 | } 59 | 60 | public setStatusBarReference(statusBar: any): void { 61 | this.statusBarReference = statusBar; 62 | } 63 | 64 | dispose(): void { 65 | this.statusBarItem.dispose(); 66 | } 67 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Simple Coding Time Tracker - GitHub Pages Site 2 | 3 | This directory contains the GitHub Pages website for the Simple Coding Time Tracker VS Code extension. 4 | 5 | ## 🌐 Live Site 6 | 7 | The site is hosted at: `https://twentytwo.github.io/vsc-ext-coding-time-tracker/` 8 | 9 | ## 📁 Structure 10 | 11 | ``` 12 | docs/ 13 | ├── index.html # Main landing page 14 | ├── documentation.html # Complete documentation 15 | ├── css/ 16 | │ └── style.css # Styles for all pages 17 | ├── js/ 18 | │ └── main.js # Interactive features 19 | └── assets/ # Additional assets (if needed) 20 | ``` 21 | 22 | ## 🚀 Features 23 | 24 | ### Landing Page (`index.html`) 25 | - Hero section with call-to-action buttons 26 | - Feature showcase with interactive cards 27 | - Screenshot gallery with light/dark theme examples 28 | - Installation guide 29 | - Use cases section 30 | - Responsive design for all devices 31 | 32 | ### Documentation Page (`documentation.html`) 33 | - Complete user guide 34 | - Configuration reference 35 | - Feature details 36 | - Health notifications guide 37 | - Command reference 38 | - Troubleshooting section 39 | - FAQ 40 | 41 | ## 🎨 Design 42 | 43 | - **Responsive:** Works on desktop, tablet, and mobile 44 | - **Modern:** Clean, professional design with smooth animations 45 | - **Accessible:** Semantic HTML and proper ARIA labels 46 | - **Fast:** Optimized CSS and JavaScript 47 | - **Theme-aware:** Matches VS Code aesthetic 48 | 49 | ## 🔧 Local Development 50 | 51 | To test the site locally: 52 | 53 | 1. Open `index.html` in your browser, or 54 | 2. Use a local server: 55 | ```bash 56 | # Python 3 57 | python -m http.server 8000 58 | 59 | # Node.js (with http-server) 60 | npx http-server 61 | ``` 62 | 3. Navigate to `http://localhost:8000` 63 | 64 | ## 📝 Updating Content 65 | 66 | ### Adding Screenshots 67 | Place images in the root directory or `assets/` folder and update the `` tags in `index.html`. 68 | 69 | ### Modifying Styles 70 | Edit `css/style.css` to customize colors, fonts, layouts, etc. 71 | 72 | ### Adding Interactivity 73 | Update `js/main.js` for new interactive features. 74 | 75 | ## 🌍 GitHub Pages Setup 76 | 77 | To enable GitHub Pages: 78 | 79 | 1. Go to your repository settings 80 | 2. Navigate to "Pages" section 81 | 3. Under "Source", select "Deploy from a branch" 82 | 4. Choose the `main` branch and `/docs` folder 83 | 5. Click "Save" 84 | 6. Your site will be available at: `https://twentytwo.github.io/vsc-ext-coding-time-tracker/` 85 | 86 | ## 📦 Technologies Used 87 | 88 | - Pure HTML5, CSS3, JavaScript (no frameworks) 89 | - Responsive Grid and Flexbox layouts 90 | - CSS animations and transitions 91 | - Intersection Observer API for scroll animations 92 | - Mobile-first responsive design 93 | 94 | ## 🤝 Contributing 95 | 96 | To improve the website: 97 | 98 | 1. Edit files in the `docs/` directory 99 | 2. Test locally 100 | 3. Commit and push changes 101 | 4. Changes will be live within minutes 102 | 103 | ## 📄 License 104 | 105 | This website and the extension are licensed under the MIT License. 106 | -------------------------------------------------------------------------------- /src/database.ts.md: -------------------------------------------------------------------------------- 1 | # `database.ts` Documentation 2 | 3 | This file implements the persistent storage and retrieval logic for the VS Code Coding Time Tracker extension. It manages time tracking entries, provides summary data, and supports search and data management operations. 4 | 5 | ## Overview 6 | - **Purpose:** Store and manage coding time entries, including date, project, time spent, and branch information. 7 | - **Storage:** Uses VS Code's `globalState` API for persistent storage across extension reloads. 8 | - **Main Class:** `Database` – encapsulates all database operations. 9 | 10 | ## Key Concepts 11 | - **TimeEntry:** 12 | - Represents a single tracked coding session. 13 | - Fields: `date` (YYYY-MM-DD), `project` (string), `timeSpent` (number, minutes), `branch` (string), `language` (string). 14 | - **SummaryData:** 15 | - Aggregated statistics for reporting (daily, per project, per branch, per language, total time). 16 | 17 | ## How It Works 18 | 19 | ### Initialization 20 | - On construction, the `Database` loads all entries from `globalState`. 21 | - If no entries exist, it initializes an empty array. 22 | - It also migrates old entries to ensure the `branch` field is present. 23 | 24 | ### Adding Entries 25 | - `addEntry(date, project, timeSpent, branch, language)` 26 | - Adds or updates a time entry for the given date, project, branch, and language. 27 | - If an entry already exists for the same date/project/branch/language, it increments the `timeSpent`. 28 | - Saves the updated entries back to persistent storage. 29 | 30 | ### Retrieving Entries 31 | - `getEntries()` 32 | - Returns all time entries from memory (reloads from storage if needed). 33 | 34 | ### Updating Entries 35 | - `updateEntries(entries)` 36 | - Internal method to update the in-memory and persistent storage with new entries. 37 | 38 | ### Summarizing Data 39 | - `getSummaryData()` 40 | - Aggregates all entries to provide: 41 | - Daily summary (total time per day) 42 | - Project summary (total time per project) 43 | - Branch summary (total time per branch) 44 | - Language summary (total time per programming language) 45 | - Total time tracked 46 | 47 | ### Searching Entries 48 | - `searchEntries(startDate?, endDate?, project?, branch?, language?)` 49 | - Returns entries matching the provided filters (date range, project, branch, language). 50 | 51 | ### Branches by Project 52 | - `getBranchesByProject(project)` 53 | - Returns a sorted list of unique branches for a given project. 54 | 55 | ### Clearing Data 56 | - `clearAllData()` 57 | - Prompts the user for confirmation before deleting all time tracking data. 58 | - If confirmed, clears all entries from storage. 59 | 60 | ## Error Handling 61 | - All storage operations are wrapped in try/catch blocks. 62 | - User is notified via VS Code notifications if an error occurs. 63 | 64 | ## Migration 65 | - On initialization, checks if any entries are missing the `branch` or `language` fields and updates them to include them (default: 'unknown'). 66 | 67 | ## Usage 68 | - The `Database` class is instantiated with the extension context and used throughout the extension to manage time tracking data. 69 | 70 | --- 71 | 72 | **See the source code in `src/database.ts` for implementation details.** 73 | -------------------------------------------------------------------------------- /out/database.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.Database = void 0; 13 | class Database { 14 | constructor(context) { 15 | this.context = context; 16 | } 17 | addEntry(date, project, timeSpent) { 18 | return __awaiter(this, void 0, void 0, function* () { 19 | const dateString = date.toISOString().split('T')[0]; 20 | const entries = this.getEntries(); 21 | const existingEntryIndex = entries.findIndex(entry => entry.date === dateString && entry.project === project); 22 | if (existingEntryIndex !== -1) { 23 | // Update existing entry 24 | entries[existingEntryIndex].timeSpent += timeSpent; 25 | } 26 | else { 27 | // Add new entry 28 | entries.push({ date: dateString, project, timeSpent }); 29 | } 30 | yield this.context.globalState.update('timeEntries', entries); 31 | }); 32 | } 33 | getEntries() { 34 | return this.context.globalState.get('timeEntries', []); 35 | } 36 | getSummaryData() { 37 | return __awaiter(this, void 0, void 0, function* () { 38 | const entries = this.getEntries(); 39 | const dailySummary = {}; 40 | const projectSummary = {}; 41 | let totalTime = 0; 42 | for (const entry of entries) { 43 | dailySummary[entry.date] = (dailySummary[entry.date] || 0) + entry.timeSpent; 44 | projectSummary[entry.project] = (projectSummary[entry.project] || 0) + entry.timeSpent; 45 | totalTime += entry.timeSpent; 46 | } 47 | return { 48 | dailySummary, 49 | projectSummary, 50 | totalTime 51 | }; 52 | }); 53 | } 54 | searchEntries(startDate, endDate, project) { 55 | return __awaiter(this, void 0, void 0, function* () { 56 | const entries = this.getEntries(); 57 | return entries.filter(entry => { 58 | const dateMatch = (!startDate || entry.date >= startDate) && (!endDate || entry.date <= endDate); 59 | const projectMatch = !project || entry.project.toLowerCase().includes(project.toLowerCase()); 60 | return dateMatch && projectMatch; 61 | }); 62 | }); 63 | } 64 | resetTodayTime() { 65 | return __awaiter(this, void 0, void 0, function* () { 66 | const today = new Date().toISOString().split('T')[0]; 67 | const entries = this.getEntries(); 68 | const updatedEntries = entries.filter(entry => entry.date !== today); 69 | yield this.context.globalState.update('timeEntries', updatedEntries); 70 | }); 71 | } 72 | resetAllTime() { 73 | return __awaiter(this, void 0, void 0, function* () { 74 | yield this.context.globalState.update('timeEntries', []); 75 | }); 76 | } 77 | } 78 | exports.Database = Database; 79 | //# sourceMappingURL=database.js.map -------------------------------------------------------------------------------- /GITHUB_PAGES_SETUP.md: -------------------------------------------------------------------------------- 1 | # 🚀 GitHub Pages Setup Guide 2 | 3 | ## Quick Setup Steps 4 | 5 | Follow these steps to enable your GitHub Pages site: 6 | 7 | ### 1. Push the docs folder to GitHub 8 | 9 | ```bash 10 | git add docs/ 11 | git commit -m "Add GitHub Pages site for extension demonstration" 12 | git push origin feature/extension-site 13 | ``` 14 | 15 | ### 2. Merge to main branch (if needed) 16 | 17 | If you want the site live on main: 18 | ```bash 19 | git checkout main 20 | git merge feature/extension-site 21 | git push origin main 22 | ``` 23 | 24 | ### 3. Enable GitHub Pages 25 | 26 | 1. Go to your repository: https://github.com/twentyTwo/vsc-ext-coding-time-tracker 27 | 2. Click on **Settings** tab 28 | 3. Scroll down to **Pages** in the left sidebar 29 | 4. Under **Source**: 30 | - Select **Deploy from a branch** 31 | - Choose **main** branch 32 | - Select **/docs** folder 33 | - Click **Save** 34 | 35 | ### 4. Wait for Deployment 36 | 37 | - GitHub will build and deploy your site (usually takes 1-2 minutes) 38 | - You'll see a message: "Your site is live at https://twentytwo.github.io/vsc-ext-coding-time-tracker/" 39 | - Click the link to view your live site! 40 | 41 | ## 🎯 What You'll Get 42 | 43 | ### Landing Page Features: 44 | ✅ Beautiful hero section with gradient background 45 | ✅ Feature showcase with 9 key features 46 | ✅ Screenshot gallery (light/dark themes) 47 | ✅ Installation instructions 48 | ✅ Use cases section 49 | ✅ Responsive design (mobile, tablet, desktop) 50 | ✅ Smooth animations and transitions 51 | ✅ Links to VS Code Marketplace and Open VSX 52 | 53 | ### Documentation Page Features: 54 | ✅ Complete user guide 55 | ✅ Configuration reference table 56 | ✅ Feature explanations 57 | ✅ Health notifications guide 58 | ✅ All available commands 59 | ✅ Troubleshooting section 60 | ✅ FAQ section 61 | ✅ Table of contents with smooth scrolling 62 | 63 | ## 🔗 Adding to Your Extension 64 | 65 | Once live, add the site URL to: 66 | 67 | 1. **README.md** - Add a link to the live demo site 68 | 2. **package.json** - Add to repository URL or homepage field 69 | 3. **VS Code Marketplace** - Update extension description with site link 70 | 71 | ## 📱 Testing Locally 72 | 73 | Before pushing, test locally: 74 | 75 | ```bash 76 | cd docs 77 | python -m http.server 8000 78 | # Or use: npx http-server 79 | ``` 80 | 81 | Open: http://localhost:8000 82 | 83 | ## 🎨 Customization Options 84 | 85 | ### Colors (in css/style.css) 86 | ```css 87 | :root { 88 | --primary-color: #007acc; /* VS Code blue */ 89 | --secondary-color: #68217a; /* Purple accent */ 90 | --accent-color: #f9826c; /* Coral accent */ 91 | } 92 | ``` 93 | 94 | ### Screenshots 95 | - Replace URLs in index.html with your own screenshot URLs 96 | - Current screenshots are pulled from your GitHub static hosting 97 | 98 | ### Content 99 | - Edit text in index.html and documentation.html 100 | - Update links to match your repository 101 | 102 | ## ⚡ Performance 103 | 104 | The site is optimized for: 105 | - Fast loading (no external dependencies) 106 | - Mobile-first responsive design 107 | - SEO-friendly semantic HTML 108 | - Accessible (ARIA labels, keyboard navigation) 109 | 110 | ## 🐛 Troubleshooting 111 | 112 | **Site not showing up?** 113 | - Wait 2-5 minutes after enabling 114 | - Check Settings → Pages for build status 115 | - Ensure /docs folder exists on the branch 116 | - Hard refresh browser (Ctrl+Shift+R) 117 | 118 | **Images not loading?** 119 | - Verify image URLs are correct 120 | - Check if images are accessible publicly 121 | - Use relative paths for local images 122 | 123 | **Mobile menu not working?** 124 | - Clear browser cache 125 | - Check browser console for errors 126 | - Verify main.js is loading 127 | 128 | ## 📞 Support 129 | 130 | Need help? Open an issue on GitHub! 131 | 132 | --- 133 | 134 | **Your site will be live at:** 135 | 🌐 https://twentytwo.github.io/vsc-ext-coding-time-tracker/ 136 | 137 | Enjoy your new extension showcase website! 🎉 138 | -------------------------------------------------------------------------------- /out/summaryView.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"summaryView.js","sourceRoot":"","sources":["../src/summaryView.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /out/timeTracker.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"timeTracker.js","sourceRoot":"","sources":["../src/timeTracker.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import * as vscode from 'vscode'; 4 | 5 | type LogEntry = { 6 | seq: number; 7 | time: string; 8 | event: string; 9 | project: string; 10 | branch: string; 11 | reason?: string; 12 | duration?: string; 13 | from?: string; 14 | to?: string; 15 | inactive_for?: string; 16 | } 17 | 18 | export class Logger { 19 | private logFilePath: string = ''; 20 | private static instance: Logger; 21 | private currentDate: string = ''; 22 | private eventCounter: number = 0; 23 | private static enableLogging: boolean = false; 24 | 25 | private constructor() { 26 | this.currentDate = this.getDateString(); 27 | this.updateLogPath(); 28 | } 29 | 30 | private updateLogPath() { 31 | if (Logger.enableLogging) { 32 | const storagePath = this.getStoragePath(); 33 | this.logFilePath = path.join(storagePath, `timetracker_${this.currentDate}.log`); 34 | this.ensureLogFileExists(); 35 | } else { 36 | this.logFilePath = ''; 37 | } 38 | } 39 | 40 | public static getInstance(): Logger { 41 | if (!Logger.instance) { 42 | Logger.instance = new Logger(); 43 | } 44 | return Logger.instance; 45 | } 46 | 47 | public static setLoggingEnabled(enabled: boolean) { 48 | Logger.enableLogging = enabled; 49 | if (Logger.instance) { 50 | Logger.instance.updateLogPath(); 51 | } 52 | } 53 | 54 | public static isLoggingEnabled(): boolean { 55 | return Logger.enableLogging; 56 | } 57 | 58 | private getStoragePath(): string { 59 | const storagePath = path.join('C:', 'VSCodeTimeTracker', 'logs'); 60 | if (!fs.existsSync(storagePath)) { 61 | fs.mkdirSync(storagePath, { recursive: true }); 62 | } 63 | return storagePath; 64 | } 65 | 66 | private getDateString(): string { 67 | const now = new Date(); 68 | return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`; 69 | } 70 | 71 | private formatTime(date: Date): string { 72 | return date.toLocaleTimeString('en-US', { 73 | hour12: false, 74 | hour: '2-digit', 75 | minute: '2-digit', 76 | second: '2-digit' 77 | }); 78 | } 79 | 80 | private formatDuration(seconds: number): string { 81 | return `${Math.round(seconds)}s`; 82 | } 83 | 84 | private ensureLogFileExists() { 85 | if (!Logger.enableLogging) { 86 | return; 87 | } 88 | const date = this.getDateString(); 89 | if (date !== this.currentDate) { 90 | this.currentDate = date; 91 | this.logFilePath = path.join(this.getStoragePath(), `timetracker_${date}.log`); 92 | this.eventCounter = 0; 93 | } 94 | if (this.logFilePath && !fs.existsSync(this.logFilePath)) { 95 | fs.writeFileSync(this.logFilePath, ''); 96 | } 97 | } 98 | 99 | public logEvent(event: string, details: Record) { 100 | if (!Logger.enableLogging) { 101 | return; 102 | } 103 | 104 | this.ensureLogFileExists(); 105 | const now = new Date(); 106 | this.eventCounter++; 107 | 108 | // Simplify the log entry 109 | const logEntry: LogEntry = { 110 | seq: this.eventCounter, 111 | time: this.formatTime(now), 112 | event, 113 | project: details.project || '', 114 | branch: details.branch || '' 115 | }; 116 | 117 | // Add relevant details based on event type 118 | switch (event) { 119 | case 'tracking_started': 120 | logEntry.reason = details.reason; 121 | break; 122 | case 'session_saved': 123 | logEntry.duration = this.formatDuration(details.duration * 60); 124 | logEntry.reason = details.reason; 125 | break; 126 | case 'branch_changed': 127 | logEntry.from = details.oldBranch; 128 | logEntry.to = details.newBranch; 129 | break; 130 | case 'inactivity_detected': 131 | logEntry.inactive_for = this.formatDuration(details.inactivityDuration); 132 | break; 133 | } 134 | 135 | try { 136 | const logLine = JSON.stringify(logEntry) + '\n'; 137 | fs.appendFileSync(this.logFilePath, logLine); 138 | } catch (error) { 139 | console.error('Failed to write to log file:', error); 140 | } 141 | } 142 | 143 | public getLogFilePath(): string { 144 | return this.logFilePath; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Simple Coding Time Tracker 2 | 3 | We're excited that you're interested in contributing to VSCode Time Tracker! This document will guide you through the process of setting up the project, making changes, and submitting your contributions. 4 | 5 | ## Project Structure 6 | 7 | The project is organized as follows: 8 | 9 | ```bash 10 | vscode-time-tracker/ 11 | ├── .vscode/ # VSCode-specific settings 12 | ├── src/ # Source code 13 | │ ├── extension.ts # Main extension file (includes test data commands) 14 | │ ├── statusBar.ts # Status bar functionality 15 | │ ├── summaryView.ts # Summary view implementation 16 | │ ├── timeTracker.ts # Time tracking logic 17 | │ ├── database.ts # Database operations 18 | │ ├── healthNotifications.ts # Health notification system 19 | │ ├── logger.ts # Logging utilities 20 | │ └── utils.ts # Utility functions 21 | ├── .gitignore # Git ignore file 22 | ├── package.json # Project metadata, dependencies, and test commands 23 | ├── README.md # Project readme 24 | ├── CONTRIBUTING.md # Contributing guidelines 25 | ├── TECHNICAL.md # Technical documentation and testing guide 26 | └── LICENSE # License information 27 | ``` 28 | 29 | ## Setting Up the Development Environment 30 | 31 | 1. Fork the repository on GitHub. 32 | 2. Clone your fork locally: 33 | ``` 34 | git clone https://github.com/your-username/vsc-ext-coding-time-tracker.git 35 | ``` 36 | 3. Navigate to the project directory: 37 | ``` 38 | cd vsc-ext-coding-time-tracker 39 | ``` 40 | 4. Install dependencies: 41 | ``` 42 | npm install 43 | ``` 44 | 45 | ## Compiling the Extension 46 | 47 | To compile the extension, run: 48 | 49 | `npm run compile` 50 | 51 | This will transpile the TypeScript files to JavaScript. 52 | 53 | ## Testing the Extension 54 | 55 | ### Development Mode Testing 56 | 57 | For testing during development: 58 | 59 | 1. **Open the project in VS Code** 60 | 2. **Press F5** to launch Extension Development Host 61 | 3. **Test your changes** in the new VS Code window 62 | 63 | ### Package Testing 64 | 65 | For testing packaged extensions: 66 | 67 | 1. **Enable test commands**: 68 | - Open Settings (`Ctrl+,`) 69 | - Search `"enableDevCommands"` 70 | - Enable "Simple Coding Time Tracker › Enable Dev Commands" 71 | 72 | 2. **Generate test data**: 73 | - Press `Ctrl+Shift+P` 74 | - Run `SCTT: Generate Test Data (Dev)` 75 | - This creates 90 days of realistic test data 76 | 77 | 3. **Test all features**: 78 | - Summary view and charts 79 | - Search and filtering 80 | - Status bar functionality 81 | - Theme compatibility 82 | 83 | 4. **Clean up**: 84 | - Run `SCTT: Delete Test Data (Dev)` to remove test data 85 | - Disable dev commands when done 86 | 87 | ### Testing Checklist 88 | 89 | Before submitting a pull request, verify: 90 | 91 | - ✅ Time tracking starts/stops correctly 92 | - ✅ Status bar updates in real-time 93 | - ✅ Charts render properly in light/dark themes 94 | - ✅ Search and filtering work correctly 95 | - ✅ Data persists across VS Code restarts 96 | - ✅ Health notifications function (if enabled) 97 | - ✅ Extension works with packaged installation 98 | - ✅ No console errors in Developer Tools 99 | 100 | ## Creating a VSCode Package 101 | 102 | To package the extension for distribution: 103 | 104 | 1. Install `vsce` globally if you haven't already: 105 | ``` 106 | npm install -g vsce 107 | ``` 108 | 2. Package the extension: 109 | ``` 110 | vsce package 111 | ``` 112 | 113 | This will create a `.vsix` file that can be installed in VSCode. 114 | 115 | ## Making Changes 116 | 117 | 1. Create a new branch for your feature or bug fix: 118 | ``` 119 | git checkout -b feature/your-feature-name 120 | ``` 121 | 2. Make your changes and commit them with a clear, descriptive commit message. 122 | 3. Push your changes to your fork: 123 | ``` 124 | git push origin feature/your-feature-name 125 | ``` 126 | 127 | ## Creating a Pull Request 128 | 129 | 1. Go to the original repository on GitHub. 130 | 2. Click on "Pull requests" and then "New pull request". 131 | 3. Select your fork and the branch containing your changes. 132 | 4. Fill out the pull request template with a clear description of your changes. 133 | 5. Submit the pull request. 134 | 135 | ## Code Style and Guidelines 136 | 137 | - Follow the existing code style in the project. 138 | - Use meaningful variable and function names. 139 | - Comment your code where necessary, especially for complex logic. 140 | - Ensure your code passes all existing tests and add new tests for new functionality. 141 | 142 | ## Questions or Need Help? 143 | 144 | If you have any questions or need assistance, please open an issue on the GitHub repository, and we'll be happy to help! 145 | 146 | Thank you for contributing to Simple Coding Time Tracker! 147 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-coding-time-tracker", 3 | "displayName": "Simple Coding Time Tracker", 4 | "description": "Track and visualize your coding time across projects", 5 | "version": "0.6.5", 6 | "publisher": "noorashuvo", 7 | "license": "MIT", 8 | "icon": "icon-sctt.png", 9 | "engines": { 10 | "vscode": "^1.60.0" 11 | }, 12 | "categories": [ 13 | "Other" 14 | ], 15 | "activationEvents": [ 16 | "onStartupFinished" 17 | ], 18 | "main": "./dist/extension.js", 19 | "contributes": { 20 | "configuration": { 21 | "title": "Simple Coding Time Tracker", 22 | "properties": { 23 | "simpleCodingTimeTracker.inactivityTimeout": { 24 | "type": "number", 25 | "default": 2.5, 26 | "minimum": 0.5, 27 | "maximum": 60, 28 | "description": "Pause tracking if there is no keyboard or mouse activity in VS Code for this many minutes." 29 | }, 30 | "simpleCodingTimeTracker.focusTimeout": { 31 | "type": "number", 32 | "default": 3, 33 | "minimum": 0.5, 34 | "maximum": 60, 35 | "description": "If you switch away from VS Code, continue counting as coding time for up to this many minutes before pausing." 36 | }, 37 | "simpleCodingTimeTracker.health.enableNotifications": { 38 | "type": "boolean", 39 | "default": false, 40 | "description": "Enable health notifications during coding sessions" 41 | }, 42 | "simpleCodingTimeTracker.health.modalNotifications": { 43 | "type": "boolean", 44 | "default": true, 45 | "description": "Make health notifications modal (blocks UI until dismissed) for better visibility" 46 | }, 47 | "simpleCodingTimeTracker.health.eyeRestInterval": { 48 | "type": "number", 49 | "default": 20, 50 | "minimum": 5, 51 | "maximum": 120, 52 | "description": "Interval for eye rest reminders (minutes) - Based on 20-20-20 rule" 53 | }, 54 | "simpleCodingTimeTracker.health.stretchInterval": { 55 | "type": "number", 56 | "default": 30, 57 | "minimum": 10, 58 | "maximum": 180, 59 | "description": "Interval for stretch reminders (minutes) - Recommended for posture health" 60 | }, 61 | "simpleCodingTimeTracker.health.breakThreshold": { 62 | "type": "number", 63 | "default": 90, 64 | "minimum": 30, 65 | "maximum": 480, 66 | "description": "Coding duration before suggesting a break (minutes) - Based on ultradian rhythms" 67 | }, 68 | "simpleCodingTimeTracker.enableDevCommands": { 69 | "type": "boolean", 70 | "default": false, 71 | "description": "Enable development commands (Generate/Delete Test Data). Only enable this for testing purposes.", 72 | "scope": "application" 73 | } 74 | } 75 | }, 76 | "commands": [ 77 | { 78 | "command": "simpleCodingTimeTracker.showSummary", 79 | "title": "SCTT: Show Coding Time Summary" 80 | }, 81 | { 82 | "command": "simpleCodingTimeTracker.viewStorageData", 83 | "title": "SCTT: View Time Tracking Data" 84 | }, 85 | { 86 | "command": "simpleCodingTimeTracker.clearAllData", 87 | "title": "SCTT: Clear All Time Tracking Data" 88 | }, 89 | { 90 | "command": "simpleCodingTimeTracker.toggleNotifications", 91 | "title": "SCTT: Toggle Notifications" 92 | }, 93 | { 94 | "command": "simpleCodingTimeTracker.toggleHealthNotifications", 95 | "title": "SCTT: Toggle Health Notifications" 96 | }, 97 | { 98 | "command": "simpleCodingTimeTracker.openSettings", 99 | "title": "SCTT: Open Settings" 100 | }, 101 | { 102 | "command": "simpleCodingTimeTracker.generateTestData", 103 | "title": "SCTT: Generate Test Data (Dev)", 104 | "when": "config.simpleCodingTimeTracker.enableDevCommands" 105 | }, 106 | { 107 | "command": "simpleCodingTimeTracker.deleteTestData", 108 | "title": "SCTT: Delete Test Data (Dev)", 109 | "when": "config.simpleCodingTimeTracker.enableDevCommands" 110 | } 111 | ] 112 | }, 113 | "scripts": { 114 | "vscode:prepublish": "npm run package", 115 | "compile": "webpack", 116 | "watch": "webpack --watch", 117 | "package": "webpack --mode production --devtool hidden-source-map", 118 | "pretest": "npm run compile && npm run lint", 119 | "lint": "eslint src --ext ts", 120 | "test": "node ./out/test/runTest.js", 121 | "refresh-extension": "code --uninstall-extension noorashuvo.simple-coding-time-tracker && vsce package && code --install-extension $(npm pack --quiet)" 122 | }, 123 | "devDependencies": { 124 | "@types/node": "^14.14.37", 125 | "@types/vscode": "^1.60.0", 126 | "@typescript-eslint/eslint-plugin": "^4.22.0", 127 | "@typescript-eslint/parser": "^4.22.0", 128 | "eslint": "^7.25.0", 129 | "ts-loader": "^9.5.2", 130 | "typescript": "^4.2.4", 131 | "webpack": "^5.99.8", 132 | "webpack-cli": "^6.0.1" 133 | }, 134 | "dependencies": { 135 | "simple-git": "^3.27.0" 136 | }, 137 | "repository": { 138 | "type": "git", 139 | "url": "https://github.com/twentyTwo/vsc-ext-coding-time-tracker.git" 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/statusBar.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { TimeTracker } from './timeTracker'; 3 | import { formatTime } from './utils'; 4 | import { SummaryViewProvider } from './summaryView'; 5 | 6 | export class StatusBar implements vscode.Disposable { 7 | private statusBarItem: vscode.StatusBarItem; 8 | private notificationItem: vscode.StatusBarItem; 9 | private timeTracker: TimeTracker; 10 | private summaryView: SummaryViewProvider; 11 | private updateInterval: NodeJS.Timeout; 12 | private readonly commandId = 'simpleCodingTimeTracker.manualSave'; 13 | private readonly notificationCommandId = 'simpleCodingTimeTracker.toggleNotifications'; 14 | 15 | constructor(timeTracker: TimeTracker, summaryView: SummaryViewProvider) { 16 | this.timeTracker = timeTracker; 17 | this.summaryView = summaryView; 18 | this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100); 19 | this.notificationItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 99.9999); 20 | 21 | // Register manual save command (clicking anywhere saves session) 22 | const commandDisposable = vscode.commands.registerCommand(this.commandId, () => { 23 | if (this.timeTracker.isActive()) { 24 | // Save current session with manual save reason 25 | this.timeTracker.saveCurrentSession('manual status bar click'); 26 | 27 | // Show summary view after saving 28 | this.summaryView.show(); 29 | 30 | // Show confirmation to user 31 | vscode.window.showInformationMessage('Time entry saved and summary view opened'); 32 | } 33 | }); 34 | 35 | // Register notification toggle command 36 | const notificationCommandDisposable = vscode.commands.registerCommand(this.notificationCommandId, () => { 37 | this.toggleNotifications(); 38 | }); 39 | 40 | 41 | // Set up main status bar item 42 | this.statusBarItem.command = this.commandId; 43 | this.statusBarItem.tooltip = 'Click to save current session and show summary'; 44 | this.statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); 45 | this.statusBarItem.show(); 46 | 47 | // Set up notification status bar item 48 | this.notificationItem.command = this.notificationCommandId; 49 | this.notificationItem.tooltip = 'Click to toggle health notifications'; 50 | this.notificationItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); 51 | this.notificationItem.show(); 52 | 53 | void this.updateStatusBar(); 54 | this.updateInterval = setInterval(() => void this.updateStatusBar(), 1000); // Update every second 55 | } private async updateStatusBar() { 56 | const todayTotal = await this.timeTracker.getTodayTotal(); 57 | const currentProjectTime = await this.timeTracker.getCurrentProjectTime(); 58 | const isActive = this.timeTracker.isActive(); 59 | 60 | // Update main status bar (time tracker only) 61 | this.statusBarItem.text = `${isActive ? '💻' : '⏸️'} ${this.formatTime(todayTotal)}`; 62 | this.statusBarItem.tooltip = await this.getTooltipText(isActive, currentProjectTime); 63 | 64 | // Update notification status bar 65 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 66 | const notificationsEnabled = config.get('health.enableNotifications', false); 67 | const notificationIcon = notificationsEnabled ? '🔔' : '🔕'; 68 | this.notificationItem.text = notificationIcon; 69 | this.notificationItem.tooltip = `Health Notifications: ${notificationsEnabled ? 'ON' : 'OFF'} (Click to toggle)`; 70 | } 71 | 72 | private formatTime(minutes: number): string { 73 | const hours = Math.floor(minutes / 60); 74 | const mins = Math.floor(minutes % 60); 75 | const secs = Math.floor((minutes * 60) % 60); 76 | return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; 77 | } 78 | 79 | private async getTooltipText(isActive: boolean, currentProjectTime: number): Promise { 80 | const weeklyTotal = await this.timeTracker.getWeeklyTotal(); 81 | const monthlyTotal = await this.timeTracker.getMonthlyTotal(); 82 | const allTimeTotal = await this.timeTracker.getAllTimeTotal(); 83 | const currentBranch = this.timeTracker.getCurrentBranch(); 84 | const currentProject = this.timeTracker.getCurrentProject(); 85 | 86 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 87 | const notificationsEnabled = config.get('health.enableNotifications', false); 88 | const notificationStatus = notificationsEnabled ? 'ON' : 'OFF'; 89 | 90 | return `${isActive ? 'Active' : 'Paused'} - Coding Time 91 | Project: ${currentProject} 92 | Branch: ${currentBranch} 93 | Current Project Today: ${formatTime(currentProjectTime)} 94 | This week total: ${formatTime(weeklyTotal)} 95 | This month total: ${formatTime(monthlyTotal)} 96 | All Time total: ${formatTime(allTimeTotal)} 97 | Notifications: ${notificationStatus} 98 | Click to save session and show summary`; 99 | } 100 | 101 | 102 | // Toggle notifications method 103 | private async toggleNotifications(): Promise { 104 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 105 | const currentEnabled = config.get('health.enableNotifications', false); 106 | 107 | // Toggle the setting 108 | await config.update('health.enableNotifications', !currentEnabled, vscode.ConfigurationTarget.Global); 109 | 110 | // Update the visual state 111 | await this.updateStatusBar(); 112 | 113 | // Show brief feedback message 114 | const status = !currentEnabled ? 'enabled' : 'disabled'; 115 | const icon = !currentEnabled ? '🔔' : '🔕'; 116 | vscode.window.showInformationMessage(`${icon} Health notifications ${status}`); 117 | } 118 | 119 | // Public method to force immediate update 120 | async updateNow() { 121 | await this.updateStatusBar(); 122 | } 123 | 124 | dispose() { 125 | this.statusBarItem.dispose(); 126 | this.notificationItem.dispose(); 127 | clearInterval(this.updateInterval); 128 | } 129 | } -------------------------------------------------------------------------------- /out/timeTracker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.TimeTracker = void 0; 13 | const vscode = require("vscode"); 14 | class TimeTracker { 15 | constructor(database) { 16 | this.isTracking = false; 17 | this.startTime = 0; 18 | this.currentProject = ''; 19 | this.updateInterval = null; 20 | this.saveInterval = null; 21 | this.database = database; 22 | this.saveIntervalSeconds = vscode.workspace 23 | .getConfiguration('simpleCodingTimeTracker') 24 | .get('saveInterval', 5); 25 | } 26 | startTracking() { 27 | if (!this.isTracking) { 28 | this.isTracking = true; 29 | this.startTime = Date.now(); 30 | this.currentProject = this.getCurrentProject(); 31 | this.updateInterval = setInterval(() => this.updateCurrentSession(), 1000); 32 | this.saveInterval = setInterval(() => this.saveCurrentSession(), this.saveIntervalSeconds * 1000); // Convert seconds to milliseconds 33 | } 34 | } 35 | stopTracking() { 36 | if (this.isTracking) { 37 | this.isTracking = false; 38 | if (this.updateInterval) { 39 | clearInterval(this.updateInterval); 40 | this.updateInterval = null; 41 | } 42 | if (this.saveInterval) { 43 | clearInterval(this.saveInterval); 44 | this.saveInterval = null; 45 | } 46 | this.saveCurrentSession(); 47 | } 48 | } 49 | updateCurrentSession() { 50 | // This method will be called every second when tracking is active 51 | // You can emit an event here if you want to update the UI more frequently 52 | } 53 | saveCurrentSession() { 54 | return __awaiter(this, void 0, void 0, function* () { 55 | if (this.isTracking) { 56 | const duration = (Date.now() - this.startTime) / 60000; // Convert to minutes 57 | yield this.database.addEntry(new Date(), this.currentProject, duration); 58 | this.startTime = Date.now(); // Reset the start time for the next interval 59 | } 60 | }); 61 | } 62 | getCurrentProject() { 63 | const workspaceFolders = vscode.workspace.workspaceFolders; 64 | return workspaceFolders ? workspaceFolders[0].name : 'Unknown Project'; 65 | } 66 | getTodayTotal() { 67 | const today = new Date().toISOString().split('T')[0]; 68 | const entries = this.database.getEntries(); 69 | const todayTotal = entries 70 | .filter((entry) => entry.date === today) 71 | .reduce((sum, entry) => sum + entry.timeSpent, 0); 72 | // Add the current session time if tracking is active 73 | if (this.isTracking) { 74 | const currentSessionTime = (Date.now() - this.startTime) / 60000; 75 | return todayTotal + currentSessionTime; 76 | } 77 | return todayTotal; 78 | } 79 | getCurrentProjectTime() { 80 | const today = new Date().toISOString().split('T')[0]; 81 | const currentProject = this.getCurrentProject(); 82 | const entries = this.database.getEntries(); 83 | const currentProjectTime = entries 84 | .filter((entry) => entry.date === today && entry.project === currentProject) 85 | .reduce((sum, entry) => sum + entry.timeSpent, 0); 86 | // Add the current session time if tracking is active 87 | if (this.isTracking && this.currentProject === currentProject) { 88 | const currentSessionTime = (Date.now() - this.startTime) / 60000; 89 | return currentProjectTime + currentSessionTime; 90 | } 91 | return currentProjectTime; 92 | } 93 | getWeeklyTotal() { 94 | const now = new Date(); 95 | const startOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay()); 96 | return this.getTotalSince(startOfWeek); 97 | } 98 | getMonthlyTotal() { 99 | const now = new Date(); 100 | const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); 101 | return this.getTotalSince(startOfMonth); 102 | } 103 | getAllTimeTotal() { 104 | const total = this.database.getEntries() 105 | .reduce((sum, entry) => sum + entry.timeSpent, 0); 106 | // Add current session if tracking is active 107 | if (this.isTracking) { 108 | const currentSessionTime = (Date.now() - this.startTime) / 60000; 109 | return total + currentSessionTime; 110 | } 111 | return total; 112 | } 113 | getTotalSince(startDate) { 114 | const entries = this.database.getEntries(); 115 | const startDateString = startDate.toISOString().split('T')[0]; 116 | const now = new Date().toISOString().split('T')[0]; 117 | const filteredEntries = entries.filter(entry => entry.date >= startDateString && entry.date <= now); 118 | const total = filteredEntries.reduce((sum, entry) => sum + entry.timeSpent, 0); 119 | // Add current session if tracking is active 120 | if (this.isTracking) { 121 | const currentSessionTime = (Date.now() - this.startTime) / 60000; 122 | return total + currentSessionTime; 123 | } 124 | return total; 125 | } 126 | getYearlyTotal() { 127 | const now = new Date(); 128 | const startOfYear = new Date(now.getFullYear(), 0, 1); // January 1st of current year 129 | return this.getTotalSince(startOfYear); 130 | } 131 | dispose() { 132 | this.stopTracking(); 133 | } 134 | resetTimer() { 135 | this.stopTracking(); 136 | this.startTime = 0; 137 | this.database.resetTodayTime(); 138 | } 139 | resetAllTimers() { 140 | this.stopTracking(); 141 | this.startTime = 0; 142 | this.database.resetAllTime(); 143 | } 144 | } 145 | exports.TimeTracker = TimeTracker; 146 | //# sourceMappingURL=timeTracker.js.map -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function formatTime(minutes: number): string { 2 | const hours = Math.floor(minutes / 60); 3 | const mins = Math.round(minutes % 60); 4 | return `${hours}h ${mins}m`; 5 | } 6 | 7 | /** 8 | * Detects programming language from file extension 9 | */ 10 | export function detectLanguageFromFile(filePath: string): string { 11 | if (!filePath) { 12 | return 'unknown'; 13 | } 14 | 15 | const extension = filePath.split('.').pop()?.toLowerCase(); 16 | 17 | // Map file extensions to language names 18 | const languageMap: { [key: string]: string } = { 19 | // JavaScript/TypeScript family 20 | 'js': 'JavaScript', 21 | 'mjs': 'JavaScript', 22 | 'jsx': 'JavaScript', 23 | 'ts': 'TypeScript', 24 | 'tsx': 'TypeScript', 25 | 26 | // Python 27 | 'py': 'Python', 28 | 'pyw': 'Python', 29 | 'pyc': 'Python', 30 | 'pyo': 'Python', 31 | 'pyd': 'Python', 32 | 33 | // Java/JVM languages 34 | 'java': 'Java', 35 | 'kt': 'Kotlin', 36 | 'kts': 'Kotlin', 37 | 'scala': 'Scala', 38 | 'groovy': 'Groovy', 39 | 40 | // C family 41 | 'c': 'C', 42 | 'h': 'C', 43 | 'cpp': 'C++', 44 | 'cxx': 'C++', 45 | 'cc': 'C++', 46 | 'hpp': 'C++', 47 | 'hxx': 'C++', 48 | 'cs': 'C#', 49 | 50 | // Web technologies 51 | 'html': 'HTML', 52 | 'htm': 'HTML', 53 | 'css': 'CSS', 54 | 'scss': 'SCSS', 55 | 'sass': 'Sass', 56 | 'less': 'Less', 57 | 'vue': 'Vue', 58 | 'svelte': 'Svelte', 59 | 60 | // PHP 61 | 'php': 'PHP', 62 | 'php3': 'PHP', 63 | 'php4': 'PHP', 64 | 'php5': 'PHP', 65 | 'phps': 'PHP', 66 | 'phtml': 'PHP', 67 | 68 | // Ruby 69 | 'rb': 'Ruby', 70 | 'rbw': 'Ruby', 71 | 72 | // Go 73 | 'go': 'Go', 74 | 75 | // Rust 76 | 'rs': 'Rust', 77 | 78 | // Swift 79 | 'swift': 'Swift', 80 | 81 | // Dart 82 | 'dart': 'Dart', 83 | 84 | // Shell scripts 85 | 'sh': 'Shell', 86 | 'bash': 'Shell', 87 | 'zsh': 'Shell', 88 | 'fish': 'Shell', 89 | 'ps1': 'PowerShell', 90 | 'psm1': 'PowerShell', 91 | 'psd1': 'PowerShell', 92 | 93 | // SQL 94 | 'sql': 'SQL', 95 | 96 | // R 97 | 'r': 'R', 98 | 99 | // MATLAB 100 | 'm': 'MATLAB', 101 | 102 | // Lua 103 | 'lua': 'Lua', 104 | 105 | // Perl 106 | 'pl': 'Perl', 107 | 'pm': 'Perl', 108 | 109 | // Haskell 110 | 'hs': 'Haskell', 111 | 112 | // Elixir 113 | 'ex': 'Elixir', 114 | 'exs': 'Elixir', 115 | 116 | // F# 117 | 'fs': 'F#', 118 | 'fsx': 'F#', 119 | 'fsi': 'F#', 120 | 121 | // Clojure 122 | 'clj': 'Clojure', 123 | 'cljs': 'Clojure', 124 | 'cljc': 'Clojure', 125 | 126 | // Configuration files 127 | 'json': 'JSON', 128 | 'xml': 'XML', 129 | 'yaml': 'YAML', 130 | 'yml': 'YAML', 131 | 'toml': 'TOML', 132 | 'ini': 'INI', 133 | 'cfg': 'Config', 134 | 'conf': 'Config', 135 | 136 | // Documentation 137 | 'md': 'Markdown', 138 | 'markdown': 'Markdown', 139 | 'rst': 'reStructuredText', 140 | 'tex': 'LaTeX', 141 | 142 | // Other 143 | 'dockerfile': 'Dockerfile', 144 | 'makefile': 'Makefile', 145 | 'cmake': 'CMake', 146 | 'gradle': 'Gradle', 147 | 'properties': 'Properties' 148 | }; 149 | 150 | if (extension && languageMap[extension]) { 151 | return languageMap[extension]; 152 | } 153 | 154 | // Check for special filenames without extensions 155 | const filename = filePath.split(/[\\\/]/).pop()?.toLowerCase(); 156 | if (filename) { 157 | if (filename === 'dockerfile') return 'Dockerfile'; 158 | if (filename === 'makefile') return 'Makefile'; 159 | if (filename === 'cmakelists.txt') return 'CMake'; 160 | if (filename === 'package.json') return 'JSON'; 161 | if (filename === 'tsconfig.json') return 'JSON'; 162 | if (filename.includes('requirements.txt')) return 'Text'; 163 | if (filename.includes('.env')) return 'Environment'; 164 | } 165 | 166 | return 'Other'; 167 | } 168 | 169 | /** 170 | * Detects programming language from VS Code language ID 171 | */ 172 | export function detectLanguageFromLanguageId(languageId: string): string { 173 | if (!languageId) { 174 | return 'unknown'; 175 | } 176 | 177 | // Map VS Code language IDs to our language names 178 | const languageIdMap: { [key: string]: string } = { 179 | 'javascript': 'JavaScript', 180 | 'javascriptreact': 'JavaScript', 181 | 'typescript': 'TypeScript', 182 | 'typescriptreact': 'TypeScript', 183 | 'python': 'Python', 184 | 'java': 'Java', 185 | 'kotlin': 'Kotlin', 186 | 'scala': 'Scala', 187 | 'c': 'C', 188 | 'cpp': 'C++', 189 | 'csharp': 'C#', 190 | 'html': 'HTML', 191 | 'css': 'CSS', 192 | 'scss': 'SCSS', 193 | 'sass': 'Sass', 194 | 'less': 'Less', 195 | 'vue': 'Vue', 196 | 'svelte': 'Svelte', 197 | 'php': 'PHP', 198 | 'ruby': 'Ruby', 199 | 'go': 'Go', 200 | 'rust': 'Rust', 201 | 'swift': 'Swift', 202 | 'dart': 'Dart', 203 | 'shellscript': 'Shell', 204 | 'powershell': 'PowerShell', 205 | 'sql': 'SQL', 206 | 'r': 'R', 207 | 'matlab': 'MATLAB', 208 | 'lua': 'Lua', 209 | 'perl': 'Perl', 210 | 'haskell': 'Haskell', 211 | 'elixir': 'Elixir', 212 | 'fsharp': 'F#', 213 | 'clojure': 'Clojure', 214 | 'json': 'JSON', 215 | 'jsonc': 'JSON', 216 | 'xml': 'XML', 217 | 'yaml': 'YAML', 218 | 'toml': 'TOML', 219 | 'ini': 'INI', 220 | 'markdown': 'Markdown', 221 | 'latex': 'LaTeX', 222 | 'dockerfile': 'Dockerfile', 223 | 'makefile': 'Makefile', 224 | 'cmake': 'CMake', 225 | 'gradle': 'Gradle', 226 | 'properties': 'Properties', 227 | 'plaintext': 'Text', 228 | 'text': 'Text' 229 | }; 230 | 231 | return languageIdMap[languageId.toLowerCase()] || 'Other'; 232 | } 233 | 234 | // Add other utility functions as needed -------------------------------------------------------------------------------- /TECHNICAL.md: -------------------------------------------------------------------------------- 1 | # Technical Documentation 2 | 3 | This document contains technical details about the Simple Coding Time Tracker VS Code extension, including development setup, release processes, and internal architecture. 4 | 5 | ## Current Status 6 | 7 | **Version**: 0.6.3 | **Marketplaces**: [VS Code](https://marketplace.visualstudio.com/items?itemName=noorashuvo.simple-coding-time-tracker) | [Open VSX](https://open-vsx.org/extension/noorashuvo/simple-coding-time-tracker) | **Website**: [GitHub Pages](https://twentytwo.github.io/vsc-ext-coding-time-tracker/) 8 | 9 | ## Development Setup 10 | 11 | ### Prerequisites 12 | - Node.js 18 or higher 13 | - Visual Studio Code 14 | - Git 15 | 16 | ### Project Structure 17 | ``` 18 | vsc-ext-coding-time-tracker/ 19 | ├── src/ # Source code 20 | │ ├── extension.ts # Main entry point 21 | │ ├── timeTracker.ts # Core tracking logic 22 | │ ├── database.ts # Data persistence 23 | │ ├── summaryView.ts # Charts & visualization 24 | │ ├── healthNotifications.ts # Health reminders 25 | │ └── [other components] # statusBar, settingsView, logger, utils 26 | ├── docs/ # Website (on site branch) 27 | ├── .github/workflows/ # CI/CD pipelines 28 | └── package.json # Extension configuration 29 | ``` 30 | 31 | ## Branch Management 32 | 33 | The repository uses a multi-branch strategy for organized development and deployment: 34 | 35 | | Branch | Purpose | Deployment | 36 | |--------|---------|------------| 37 | | `main` | Production releases | VS Code Marketplace, Open VSX | 38 | | `develop` | Beta releases & testing | Pre-release testing | 39 | | `site` | GitHub Pages website | https://twentytwo.github.io/vsc-ext-coding-time-tracker/ | 40 | | `stats-data` | Repository statistics | Data storage only | 41 | 42 | ## Release Process 43 | 44 | The extension uses GitHub Actions to automate the release process. There are two types of releases supported: 45 | 46 | ### Beta Releases 47 | 48 | Beta releases are pre-release versions used for testing new features or changes before a stable release. 49 | 50 | To create a beta release: 51 | ```bash 52 | git tag v-beta. 53 | git push origin v-beta. 54 | ``` 55 | 56 | Example: 57 | ```bash 58 | git tag v3.2.1-beta.1 59 | git push origin v3.2.1-beta.1 60 | ``` 61 | 62 | For multiple beta releases of the same version, increment the beta number: 63 | - v3.2.1-beta.1 64 | - v3.2.1-beta.2 65 | - v3.2.1-beta.3 66 | 67 | ### Production Releases 68 | 69 | Production releases are stable versions ready for general use. 70 | 71 | To create a production release: 72 | ```bash 73 | git tag v 74 | git push origin v 75 | ``` 76 | 77 | Example: 78 | ```bash 79 | git tag v3.2.1 80 | git push origin v3.2.1 81 | ``` 82 | 83 | ### Automated Actions 84 | 85 | When a tag is pushed, the following automated actions are performed: 86 | 87 | 1. **Build Process**: 88 | - Checks out the code 89 | - Sets up Node.js environment 90 | - Installs dependencies 91 | - Compiles the TypeScript code 92 | - Packages the VS Code extension (.vsix file) 93 | 94 | 2. **Release Creation**: 95 | - Creates a GitHub release 96 | - Attaches the .vsix package to the release 97 | - Sets appropriate release metadata: 98 | - For beta releases: 99 | - Marked as "pre-release" 100 | - Tagged with "🧪 Beta Release" 101 | - Includes beta warning message 102 | - For production releases: 103 | - Marked as full release 104 | - Tagged with "🚀 Release" 105 | - Includes stable release message 106 | - Links to CHANGELOG.md for detailed changes 107 | 108 | 3. **Extension Publishing**: 109 | - Automatically publishes the extension to the Visual Studio Code Marketplace 110 | - Updates the extension version and metadata 111 | 112 | ### Best Practices 113 | 114 | - Create beta releases from feature branches when testing new functionality 115 | - Create production releases only from the main branch 116 | - Always update the CHANGELOG.md before creating a new release 117 | - Keep version numbers consistent between beta and production releases 118 | - Follow semantic versioning (MAJOR.MINOR.PATCH) 119 | - Test beta releases thoroughly before creating a production release 120 | 121 | ### Release Files 122 | 123 | The release process is defined in two GitHub Actions workflow files: 124 | 125 | 1. `.github/workflows/release.yml`: Handles the release creation process 126 | 2. `.github/workflows/publish.yml`: Handles publishing to the VS Code Marketplace 127 | 128 | ## Internal Architecture 129 | 130 | ### Core Components 131 | 132 | - **Time Tracker**: Activity detection, inactivity/focus timeouts, Git branch tracking, 50+ language support 133 | - **Database**: Local storage, CRUD operations, filtering, export 134 | - **Summary View**: Interactive charts (project, timeline, heatmap, languages), theme-aware 135 | - **Health Notifications**: Eye rest (20-20-20), stretch, break reminders with snooze 136 | - **Status Bar**: Real-time display, tooltips, click to open summary 137 | 138 | ### Git Branch Tracking 139 | 140 | The extension uses the `simple-git` library to monitor git branch changes and associate coding time with specific branches: 141 | 142 | 1. **Initialization** 143 | - The git watcher is initialized in `timeTracker.ts` through the `setupGitWatcher()` method 144 | - It checks if the current workspace is a git repository 145 | - Sets up an interval to periodically check for branch changes 146 | 147 | 2. **Branch Monitoring** 148 | - Checks for branch changes every 5 seconds (optimized from 1 second to reduce CPU load) 149 | - Uses `git.branch()` to get the current branch name 150 | - When a branch change is detected, the current session is saved and a new one is started 151 | 152 | 3. **Optimization Considerations** 153 | - The interval is cleared and recreated when appropriate to prevent multiple overlapping intervals 154 | - A locking mechanism prevents concurrent executions of branch checking 155 | - Proper cleanup is implemented when the extension is deactivated 156 | 157 | ### Data Flow 158 | 159 | 1. User activity triggers events in VS Code 160 | 2. TimeTracker processes these events and tracks active time 161 | 3. Data is periodically saved to the Database 162 | 4. StatusBar updates in real-time 163 | 5. SummaryView queries the Database for visualization 164 | 165 | ### Configuration 166 | 167 | **Key Settings:** 168 | - `inactivityTimeout`: 2.5 min (pause on inactivity) 169 | - `focusTimeout`: 3 min (continue after focus loss) 170 | - Health notifications: eye rest (20m), stretch (30m), breaks (90m) 171 | - `enableDevCommands`: false (test data generation) 172 | 173 | **Access:** Settings button in summary view OR VS Code settings 174 | 175 | ## Contributing 176 | 177 | For detailed contribution guidelines, please see [CONTRIBUTING.md](CONTRIBUTING.md). 178 | 179 | ## Testing 180 | 181 | ### Dev Commands 182 | Enable `enableDevCommands` → `SCTT: Generate Test Data (Dev)` creates 90 days of realistic data 183 | 184 | ### Manual Testing 185 | F5 launches Extension Development Host for testing core features 186 | 187 | ## GitHub Pages Website 188 | 189 | Site: https://twentytwo.github.io/vsc-ext-coding-time-tracker/ (on `site` branch) 190 | See [WEBSITE.md](WEBSITE.md) for maintenance 191 | 192 | ## Documentation References 193 | 194 | - **Branch Management**: [BRANCHES.md](BRANCHES.md) - Complete branch workflow guide 195 | - **Website Maintenance**: [WEBSITE.md](WEBSITE.md) - Detailed website documentation 196 | - **GitHub Pages Setup**: [GITHUB_PAGES_SETUP.md](GITHUB_PAGES_SETUP.md) - Initial deployment guide 197 | - **Contributing Guidelines**: [CONTRIBUTING.md](CONTRIBUTING.md) - How to contribute to the project 198 | - **User Documentation**: Available on the [GitHub Pages site](https://twentytwo.github.io/vsc-ext-coding-time-tracker/documentation.html) -------------------------------------------------------------------------------- /src/database.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export interface TimeEntry { 4 | date: string; 5 | project: string; 6 | timeSpent: number; 7 | branch: string; 8 | language: string; 9 | } 10 | 11 | export interface SummaryData { 12 | dailySummary: { [date: string]: number }; 13 | projectSummary: { [project: string]: number }; 14 | branchSummary: { [branch: string]: number }; 15 | languageSummary: { [language: string]: number }; 16 | totalTime: number; 17 | } 18 | 19 | export class Database { 20 | private context: vscode.ExtensionContext; 21 | private entries: TimeEntry[] | null = null; 22 | 23 | constructor(context: vscode.ExtensionContext) { 24 | this.context = context; 25 | // Initialize storage if empty 26 | if (!this.context.globalState.get('timeEntries')) { 27 | this.context.globalState.update('timeEntries', []); 28 | } 29 | // Load entries into memory 30 | this.entries = this.context.globalState.get('timeEntries', []); 31 | 32 | // Migrate existing entries to include branch if needed 33 | this.migrateEntries(); 34 | } 35 | 36 | private async migrateEntries() { 37 | if (this.entries && this.entries.length > 0) { 38 | const needsBranchMigration = this.entries.some(entry => !('branch' in entry)); 39 | const needsLanguageMigration = this.entries.some(entry => !('language' in entry)); 40 | 41 | if (needsBranchMigration || needsLanguageMigration) { 42 | const migratedEntries = this.entries.map(entry => ({ 43 | ...entry, 44 | branch: 'branch' in entry ? entry.branch : 'unknown', 45 | language: 'language' in entry ? entry.language : 'unknown' 46 | })); 47 | await this.updateEntries(migratedEntries); 48 | } 49 | } 50 | } 51 | 52 | private getLocalDateString(date: Date): string { 53 | return new Date(date.getTime() - (date.getTimezoneOffset() * 60000)) 54 | .toISOString() 55 | .split('T')[0]; 56 | } 57 | 58 | async addEntry(date: Date, project: string, timeSpent: number, branch: string, language: string) { 59 | // Validate time spent to prevent unreasonable values 60 | if (timeSpent <= 0 || timeSpent > 24 * 60) { // More than 24 hours is unlikely 61 | console.warn(`Suspicious time value detected: ${timeSpent} minutes for ${project}/${branch}/${language}`); 62 | return; 63 | } 64 | 65 | const dateString = this.getLocalDateString(date); 66 | const entries = this.getEntries(); 67 | 68 | const existingEntryIndex = entries.findIndex(entry => 69 | entry.date === dateString && 70 | entry.project === project && 71 | entry.branch === branch && 72 | entry.language === language 73 | ); 74 | 75 | // Round to 2 decimal places to avoid floating point issues 76 | const roundedTime = Math.round(timeSpent * 100) / 100; 77 | 78 | if (existingEntryIndex !== -1) { 79 | entries[existingEntryIndex].timeSpent += roundedTime; 80 | } else { 81 | entries.push({ date: dateString, project, timeSpent: roundedTime, branch, language }); 82 | } 83 | 84 | try { 85 | await this.updateEntries(entries); 86 | console.log(`Saved ${roundedTime}min for ${project}/${branch}/${language} on ${dateString}`); 87 | } catch (error) { 88 | console.error('Error saving entry:', error); 89 | vscode.window.showErrorMessage('Failed to save time entry'); 90 | } 91 | } 92 | 93 | getEntries(): TimeEntry[] { 94 | if (!this.entries) { 95 | this.entries = this.context.globalState.get('timeEntries', []); 96 | } 97 | return this.entries; 98 | } 99 | 100 | private async updateEntries(entries: TimeEntry[]): Promise { 101 | this.entries = entries; 102 | await this.context.globalState.update('timeEntries', entries); 103 | } 104 | 105 | async getSummaryData(): Promise { 106 | const entries = this.getEntries(); 107 | const dailySummary: { [date: string]: number } = {}; 108 | const projectSummary: { [project: string]: number } = {}; 109 | const branchSummary: { [branch: string]: number } = {}; 110 | const languageSummary: { [language: string]: number } = {}; 111 | let totalTime = 0; 112 | 113 | for (const entry of entries) { 114 | dailySummary[entry.date] = (dailySummary[entry.date] || 0) + entry.timeSpent; 115 | projectSummary[entry.project] = (projectSummary[entry.project] || 0) + entry.timeSpent; 116 | branchSummary[entry.branch] = (branchSummary[entry.branch] || 0) + entry.timeSpent; 117 | languageSummary[entry.language] = (languageSummary[entry.language] || 0) + entry.timeSpent; 118 | totalTime += entry.timeSpent; 119 | } 120 | 121 | return { 122 | dailySummary, 123 | projectSummary, 124 | branchSummary, 125 | languageSummary, 126 | totalTime 127 | }; 128 | } 129 | 130 | async searchEntries(startDate?: string, endDate?: string, project?: string, branch?: string, language?: string): Promise { 131 | const entries = this.getEntries(); 132 | return entries.filter(entry => { 133 | const dateMatch = (!startDate || entry.date >= startDate) && (!endDate || entry.date <= endDate); 134 | const projectMatch = !project || entry.project.toLowerCase().includes(project.toLowerCase()); 135 | const branchMatch = !branch || entry.branch.toLowerCase().includes(branch.toLowerCase()); 136 | const languageMatch = !language || entry.language.toLowerCase().includes(language.toLowerCase()); 137 | return dateMatch && projectMatch && branchMatch && languageMatch; 138 | }); 139 | } 140 | 141 | async getBranchesByProject(project: string): Promise { 142 | const entries = await this.getEntries(); 143 | const branchSet = new Set( 144 | entries 145 | .filter(entry => entry.project === project) 146 | .map(entry => entry.branch) 147 | ); 148 | return Array.from(branchSet).sort(); 149 | } 150 | 151 | async clearAllData(): Promise { 152 | // First confirm with a warning dialog 153 | const warningResult = await vscode.window.showWarningMessage( 154 | 'Are you sure you want to delete all time tracking data? This cannot be undone.', 155 | { modal: true }, 156 | 'Delete All Data', 157 | 'Cancel' 158 | ); 159 | 160 | if (warningResult !== 'Delete All Data') { 161 | return false; 162 | } 163 | 164 | // Then require typing confirmation for extra safety 165 | const response = await vscode.window.showInputBox({ 166 | prompt: 'Type "DELETE ALL DATA" to confirm permanent deletion of all time tracking data.', 167 | placeHolder: 'DELETE ALL DATA', 168 | ignoreFocusOut: true 169 | }); 170 | 171 | if (response !== 'DELETE ALL DATA') { 172 | vscode.window.showInformationMessage('Data deletion cancelled.'); 173 | return false; 174 | } 175 | 176 | try { 177 | // Clear memory cache 178 | this.entries = []; 179 | 180 | // Clear persistent storage 181 | await this.context.globalState.update('timeEntries', []); 182 | 183 | // Show success message 184 | vscode.window.showInformationMessage('All time tracking data has been cleared successfully.'); 185 | return true; 186 | } catch (error) { 187 | console.error('Error clearing data:', error); 188 | vscode.window.showErrorMessage('Failed to clear time tracking data: ' + (error instanceof Error ? error.message : 'Unknown error')); 189 | return false; 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /src/healthNotifications.ts.md: -------------------------------------------------------------------------------- 1 | # Health Notifications Implementation 2 | 3 | This document explains the implementation of the health notification system in the Simple Coding Time Tracker extension. 4 | 5 | ## Overview 6 | 7 | The health notification system provides proactive reminders to promote healthy coding habits through: 8 | - Eye rest reminders (20-20-20 rule) 9 | - Stretch reminders for physical health 10 | - Break suggestions for mental wellness 11 | - One-click pause functionality 12 | 13 | ## Architecture 14 | 15 | ### Core Components 16 | 17 | #### 1. HealthNotificationManager Class 18 | Located in `src/healthNotifications.ts` 19 | 20 | ```typescript 21 | export class HealthNotificationManager { 22 | private eyeRestTimer?: any; 23 | private stretchTimer?: any; 24 | private breakTimer?: any; 25 | private settings!: HealthNotificationSettings; 26 | private onPauseCallback?: () => void; 27 | private isActive: boolean = false; 28 | } 29 | ``` 30 | 31 | #### 2. Configuration Interface 32 | ```typescript 33 | export interface HealthNotificationSettings { 34 | eyeRestInterval: number; // minutes 35 | stretchInterval: number; // minutes 36 | breakThreshold: number; // minutes 37 | enableNotifications: boolean; 38 | } 39 | ``` 40 | 41 | ### Integration Points 42 | 43 | #### TimeTracker Integration 44 | The health notification manager is integrated into the existing `TimeTracker` class: 45 | 46 | ```typescript 47 | export class TimeTracker { 48 | private healthManager: HealthNotificationManager; 49 | 50 | constructor(database: Database) { 51 | // ... existing code ... 52 | this.healthManager = new HealthNotificationManager(() => this.pauseTimer()); 53 | } 54 | } 55 | ``` 56 | 57 | #### Configuration Settings 58 | Added to `package.json` configuration: 59 | 60 | ```json 61 | { 62 | "simpleCodingTimeTracker.health.enableNotifications": { 63 | "type": "boolean", 64 | "default": true, 65 | "description": "Enable health notifications during coding sessions" 66 | }, 67 | "simpleCodingTimeTracker.health.eyeRestInterval": { 68 | "type": "number", 69 | "default": 20, 70 | "description": "Interval for eye rest reminders (minutes)" 71 | }, 72 | "simpleCodingTimeTracker.health.stretchInterval": { 73 | "type": "number", 74 | "default": 45, 75 | "description": "Interval for stretch reminders (minutes)" 76 | }, 77 | "simpleCodingTimeTracker.health.breakThreshold": { 78 | "type": "number", 79 | "default": 120, 80 | "description": "Coding duration before suggesting a break (minutes)" 81 | } 82 | } 83 | ``` 84 | 85 | ## Implementation Details 86 | 87 | ### Enhanced Notification Visibility 88 | 89 | The system uses a **three-tier notification approach** for optimal user attention: 90 | 91 | 1. **Information Level**: Not used for health notifications (too subtle) 92 | 2. **Warning Level**: Used for eye rest and stretch reminders 93 | - Orange/yellow color for visibility 94 | - Non-blocking but persistent 95 | - Clear action buttons for user choice 96 | 3. **Error Level Modal**: Used for break notifications 97 | - Red color for urgency 98 | - Modal behavior blocks UI until addressed 99 | - Multiple break duration options 100 | 101 | ### Persistence Strategy 102 | 103 | All notifications are designed to be **persistent** rather than auto-dismissing: 104 | - No timeout-based dismissal 105 | - User must actively interact with notification 106 | - Prevents accidental ignoring of health reminders 107 | - Maintains user control through clear action buttons 108 | 109 | ### Timer Management 110 | 111 | The system uses three independent timers: 112 | 113 | 1. **Eye Rest Timer**: Triggers every 20 minutes (configurable) 114 | 2. **Stretch Timer**: Triggers every 30 minutes (configurable) 115 | 3. **Break Timer**: Triggers every 90 minutes (configurable) 116 | 117 | Each timer is implemented using `setTimeout` with automatic restart: 118 | 119 | ```typescript 120 | private startEyeRestReminder(): void { 121 | this.eyeRestTimer = setTimeout(() => { 122 | this.showEyeRestNotification(); 123 | // Restart timer for next reminder 124 | if (this.isActive) { 125 | this.startEyeRestReminder(); 126 | } 127 | }, this.settings.eyeRestInterval * 60 * 1000); 128 | } 129 | ``` 130 | 131 | ### Notification Types 132 | 133 | #### 1. Eye Rest Notification 134 | - Shows **warning-level message** for prominence (orange/yellow color) 135 | - Includes "Pause Timer" and "Got it!" buttons 136 | - **Non-blocking** but persistent until dismissed 137 | 138 | ```typescript 139 | private async showEyeRestNotification(): Promise { 140 | const pauseAction = 'Pause Timer'; 141 | const dismissAction = 'Got it!'; 142 | const result = await vscode.window.showWarningMessage( 143 | '👁️ EYE HEALTH REMINDER: Look at something 20 feet away for 20 seconds (20-20-20 rule)', 144 | { modal: false }, // Non-blocking but prominent 145 | pauseAction, 146 | dismissAction 147 | ); 148 | 149 | if (result === pauseAction && this.onPauseCallback) { 150 | this.onPauseCallback(); 151 | } 152 | } 153 | ``` 154 | 155 | #### 2. Stretch Notification 156 | - Similar pattern to eye rest with **warning-level visibility** 157 | - **Persistent** until user interaction 158 | 159 | #### 3. Break Notification 160 | - **Error-level modal notification** for maximum attention (red color) 161 | - **Blocks UI** until user responds - cannot be ignored 162 | - Multiple break options available 163 | - User must choose an option to proceed 164 | 165 | ```typescript 166 | private async showBreakNotification(): Promise { 167 | const result = await vscode.window.showErrorMessage( 168 | '🚨 HEALTH BREAK REQUIRED: You\'ve been coding for 2+ hours! Take a break now for your health.', 169 | { modal: true }, // Modal - blocks UI until dismissed 170 | pauseAction, 171 | quickStretch, 172 | eyeRest, 173 | coffeeBreak, 174 | properBreak 175 | ); 176 | // ... handling logic 177 | } 178 | ``` 179 | 180 | ### Lifecycle Management 181 | 182 | #### Startup 183 | ```typescript 184 | async startTracking(reason: string = 'manual') { 185 | if (!this.isTracking) { 186 | // ... existing tracking logic ... 187 | 188 | // Start health notifications 189 | this.healthManager.start(); 190 | } 191 | } 192 | ``` 193 | 194 | #### Shutdown 195 | ```typescript 196 | stopTracking(reason?: string) { 197 | if (this.isTracking) { 198 | // ... existing stopping logic ... 199 | 200 | // Stop health notifications 201 | this.healthManager.stop(); 202 | } 203 | } 204 | ``` 205 | 206 | #### Pause Functionality 207 | ```typescript 208 | pauseTimer() { 209 | this.stopTracking('health notification pause'); 210 | vscode.window.showInformationMessage('Timer paused. It will resume when you start typing again.'); 211 | } 212 | ``` 213 | 214 | ### Configuration Updates 215 | 216 | The system responds to configuration changes in real-time: 217 | 218 | ```typescript 219 | public updateConfiguration() { 220 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 221 | // ... existing configuration ... 222 | 223 | // Update health notification settings 224 | if (this.healthManager) { 225 | this.healthManager.updateSettings(); 226 | } 227 | } 228 | ``` 229 | 230 | ## Commands 231 | 232 | ### Toggle Health Notifications 233 | Registered in `extension.ts`: 234 | 235 | ```typescript 236 | let toggleHealthCommand = vscode.commands.registerCommand('simpleCodingTimeTracker.toggleHealthNotifications', async () => { 237 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 238 | const currentEnabled = config.get('health.enableNotifications', true); 239 | await config.update('health.enableNotifications', !currentEnabled, vscode.ConfigurationTarget.Global); 240 | 241 | const message = !currentEnabled ? 242 | 'Health notifications enabled! 💡 You\'ll receive reminders for eye rest, stretching, and breaks.' : 243 | 'Health notifications disabled.'; 244 | vscode.window.showInformationMessage(message); 245 | }); 246 | ``` 247 | 248 | ## Error Handling 249 | 250 | The system includes robust error handling: 251 | - Graceful timer cleanup on disposal 252 | - Safe configuration loading with defaults 253 | - Proper null checking for callbacks 254 | 255 | ## Performance Considerations 256 | 257 | - **Lightweight Timers**: Uses efficient setTimeout approach 258 | - **Minimal Resource Usage**: Only active when time tracking is active 259 | - **Automatic Cleanup**: Properly disposes of resources when not needed 260 | - **Strategic Notification Levels**: 261 | - Warning-level for regular reminders (non-blocking) 262 | - Error-level modal for critical breaks (blocking but necessary) 263 | - **Persistent Design**: Prevents notification dismissal accidents while maintaining user control 264 | 265 | ## Testing 266 | 267 | To test the health notification system: 268 | 269 | 1. Enable notifications in settings 270 | 2. Start coding to activate time tracking 271 | 3. Wait for notifications (or temporarily reduce intervals for testing) 272 | 4. Verify pause functionality works correctly 273 | 5. Test configuration changes take effect immediately 274 | 275 | ## Future Enhancements 276 | 277 | Potential improvements for the health notification system: 278 | - Different notification styles (modal vs toast) 279 | - Custom health tips rotation 280 | - Integration with Do Not Disturb modes 281 | - Break duration tracking 282 | - Health statistics and insights 283 | -------------------------------------------------------------------------------- /docs/js/main.js: -------------------------------------------------------------------------------- 1 | // Mobile Menu Toggle 2 | document.addEventListener('DOMContentLoaded', function() { 3 | const mobileMenuToggle = document.querySelector('.mobile-menu-toggle'); 4 | const navLinks = document.querySelector('.nav-links'); 5 | 6 | if (mobileMenuToggle) { 7 | mobileMenuToggle.addEventListener('click', function() { 8 | navLinks.classList.toggle('active'); 9 | 10 | // Animate hamburger icon 11 | const spans = this.querySelectorAll('span'); 12 | spans[0].style.transform = navLinks.classList.contains('active') ? 13 | 'rotate(-45deg) translate(-5px, 6px)' : 'none'; 14 | spans[1].style.opacity = navLinks.classList.contains('active') ? '0' : '1'; 15 | spans[2].style.transform = navLinks.classList.contains('active') ? 16 | 'rotate(45deg) translate(-5px, -6px)' : 'none'; 17 | }); 18 | } 19 | 20 | // Close mobile menu when clicking on a link 21 | const navLinkItems = document.querySelectorAll('.nav-links a'); 22 | navLinkItems.forEach(link => { 23 | link.addEventListener('click', () => { 24 | if (navLinks.classList.contains('active')) { 25 | navLinks.classList.remove('active'); 26 | const spans = mobileMenuToggle.querySelectorAll('span'); 27 | spans[0].style.transform = 'none'; 28 | spans[1].style.opacity = '1'; 29 | spans[2].style.transform = 'none'; 30 | } 31 | }); 32 | }); 33 | 34 | // Smooth scrolling for anchor links 35 | document.querySelectorAll('a[href^="#"]').forEach(anchor => { 36 | anchor.addEventListener('click', function (e) { 37 | e.preventDefault(); 38 | const target = document.querySelector(this.getAttribute('href')); 39 | if (target) { 40 | const offsetTop = target.offsetTop - 80; // Account for fixed navbar 41 | window.scrollTo({ 42 | top: offsetTop, 43 | behavior: 'smooth' 44 | }); 45 | } 46 | }); 47 | }); 48 | 49 | // Intersection Observer for fade-in animations 50 | const observerOptions = { 51 | threshold: 0.1, 52 | rootMargin: '0px 0px -100px 0px' 53 | }; 54 | 55 | const observer = new IntersectionObserver(function(entries) { 56 | entries.forEach(entry => { 57 | if (entry.isIntersecting) { 58 | entry.target.style.opacity = '1'; 59 | entry.target.style.transform = 'translateY(0)'; 60 | } 61 | }); 62 | }, observerOptions); 63 | 64 | // Observe feature cards 65 | const featureCards = document.querySelectorAll('.feature-card'); 66 | featureCards.forEach((card, index) => { 67 | card.style.opacity = '0'; 68 | card.style.transform = 'translateY(30px)'; 69 | card.style.transition = `opacity 0.6s ease-out ${index * 0.1}s, transform 0.6s ease-out ${index * 0.1}s`; 70 | observer.observe(card); 71 | }); 72 | 73 | // Observe use case cards 74 | const useCases = document.querySelectorAll('.use-case'); 75 | useCases.forEach((useCase, index) => { 76 | useCase.style.opacity = '0'; 77 | useCase.style.transform = 'translateY(30px)'; 78 | useCase.style.transition = `opacity 0.6s ease-out ${index * 0.1}s, transform 0.6s ease-out ${index * 0.1}s`; 79 | observer.observe(useCase); 80 | }); 81 | 82 | // Observe installation methods 83 | const installMethods = document.querySelectorAll('.install-method'); 84 | installMethods.forEach((method, index) => { 85 | method.style.opacity = '0'; 86 | method.style.transform = 'translateY(30px)'; 87 | method.style.transition = `opacity 0.6s ease-out ${index * 0.2}s, transform 0.6s ease-out ${index * 0.2}s`; 88 | observer.observe(method); 89 | }); 90 | 91 | // Navbar scroll effect 92 | let lastScroll = 0; 93 | const navbar = document.querySelector('.navbar'); 94 | 95 | window.addEventListener('scroll', () => { 96 | const currentScroll = window.pageYOffset; 97 | 98 | if (currentScroll <= 0) { 99 | navbar.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)'; 100 | } else { 101 | navbar.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; 102 | } 103 | 104 | // Optional: Hide navbar on scroll down, show on scroll up 105 | // if (currentScroll > lastScroll && currentScroll > 100) { 106 | // navbar.style.transform = 'translateY(-100%)'; 107 | // } else { 108 | // navbar.style.transform = 'translateY(0)'; 109 | // } 110 | 111 | lastScroll = currentScroll; 112 | }); 113 | 114 | // Add loading effect for images 115 | const images = document.querySelectorAll('img'); 116 | images.forEach(img => { 117 | img.addEventListener('load', function() { 118 | this.style.opacity = '1'; 119 | }); 120 | 121 | // Set initial opacity 122 | if (!img.complete) { 123 | img.style.opacity = '0'; 124 | img.style.transition = 'opacity 0.3s ease-in'; 125 | } 126 | }); 127 | 128 | // Parallax effect for hero section (subtle) 129 | const hero = document.querySelector('.hero'); 130 | if (hero) { 131 | window.addEventListener('scroll', () => { 132 | const scrolled = window.pageYOffset; 133 | const rate = scrolled * 0.5; 134 | hero.style.transform = `translate3d(0, ${rate}px, 0)`; 135 | }); 136 | } 137 | 138 | // Copy code snippets functionality (if needed in future) 139 | const codeBlocks = document.querySelectorAll('code'); 140 | codeBlocks.forEach(code => { 141 | code.style.cursor = 'pointer'; 142 | code.title = 'Click to copy'; 143 | 144 | code.addEventListener('click', function() { 145 | const text = this.textContent; 146 | navigator.clipboard.writeText(text).then(() => { 147 | // Show temporary tooltip 148 | const tooltip = document.createElement('span'); 149 | tooltip.textContent = 'Copied!'; 150 | tooltip.style.cssText = ` 151 | position: absolute; 152 | background: #007acc; 153 | color: white; 154 | padding: 4px 8px; 155 | border-radius: 4px; 156 | font-size: 12px; 157 | margin-left: 10px; 158 | animation: fadeOut 2s forwards; 159 | `; 160 | 161 | this.parentElement.style.position = 'relative'; 162 | this.parentElement.appendChild(tooltip); 163 | 164 | setTimeout(() => tooltip.remove(), 2000); 165 | }); 166 | }); 167 | }); 168 | 169 | // Add CSS for fadeOut animation 170 | const style = document.createElement('style'); 171 | style.textContent = ` 172 | @keyframes fadeOut { 173 | 0% { opacity: 1; transform: translateY(0); } 174 | 70% { opacity: 1; transform: translateY(0); } 175 | 100% { opacity: 0; transform: translateY(-10px); } 176 | } 177 | `; 178 | document.head.appendChild(style); 179 | 180 | // Stats counter animation (when in viewport) 181 | const stats = document.querySelectorAll('.stat-number'); 182 | const statsObserver = new IntersectionObserver((entries) => { 183 | entries.forEach(entry => { 184 | if (entry.isIntersecting && entry.target.textContent !== 'Auto' && entry.target.textContent !== 'Smart') { 185 | const finalValue = entry.target.textContent; 186 | if (!isNaN(finalValue.replace('+', ''))) { 187 | animateCounter(entry.target, 0, parseInt(finalValue.replace('+', '')), 2000); 188 | } 189 | statsObserver.unobserve(entry.target); 190 | } 191 | }); 192 | }, { threshold: 0.5 }); 193 | 194 | stats.forEach(stat => statsObserver.observe(stat)); 195 | 196 | function animateCounter(element, start, end, duration) { 197 | const range = end - start; 198 | const increment = range / (duration / 16); 199 | let current = start; 200 | 201 | const timer = setInterval(() => { 202 | current += increment; 203 | if (current >= end) { 204 | element.textContent = end + '+'; 205 | clearInterval(timer); 206 | } else { 207 | element.textContent = Math.floor(current) + '+'; 208 | } 209 | }, 16); 210 | } 211 | 212 | // Add active state to current nav link based on scroll position 213 | const sections = document.querySelectorAll('section[id]'); 214 | 215 | function highlightNavigation() { 216 | const scrollY = window.pageYOffset; 217 | 218 | sections.forEach(section => { 219 | const sectionHeight = section.offsetHeight; 220 | const sectionTop = section.offsetTop - 100; 221 | const sectionId = section.getAttribute('id'); 222 | const navLink = document.querySelector(`.nav-links a[href="#${sectionId}"]`); 223 | 224 | if (navLink && scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) { 225 | document.querySelectorAll('.nav-links a').forEach(link => { 226 | link.style.borderBottomColor = 'transparent'; 227 | }); 228 | navLink.style.borderBottomColor = 'var(--primary-color)'; 229 | } 230 | }); 231 | } 232 | 233 | window.addEventListener('scroll', highlightNavigation); 234 | 235 | console.log('Simple Coding Time Tracker - Website loaded successfully! 🚀'); 236 | }); 237 | -------------------------------------------------------------------------------- /src/healthNotifications.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export interface HealthNotificationSettings { 4 | eyeRestInterval: number; // minutes 5 | stretchInterval: number; // minutes 6 | breakThreshold: number; // minutes 7 | enableNotifications: boolean; 8 | modalNotifications: boolean; // Make all notifications modal 9 | } 10 | 11 | export class HealthNotificationManager { 12 | private eyeRestTimer?: any; 13 | private stretchTimer?: any; 14 | private breakTimer?: any; 15 | private settings!: HealthNotificationSettings; 16 | private onPauseCallback?: () => void; 17 | private onResumeCallback?: () => void; 18 | private isActive: boolean = false; 19 | 20 | constructor(onPauseCallback?: () => void, onResumeCallback?: () => void) { 21 | this.onPauseCallback = onPauseCallback; 22 | this.onResumeCallback = onResumeCallback; 23 | this.loadSettings(); 24 | } 25 | 26 | private loadSettings(): void { 27 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 28 | this.settings = { 29 | eyeRestInterval: config.get('health.eyeRestInterval', 20), 30 | stretchInterval: config.get('health.stretchInterval', 30), 31 | breakThreshold: config.get('health.breakThreshold', 90), 32 | enableNotifications: config.get('health.enableNotifications', false), 33 | modalNotifications: config.get('health.modalNotifications', true) 34 | }; 35 | } 36 | 37 | public updateSettings(): void { 38 | this.loadSettings(); 39 | if (this.isActive) { 40 | this.stop(); 41 | this.start(); 42 | } 43 | } 44 | 45 | public start(): void { 46 | if (!this.settings.enableNotifications || this.isActive) { 47 | return; 48 | } 49 | 50 | this.isActive = true; 51 | this.startEyeRestReminder(); 52 | this.startStretchReminder(); 53 | this.startBreakReminder(); 54 | } 55 | 56 | public stop(): void { 57 | if (!this.isActive) { 58 | return; 59 | } 60 | 61 | this.isActive = false; 62 | this.clearAllTimers(); 63 | } 64 | 65 | private clearAllTimers(): void { 66 | if (this.eyeRestTimer) { 67 | clearTimeout(this.eyeRestTimer); 68 | this.eyeRestTimer = undefined; 69 | } 70 | if (this.stretchTimer) { 71 | clearTimeout(this.stretchTimer); 72 | this.stretchTimer = undefined; 73 | } 74 | if (this.breakTimer) { 75 | clearTimeout(this.breakTimer); 76 | this.breakTimer = undefined; 77 | } 78 | } 79 | 80 | private startEyeRestReminder(): void { 81 | this.eyeRestTimer = setTimeout(() => { 82 | this.showEyeRestNotification(); 83 | // Restart timer for next reminder 84 | if (this.isActive) { 85 | this.startEyeRestReminder(); 86 | } 87 | }, this.settings.eyeRestInterval * 60 * 1000); 88 | } 89 | 90 | private startStretchReminder(): void { 91 | this.stretchTimer = setTimeout(() => { 92 | this.showStretchNotification(); 93 | // Restart timer for next reminder 94 | if (this.isActive) { 95 | this.startStretchReminder(); 96 | } 97 | }, this.settings.stretchInterval * 60 * 1000); 98 | } 99 | 100 | private startBreakReminder(): void { 101 | this.breakTimer = setTimeout(() => { 102 | this.showBreakNotification(); 103 | // Restart timer for next reminder 104 | if (this.isActive) { 105 | this.startBreakReminder(); 106 | } 107 | }, this.settings.breakThreshold * 60 * 1000); 108 | } 109 | 110 | private async showEyeRestNotification(): Promise { 111 | const remindAction = 'Remind me in 5 min'; 112 | const doneAction = 'I just did it!'; 113 | 114 | // Auto-pause timer when modal appears (if modal is enabled) 115 | if (this.settings.modalNotifications && this.onPauseCallback) { 116 | console.log('Eye rest modal: Auto-pausing timer'); 117 | this.onPauseCallback(); 118 | } 119 | 120 | const result = await vscode.window.showWarningMessage( 121 | '👁️ EYE HEALTH REMINDER: Look at something 20 feet away for 20 seconds (20-20-20 rule)', 122 | { modal: this.settings.modalNotifications }, 123 | doneAction, 124 | remindAction 125 | ); 126 | 127 | // Auto-resume timer when modal disappears (if modal is enabled) 128 | if (this.settings.modalNotifications && this.onResumeCallback) { 129 | console.log('Eye rest modal: Auto-resuming timer'); 130 | this.onResumeCallback(); 131 | } 132 | 133 | if (result === remindAction) { 134 | console.log('Eye rest notification: Snooze requested for 5 minutes'); 135 | // Restart eye rest timer in 5 minutes instead of full interval 136 | if (this.eyeRestTimer) { 137 | clearTimeout(this.eyeRestTimer); 138 | } 139 | this.eyeRestTimer = setTimeout(() => { 140 | this.showEyeRestNotification(); 141 | // After snooze, restart normal timer 142 | if (this.isActive) { 143 | this.startEyeRestReminder(); 144 | } 145 | }, 5 * 60 * 1000); // 5 minutes 146 | vscode.window.showInformationMessage('⏰ Eye rest reminder snoozed for 5 minutes'); 147 | } else if (result === doneAction) { 148 | console.log('Eye rest notification: User completed eye rest exercise'); 149 | vscode.window.showInformationMessage('👏 Excellent! Your eyes thank you for the break. Keep protecting your vision!'); 150 | } 151 | } 152 | 153 | private async showStretchNotification(): Promise { 154 | const remindAction = 'Remind me in 10 min'; 155 | const doneAction = 'I just stretched!'; 156 | 157 | // Auto-pause timer when modal appears (if modal is enabled) 158 | if (this.settings.modalNotifications && this.onPauseCallback) { 159 | console.log('Stretch modal: Auto-pausing timer'); 160 | this.onPauseCallback(); 161 | } 162 | 163 | const result = await vscode.window.showWarningMessage( 164 | '🧘 STRETCH REMINDER: Stand up and stretch your back and neck - Your body needs it!', 165 | { modal: this.settings.modalNotifications }, 166 | doneAction, 167 | remindAction 168 | ); 169 | 170 | // Auto-resume timer when modal disappears (if modal is enabled) 171 | if (this.settings.modalNotifications && this.onResumeCallback) { 172 | console.log('Stretch modal: Auto-resuming timer'); 173 | this.onResumeCallback(); 174 | } 175 | 176 | if (result === remindAction) { 177 | console.log('Stretch notification: Snooze requested for 10 minutes'); 178 | // Restart stretch timer in 10 minutes instead of full interval 179 | if (this.stretchTimer) { 180 | clearTimeout(this.stretchTimer); 181 | } 182 | this.stretchTimer = setTimeout(() => { 183 | this.showStretchNotification(); 184 | // After snooze, restart normal timer 185 | if (this.isActive) { 186 | this.startStretchReminder(); 187 | } 188 | }, 10 * 60 * 1000); // 10 minutes 189 | vscode.window.showInformationMessage('⏰ Stretch reminder snoozed for 10 minutes'); 190 | } else if (result === doneAction) { 191 | console.log('Stretch notification: User completed stretching'); 192 | vscode.window.showInformationMessage('💪 Amazing! Your back and neck feel better already. Keep up the healthy habits!'); 193 | } 194 | } 195 | 196 | private async showBreakNotification(): Promise { 197 | const remindAction = 'Remind me in 15 min'; 198 | const doneAction = 'I took a break!'; 199 | 200 | // Auto-pause timer when modal appears (if modal is enabled) 201 | if (this.settings.modalNotifications && this.onPauseCallback) { 202 | console.log('Break modal: Auto-pausing timer'); 203 | this.onPauseCallback(); 204 | } 205 | 206 | const result = await vscode.window.showErrorMessage( 207 | '🚨 HEALTH BREAK REQUIRED: You\'ve been coding intensely! Take a break now for your health.', 208 | { modal: this.settings.modalNotifications }, 209 | doneAction, 210 | remindAction 211 | ); 212 | 213 | // Auto-resume timer when modal disappears (if modal is enabled) 214 | if (this.settings.modalNotifications && this.onResumeCallback) { 215 | console.log('Break modal: Auto-resuming timer'); 216 | this.onResumeCallback(); 217 | } 218 | 219 | if (result === remindAction) { 220 | console.log('Break notification: Snooze requested for 15 minutes'); 221 | // Restart break timer in 15 minutes instead of full interval 222 | if (this.breakTimer) { 223 | clearTimeout(this.breakTimer); 224 | } 225 | this.breakTimer = setTimeout(() => { 226 | this.showBreakNotification(); 227 | // After snooze, restart normal timer 228 | if (this.isActive) { 229 | this.startBreakReminder(); 230 | } 231 | }, 15 * 60 * 1000); // 15 minutes 232 | vscode.window.showInformationMessage('⏰ Break reminder snoozed for 15 minutes'); 233 | } else if (result === doneAction) { 234 | console.log('Break notification: User took a proper break'); 235 | vscode.window.showInformationMessage('🌟 Outstanding! You prioritized your health. Your mind and body are recharged and ready to code!'); 236 | } 237 | } 238 | 239 | public dispose(): void { 240 | this.stop(); 241 | } 242 | 243 | // Test method to trigger notifications for debugging 244 | public triggerTestNotification(): void { 245 | console.log('Triggering test eye rest notification'); 246 | this.showEyeRestNotification(); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { TimeTracker } from './timeTracker'; 3 | import { StatusBar } from './statusBar'; 4 | import { Database } from './database'; 5 | import { SummaryViewProvider } from './summaryView'; 6 | import { SettingsViewProvider } from './settingsView'; 7 | 8 | export function activate(context: vscode.ExtensionContext) { 9 | const database = new Database(context); 10 | const timeTracker = new TimeTracker(database); 11 | const summaryView = new SummaryViewProvider(context, database, timeTracker); 12 | const settingsView = new SettingsViewProvider(context); 13 | const statusBar = new StatusBar(timeTracker, summaryView); 14 | 15 | // Register cursor tracking 16 | context.subscriptions.push( 17 | vscode.window.onDidChangeTextEditorSelection(() => { 18 | timeTracker.updateCursorActivity(); 19 | }) 20 | ); 21 | 22 | // Register configuration change listener 23 | context.subscriptions.push( 24 | vscode.workspace.onDidChangeConfiguration(e => { 25 | if (e.affectsConfiguration('simpleCodingTimeTracker')) { 26 | timeTracker.updateConfiguration(); 27 | // Update status bar when health notifications setting changes 28 | if (e.affectsConfiguration('simpleCodingTimeTracker.health.enableNotifications')) { 29 | statusBar.updateNow(); 30 | } 31 | } 32 | }) 33 | ); 34 | 35 | // Register the show summary command 36 | let disposable = vscode.commands.registerCommand('simpleCodingTimeTracker.showSummary', () => { 37 | summaryView.show(); 38 | }); 39 | 40 | // Register open settings command 41 | let openSettingsCommand = vscode.commands.registerCommand('simpleCodingTimeTracker.openSettings', () => { 42 | settingsView.show(); 43 | }); 44 | 45 | // Register view storage command 46 | let viewStorageDisposable = vscode.commands.registerCommand('simpleCodingTimeTracker.viewStorageData', async () => { 47 | try { 48 | const entries = await database.getEntries(); 49 | const processedData = { 50 | totalEntries: entries.length, 51 | exportDate: new Date().toISOString(), 52 | entries: entries.map(entry => ({ 53 | ...entry, 54 | timeSpentFormatted: `${Math.round(entry.timeSpent)} minutes` 55 | })) 56 | }; 57 | 58 | // Create a temporary untitled document 59 | const document = await vscode.workspace.openTextDocument({ 60 | content: JSON.stringify(processedData, null, 2), 61 | language: 'json' 62 | }); 63 | 64 | await vscode.window.showTextDocument(document, { 65 | preview: false, 66 | viewColumn: vscode.ViewColumn.One 67 | }); 68 | 69 | vscode.window.showInformationMessage('Time tracking data loaded successfully'); } catch (error: any) { 70 | vscode.window.showErrorMessage(`Failed to view data: ${error?.message || 'Unknown error'}`); 71 | } 72 | }); 73 | 74 | // Register data management command 75 | let clearDataCommand = vscode.commands.registerCommand('simpleCodingTimeTracker.clearAllData', async () => { 76 | // Stop tracking before clearing data 77 | if (timeTracker.isActive()) { 78 | timeTracker.stopTracking('clear all data'); 79 | } 80 | 81 | const success = await database.clearAllData(); 82 | if (success) { 83 | // Update the summary view to show empty state 84 | await summaryView.show(); 85 | // Force status bar update 86 | statusBar.updateNow(); 87 | } 88 | }); 89 | 90 | 91 | // Register health notifications toggle command (legacy command with confirmation) 92 | let toggleHealthCommand = vscode.commands.registerCommand('simpleCodingTimeTracker.toggleHealthNotifications', async () => { 93 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 94 | const currentEnabled = config.get('health.enableNotifications', false); 95 | 96 | // Show current state and ask for confirmation 97 | const action = currentEnabled ? 'disable' : 'enable'; 98 | const icon = currentEnabled ? '🔕' : '🔔'; 99 | const statusText = currentEnabled ? 'currently ENABLED' : 'currently DISABLED'; 100 | 101 | const message = `Health notifications are ${statusText}. ${icon} ${action.charAt(0).toUpperCase() + action.slice(1)} them?`; 102 | const confirmAction = currentEnabled ? 'Disable' : 'Enable'; 103 | 104 | const choice = await vscode.window.showInformationMessage( 105 | message, 106 | { modal: false }, 107 | confirmAction, 108 | 'Cancel' 109 | ); 110 | 111 | if (choice === confirmAction) { 112 | await config.update('health.enableNotifications', !currentEnabled, vscode.ConfigurationTarget.Global); 113 | 114 | const resultMessage = !currentEnabled ? 115 | '$(bell) Health notifications enabled! You\'ll receive reminders for eye rest (20min), stretching (45min), and breaks (2h).' : 116 | '$(bell-slash) Health notifications disabled. No health reminders will be shown.'; 117 | 118 | vscode.window.showInformationMessage(resultMessage); 119 | 120 | // Update status bar to reflect the change 121 | statusBar.updateNow(); 122 | } 123 | }); 124 | 125 | // Register test pause command for debugging 126 | let testPauseCommand = vscode.commands.registerCommand('simpleCodingTimeTracker.testPause', () => { 127 | console.log('Test pause command executed'); 128 | if (timeTracker.isActive()) { 129 | timeTracker.pauseTimer(); 130 | vscode.window.showInformationMessage('Test pause executed - check console for logs'); 131 | } else { 132 | vscode.window.showInformationMessage('Timer is not active'); 133 | } 134 | }); 135 | 136 | // Register test notification command for debugging 137 | let testNotificationCommand = vscode.commands.registerCommand('simpleCodingTimeTracker.testNotification', () => { 138 | console.log('Test notification command executed'); 139 | (timeTracker as any).healthManager.triggerTestNotification(); 140 | }); 141 | 142 | // Test data generation command 143 | let generateTestDataCommand = vscode.commands.registerCommand('simpleCodingTimeTracker.generateTestData', async () => { 144 | // Check if dev commands are enabled 145 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 146 | const enableDevCommands = config.get('enableDevCommands', false) || 147 | context.extensionMode === vscode.ExtensionMode.Development; 148 | 149 | if (!enableDevCommands) { 150 | vscode.window.showWarningMessage('Development commands are disabled. Enable "simpleCodingTimeTracker.enableDevCommands" in settings to use this feature.'); 151 | return; 152 | } 153 | 154 | const projects = [ 155 | 'React Dashboard', 'Node.js API', 'Mobile App', 'Database Migration', 'UI Components', 156 | 'Backend Service', 'Documentation', 'Testing Suite', 'DevOps Setup', 'Data Analytics' 157 | ]; 158 | 159 | const branches = [ 160 | 'main', 'develop', 'feature/user-auth', 'feature/dashboard', 'feature/api-integration', 161 | 'bugfix/login-issue', 'bugfix/memory-leak', 'hotfix/security-patch', 'release/v1.2.0', 162 | 'feature/mobile-responsive', 'feature/dark-theme', 'refactor/database-layer' 163 | ]; 164 | 165 | const languages = [ 166 | 'typescript', 'javascript', 'python', 'java', 'csharp', 'cpp', 'go', 'rust', 167 | 'php', 'ruby', 'swift', 'kotlin', 'html', 'css', 'scss', 'json', 'yaml', 'markdown', 'sql', 'bash' 168 | ]; 169 | 170 | const today = new Date(); 171 | let totalEntries = 0; 172 | 173 | await vscode.window.withProgress({ 174 | location: vscode.ProgressLocation.Notification, 175 | title: "🎲 Generating test data...", 176 | cancellable: false 177 | }, async (progress) => { 178 | try { 179 | // Generate data for the last 90 days 180 | for (let i = 0; i < 90; i++) { 181 | const date = new Date(today); 182 | date.setDate(date.getDate() - i); 183 | 184 | // Skip some days randomly (weekends or days off) 185 | if (Math.random() < 0.2) continue; // 20% chance to skip a day 186 | 187 | // Generate 1-4 entries per day 188 | const entriesCount = Math.floor(Math.random() * 4) + 1; 189 | 190 | for (let j = 0; j < entriesCount; j++) { 191 | const project = projects[Math.floor(Math.random() * projects.length)]; 192 | const branch = branches[Math.floor(Math.random() * branches.length)]; 193 | const language = languages[Math.floor(Math.random() * languages.length)]; 194 | // Random time between 15 minutes and 3 hours 195 | const timeSpent = Math.floor(Math.random() * 165) + 15; 196 | 197 | // Use the database's addEntry method 198 | await database.addEntry(date, project, timeSpent, branch, language); 199 | totalEntries++; 200 | } 201 | 202 | // Update progress 203 | progress.report({ increment: (1/90) * 100 }); 204 | } 205 | 206 | // Add some special test cases for yesterday and today 207 | const yesterday = new Date(today); 208 | yesterday.setDate(yesterday.getDate() - 1); 209 | 210 | await database.addEntry(yesterday, 'Node.js API', 480, 'feature/api-integration', 'typescript'); 211 | await database.addEntry(today, 'React Dashboard', 120, 'feature/dashboard', 'typescript'); 212 | await database.addEntry(today, 'Documentation', 60, 'main', 'markdown'); 213 | totalEntries += 3; 214 | 215 | } catch (error) { 216 | vscode.window.showErrorMessage(`❌ Error generating test data: ${error}`); 217 | return; 218 | } 219 | }); 220 | 221 | vscode.window.showInformationMessage(`✅ Generated ${totalEntries} test entries successfully!`); 222 | }); 223 | 224 | // Delete test data command 225 | let deleteTestDataCommand = vscode.commands.registerCommand('simpleCodingTimeTracker.deleteTestData', async () => { 226 | // Check if dev commands are enabled 227 | const config = vscode.workspace.getConfiguration('simpleCodingTimeTracker'); 228 | const enableDevCommands = config.get('enableDevCommands', false) || 229 | context.extensionMode === vscode.ExtensionMode.Development; 230 | 231 | if (!enableDevCommands) { 232 | vscode.window.showWarningMessage('Development commands are disabled. Enable "simpleCodingTimeTracker.enableDevCommands" in settings to use this feature.'); 233 | return; 234 | } 235 | 236 | const confirmation = await vscode.window.showWarningMessage( 237 | '🗑️ Delete all time tracking data? This action cannot be undone.', 238 | { modal: true }, 239 | 'Delete All Data', 240 | 'Cancel' 241 | ); 242 | 243 | if (confirmation === 'Delete All Data') { 244 | const finalConfirmation = await vscode.window.showInputBox({ 245 | prompt: 'Type "DELETE ALL DATA" to confirm permanent deletion of all time tracking data.', 246 | placeHolder: 'DELETE ALL DATA', 247 | ignoreFocusOut: true 248 | }); 249 | 250 | if (finalConfirmation === 'DELETE ALL DATA') { 251 | try { 252 | const success = await database.clearAllData(); 253 | if (success) { 254 | vscode.window.showInformationMessage('✅ All time tracking data has been deleted successfully!'); 255 | } 256 | } catch (error) { 257 | vscode.window.showErrorMessage(`❌ Error deleting data: ${error}`); 258 | } 259 | } else { 260 | vscode.window.showInformationMessage('Data deletion cancelled.'); 261 | } 262 | } 263 | }); 264 | 265 | context.subscriptions.push(clearDataCommand); 266 | context.subscriptions.push(toggleHealthCommand); 267 | context.subscriptions.push(openSettingsCommand); 268 | context.subscriptions.push(testPauseCommand); 269 | context.subscriptions.push(testNotificationCommand); 270 | context.subscriptions.push(generateTestDataCommand); 271 | context.subscriptions.push(deleteTestDataCommand); 272 | 273 | context.subscriptions.push(disposable); 274 | context.subscriptions.push(viewStorageDisposable); 275 | context.subscriptions.push(timeTracker); 276 | context.subscriptions.push(statusBar); 277 | context.subscriptions.push(settingsView); 278 | 279 | // Start tracking immediately if VS Code is already focused 280 | if (vscode.window.state.focused) { 281 | timeTracker.startTracking('initial startup'); 282 | } 283 | 284 | // Window state is now handled in TimeTracker class 285 | vscode.window.onDidChangeWindowState((e: vscode.WindowState) => { 286 | if (e.focused && !timeTracker.isActive()) { 287 | timeTracker.startTracking('window focus'); 288 | } 289 | }); 290 | 291 | vscode.workspace.onDidOpenTextDocument(() => { 292 | if (vscode.window.state.focused) { 293 | timeTracker.startTracking('document opened'); 294 | } 295 | }); 296 | 297 | } 298 | 299 | export function deactivate() {} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 🌐 **Website**: [https://twentytwo.github.io/vsc-ext-coding-time-tracker/](https://twentytwo.github.io/vsc-ext-coding-time-tracker/) 2 | 3 | **📖 For detailed configuration, advanced features, and complete documentation, see the [Simple Coding Time Tracker Guide](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/wiki) in our wiki.** 4 | 5 | 📦 **Installation:** [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=noorashuvo.simple-coding-time-tracker) (VS Code) | [Open VSX Registry](https://open-vsx.org/extension/noorashuvo/simple-coding-time-tracker) (Cursor, Windsurf, Trae, VS Codium etc) 6 | 7 | 8 | # Simple Coding Time Tracker: A Visual Studio Code Extension 9 |
10 | Simple Coding Time Tracker Icon 11 |
12 | 13 | Simple Coding Time Tracker is a powerful extension for Visual Studio Code that helps you monitor and analyze your coding time. If you are curious about your coding habits, this extension covers you. 14 | 15 | ## Features 16 | 17 | - **Automatic Time Tracking**: Seamlessly tracks your coding time in the background. 18 | - **Project and Branch Tracking**: Organizes time data by project and Git branches for comprehensive analysis. 19 | - **Language Tracking**: Automatically detects and tracks time spent in different programming languages. 20 | - **Smart Activity Detection**: Automatically pauses tracking during periods of inactivity. 21 | - **Focused Work Detection**: Intelligently tracks time even when VS Code isn't focused. 22 | - **Health Notification System**: Proactive reminders to promote healthy coding habits with scientifically backed intervals. 23 | - **Dedicated Settings View**: Comprehensive settings interface accessible via the summary view with easy-to-use controls for all configuration options. 24 | - **Interactive Data Visualization**: 25 | - Project Summary Chart: Visual breakdown of time spent on each project 26 | - Daily Activity Timeline: Interactive line chart showing your coding patterns 27 | - Activity Heatmap: 3-month calendar view showing coding intensity 28 | - Language Distribution Chart: Visual breakdown of time spent in different programming languages 29 | - Theme-Aware Charts: Automatically adapts to VS Code's light/dark themes 30 | - **Advanced Search & Filtering**: 31 | - Date Range Selection: Filter data by specific time periods 32 | - Project Filtering: Focus on specific projects 33 | - Language Filtering: Focus on specific programming languages 34 | - Quick Reset: One-click reset for search filters 35 | - **Data Persistence**: Safely stores your time data for long-term analysis. 36 | 37 | ## Time Tracking Details 38 | The extension tracks your coding time by monitoring file changes and user activity within Visual Studio Code. It uses a combination of timers and event listeners to ensure accurate tracking without impacting performance. The extension automatically detects the programming language you're working with based on file extensions and VS Code's language detection. 39 | 40 | **📖 For detailed configuration, advanced features, and complete documentation, see the [Time Tracking Guide](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/wiki/Time-Tracking) in our wiki.** 41 | 42 | ## Health Notification System Details 43 | 44 | The extension includes a comprehensive health notification system to promote healthy coding habits and prevent strain-related issues. 45 | 46 | ### 🔔 Smart Health Notifications 47 | - **Eye Rest Reminders**: Every 20 minutes, get reminded to follow the 20-20-20 rule (look at something 20 feet away for 20 seconds) 48 | - **Stretch Reminders**: Every 30 minutes, get reminded to stand up and stretch your back and neck - Recommended for posture health 49 | - **Break Suggestions**: Every 90 minutes, get prompted to take a proper break with multiple options - Based on ultradian rhythms 50 | These are default values and designed to help you maintain focus and prevent fatigue during long coding sessions. You can always customize these intervals in the settings. 51 | 52 | **📖 For detailed configuration, advanced features, and complete documentation, see the [Health Notifications Guide](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/wiki/Health-Notifications) in our wiki.** 53 | 54 | ## Installation 55 | 56 | 1. Open Visual Studio Code 57 | 2. Go to the Extensions view (Ctrl+Shift+X or Cmd+Shift+X on macOS) 58 | 3. Search for "Simple Coding Time Tracker" 59 | 4. Click "Install" 60 | 61 | ## Usage 62 | 63 | Once installed, the extension will automatically start tracking your coding time. You can view your current session time in the status bar at the bottom of the VSCode window. 64 | 65 | ### Using Search & Filters 66 | 67 | 1. In the summary view, locate the search form 68 | 2. Select a date range using the date pickers 69 | 3. Filter by project, branch, and/or language: 70 | - Choose a specific project to see all its branches 71 | - Select a branch to see time data for that specific branch 72 | - Select a language to see time data for that specific programming language 73 | - The branch dropdown automatically updates to show only branches from the selected project 74 | 4. Click "Search" to apply filters 75 | 5. Use "Reset" to clear all filters and refresh the view 76 | 77 | The charts and visualizations will automatically update to reflect your selected project, branch, and language filters. 78 | 79 | ### Configuration Options 80 | 81 | You can customize the extension's behavior through VS Code settings or the dedicated Settings view: 82 | 83 | **Method 1: Using the Settings View (Recommended)** 84 | 1. Open the Coding Time Summary view by clicking on the status bar or using the command `SCTT: Show Coding Time Summary` 85 | 2. Click the "Settings" button in the top-right corner of the summary view 86 | 3. Configure all settings through the user-friendly interface with descriptions and validation 87 | 4. Click "Save Settings" to apply changes 88 | 89 | **Method 2: Using VS Code Settings** 90 | 1. Open VS Code Settings (Ctrl+, or Cmd+, on macOS) 91 | 2. Search for "Simple Coding Time Tracker" 92 | 93 | **Available settings:** 94 | - **Inactivity Timeout**: How long to wait before stopping the timer when no activity is detected but you are focused on VS Code (in minutes) 95 | - Default: 2.5 minutes 96 | - Lower values will stop tracking sooner when you're not actively coding 97 | - Higher values will continue tracking for longer during breaks 98 | - **Focus Timeout**: How long to continue tracking after VS Code loses focus (in minutes) 99 | - Default: 3 minutes 100 | - Determines how long to keep tracking when you switch to other applications 101 | - Useful for when you're referencing documentation or testing your application 102 | - **Health Notifications**: Configure health reminder settings 103 | - **Modal Notifications**: Enable/disable modal behavior for health notifications (default: true) 104 | - **Enable Notifications**: Enable/disable all health notifications (default: true) 105 | - **Eye Rest Interval**: Frequency of eye rest reminders in minutes (default: 20) - Based on 20-20-20 rule 106 | - **Stretch Interval**: Frequency of stretch reminders in minutes (default: 30) - Recommended for posture health 107 | - **Break Threshold**: Coding duration before suggesting a break in minutes (default: 90) - Based on ultradian rhythms 108 | 109 | 110 | ## Screenshots 111 | 112 | ### Coding time summary 113 | The summary page provides a detailed report of your coding activity with interactive charts and visualizations: 114 | - Project distribution chart showing time allocation across projects 115 | - Daily activity timeline with interactive tooltips 116 | - 3-month activity heatmap for long-term pattern analysis 117 | - Language distribution chart showing time spent in different programming languages 118 | - Theme-aware visualizations that adapt to your VS Code theme 119 | - Advanced search and filtering capabilities 120 | - Quick access to settings via the Settings button in the header 121 | 122 | ![Coding Summary](https://raw.githubusercontent.com/twentyTwo/static-file-hosting/main/vsc-ext-coding-time-tracker-files/sctt-light.png) 123 | 124 | #### Dark theme 125 | ![Coding Summary Dark Theme](https://raw.githubusercontent.com/twentyTwo/static-file-hosting/main/vsc-ext-coding-time-tracker-files/sctt-dark.png)) 126 | 127 | 128 | #### Status Bar 129 | Status bar resets to zero at midnight each day and hence shows the coding time for the current day. 130 | ![Status Bar](https://raw.githubusercontent.com/twentyTwo/static-file-hosting/main/vsc-ext-coding-time-tracker-files/statusbar.png) 131 | 132 | #### Tooltip 133 | Tooltip shows the total coding time weekly, monthly and all time basis. 134 | ![Tooltip](https://raw.githubusercontent.com/twentyTwo/static-file-hosting/main/vsc-ext-coding-time-tracker-files/tooltip.png) 135 | 136 | 137 | #### Settings View 138 | The dedicated settings view provides an easy-to-use interface for configuring all extension options. Access it by clicking the "Settings" button in the summary view header. 139 | ![Settings](https://raw.githubusercontent.com/twentyTwo/static-file-hosting/main/vsc-ext-coding-time-tracker-files/settings.png) 140 | 141 | 142 | ## 📚 Documentation 143 | 144 | For comprehensive documentation, guides, and testing information, visit our **[Documentation Wiki](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/wiki)**: 145 | 146 | - **[Health Notifications Guide](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/wiki/Health-Notifications)** - Complete health notification configuration and features 147 | - **[Time Tracking Guide](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/wiki/Time-Tracking)** - How time tracking works internally 148 | - **[Test Scenarios](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/wiki/Test-Scenarios)** - Comprehensive testing documentation 149 | - **[Development Roadmap](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/wiki/TODO)** - Current tasks and future plans 150 | 151 | ## Testing & Development 152 | 153 | For developers and testers, the extension includes built-in test data generation commands: 154 | 155 | ### Enabling Test Commands 156 | 1. Open Settings (`Ctrl+,`) 157 | 2. Search: `"enableDevCommands"` 158 | 3. Enable "Simple Coding Time Tracker › Enable Dev Commands" 159 | 160 | ### Available Test Commands 161 | - **`SCTT: Generate Test Data (Dev)`** - Creates realistic test data for 90 days 162 | - **`SCTT: Delete Test Data (Dev)`** - Safely removes all tracking data 163 | 164 | **Note**: These commands are hidden from regular users and only appear when explicitly enabled in settings. 165 | 166 | For complete testing documentation, see [TECHNICAL.md](TECHNICAL.md). 167 | 168 | ## Technical Documentation 169 | 170 | For technical details about development, release process, and internal architecture, please see [TECHNICAL.md](TECHNICAL.md). 171 | 172 | ## Changelog 173 | 174 | ### [0.6.3] - 2025-10-10 175 | - Added dedicated Settings View accessible from the summary view header 176 | - Implemented `SCTT: Open Settings` command for direct access to settings interface 177 | - Enhanced user experience with intuitive settings management through webview interface 178 | - Added Settings button to summary view header for quick access to configuration options 179 | - All extension settings now available through both traditional VS Code settings and the new dedicated view 180 | 181 | ### [0.6.1] - 2025-08-30 182 | - Added comprehensive test data generation commands for developers and testers 183 | - Implemented `SCTT: Generate Test Data (Dev)` command that creates 90 days of realistic test data 184 | - Added `SCTT: Delete Test Data (Dev)` command for safe cleanup of test data 185 | - Test commands are hidden from end users by default and only visible when `enableDevCommands` setting is enabled 186 | - Enhanced security with configuration-controlled command visibility 187 | - Improved testing workflow for packaged extension installations 188 | - Added progress indicators for test data generation process 189 | 190 | ### [0.6.0] - 2025-08-28 191 | - Added comprehensive language tracking to monitor time spent in different programming languages 192 | - Automatic language detection based on file extensions and VS Code language IDs 193 | - Support for 50+ programming languages including JavaScript, TypeScript, Python, Java, C++, and more 194 | - Enhanced search and filtering capabilities to include language-based filtering 195 | - Language distribution visualization in summary charts 196 | - All existing functionality preserved with seamless migration of historical data 197 | 198 | ### [0.5.0] - 2025-08-02 199 | - Added comprehensive health notification system with customizable intervals 200 | - Implemented **prominent, persistent notifications** that don't auto-dismiss: 201 | - Eye rest reminders (20-20-20 rule) every 20 minutes 202 | - Stretch reminders every 30 minutes (Recommended for posture health) 203 | - Break suggestions every 90 minutes using (ultradian rhythms) 204 | 205 | - Implemented toggle command for quick enable/disable of health notifications 206 | - Health notifications automatically start/stop with time tracking 207 | - All health notification intervals are fully configurable through VS Code settings 208 | 209 | ### [0.4.1] - 2025-07-26 210 | - Fixed issue with excessive git processes being spawned 211 | - Optimized git branch monitoring to reduce CPU load 212 | - Reduced frequency of git checks from every 1 second to every 5 seconds 213 | - Improved cleanup of interval timers to prevent memory leaks 214 | 215 | ### [0.4.0] - 2025-06-28 216 | - Added Git branch tracking to monitor time spent on different branches 217 | - Enhanced project view with branch-specific time tracking 218 | - Implemented dynamic branch filtering based on selected project 219 | - Improved charts to show time distribution across branches 220 | - Added branch-specific data in search results and visualizations 221 | 222 | ### [0.3.9] - 2025-05-25 223 | - Added Focus Timeout setting to intelligently track time when VS Code loses focus 224 | - Fixed version tracking in GitHub Actions workflow to prevent publishing issues 225 | - Updated documentation to clarify timeout settings and their purposes 226 | - Enhanced error handling in the publishing workflow 227 | 228 | ### [0.3.4] - 2025-04-19 229 | - Handle multi-root workspaces, external files, and virtual files more effectively. 230 | - Added a verify-changes job to check if a version update is required and ensure non-documentation files are modified before publishing. This prevents unnecessary releases. 231 | - Introduced a new workflow to automate the creation of beta and production releases, including attaching .vsix files and setting appropriate release metadata. 232 | - Added a new technical documentation file outlining the development setup, release process, internal architecture, and testing guidelines for the extension. 233 | 234 | ### [0.3.0] - 2025-04-14 235 | - Added smart activity detection with configurable inactivity timeout 236 | - Enhanced chart interactivity and responsiveness 237 | - Improved theme compatibility for all visualizations 238 | - Added quick reset button for search filters 239 | - Refined chart tooltips and legends for better readability 240 | 241 | ### [0.2.3] - 2025-03-19 242 | - Made the save interval configurable by the user, with a default of 5 seconds. 243 | - Updated the documentation to reflect the new configuration option. 244 | 245 | ### [0.2.2] - 2024-10-04 246 | - Added command to reset all timers 247 | - Added a command to reset daily timer 248 | 249 | ### [0.2.1] - 2024-10-02 250 | - Enhanced the UI of the summary view for a more professional look 251 | - Implemented date range search functionality 252 | - Added a reload button to reset search fields and refresh data 253 | - Improved the layout and styling of the Total Coding Time section 254 | 255 | ### [0.1.4] 256 | - Initial release 257 | - Automatic time tracking 258 | - Project-based tracking 259 | - Status bar display with tooltip 260 | - Detailed summary view 261 | - Data persistence 262 | 263 | ## Contributing 264 | 265 | For developers interested in contributing to the project, please check out our [CONTRIBUTING.md](CONTRIBUTING.md) file for guidelines and instructions. 266 | -------------------------------------------------------------------------------- /.github/workflows/build-and-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish Extension 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '.github/workflows/**' 8 | workflow_dispatch: 9 | inputs: 10 | publish_vscode: 11 | description: 'Publish to VS Code Marketplace' 12 | required: false 13 | default: true 14 | type: boolean 15 | publish_openvsx: 16 | description: 'Publish to Open VSX Registry' 17 | required: false 18 | default: true 19 | type: boolean 20 | force_publish: 21 | description: 'Force publish (ignore version change check)' 22 | required: false 23 | default: false 24 | type: boolean 25 | 26 | jobs: 27 | # 1∩╕ÅΓâú BUILD ONCE - Single source of truth 28 | build: 29 | runs-on: ubuntu-latest 30 | outputs: 31 | version: ${{ steps.package.outputs.version }} 32 | should_publish: ${{ steps.verify.outputs.should_publish }} 33 | vsix_name: ${{ steps.build.outputs.vsix_name }} 34 | 35 | steps: 36 | - name: Checkout code 37 | uses: actions/checkout@v4 38 | with: 39 | fetch-depth: 0 40 | 41 | - name: Setup Node.js 42 | uses: actions/setup-node@v4 43 | with: 44 | node-version: '20' 45 | 46 | - name: Install dependencies 47 | run: npm ci 48 | 49 | - name: Compile TypeScript 50 | run: npm run compile 51 | 52 | - name: Lint code 53 | run: npm run lint 54 | 55 | - name: Verify changes 56 | id: verify 57 | run: | 58 | # Handle different trigger scenarios 59 | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then 60 | # For manual triggers, compare with previous commit 61 | echo "files=$(git diff --name-only HEAD~1 HEAD | tr '\n' ' ')" >> $GITHUB_OUTPUT 62 | VERSION_CHANGED=$(git diff HEAD~1 HEAD package.json | grep -E '^\+\s*"version"' || echo "") 63 | elif [ -z "${{ github.event.before }}" ] || [ "${{ github.event.before }}" = "0000000000000000000000000000000000000000" ]; then 64 | # For first commit or when before is null, get all files in the commit 65 | echo "files=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git ls-files | tr '\n' ' ')" >> $GITHUB_OUTPUT 66 | VERSION_CHANGED="version_changed_first_commit" 67 | else 68 | # Normal push event 69 | echo "files=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | tr '\n' ' ')" >> $GITHUB_OUTPUT 70 | VERSION_CHANGED=$(git diff ${{ github.event.before }} ${{ github.event.after }} package.json | grep -E '^\+\s*"version"' || echo "") 71 | fi 72 | 73 | # Get list of changed files 74 | CHANGED_FILES=$(echo "${{ github.event_name }}" | grep -q "workflow_dispatch" && git diff --name-only HEAD~1 HEAD | tr '\n' ' ' || git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | tr '\n' ' ') 75 | 76 | # Debug output 77 | echo "≡ƒöì Event: ${{ github.event_name }}" 78 | echo "≡ƒôü Changed files: $CHANGED_FILES" 79 | echo "≡ƒôª Version changed: ${VERSION_CHANGED:-'no'}" 80 | 81 | # Check if only documentation files were changed 82 | DOCS_ONLY=true 83 | for file in $CHANGED_FILES; do 84 | if [[ ! $file =~ \.(md|txt|doc|docx)$ ]] && [[ ! $file =~ ^docs/ ]]; then 85 | DOCS_ONLY=false 86 | break 87 | fi 88 | done 89 | 90 | # Initialize should_publish as false 91 | echo "should_publish=false" >> $GITHUB_OUTPUT 92 | 93 | # Decision logic 94 | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then 95 | if [ "${{ github.event.inputs.force_publish }}" = "true" ]; then 96 | echo "≡ƒÜÇ Manual workflow dispatch with force publish - proceeding with publish" 97 | echo "should_publish=true" >> $GITHUB_OUTPUT 98 | elif [[ -n "$VERSION_CHANGED" ]]; then 99 | echo "≡ƒÜÇ Manual workflow dispatch with version change - proceeding with publish" 100 | echo "should_publish=true" >> $GITHUB_OUTPUT 101 | else 102 | echo "ΓÜá∩╕Å Manual workflow dispatch without version change - use force_publish to override" 103 | echo "should_publish=false" >> $GITHUB_OUTPUT 104 | fi 105 | elif [[ "$DOCS_ONLY" == "true" ]]; then 106 | echo "Γä╣∩╕Å Only documentation files were changed - skipping publish" 107 | echo "should_publish=false" >> $GITHUB_OUTPUT 108 | elif [[ -z "$VERSION_CHANGED" ]]; then 109 | echo "Γ¥î Version in package.json was not updated - skipping publish" 110 | echo "should_publish=false" >> $GITHUB_OUTPUT 111 | else 112 | echo "Γ£à Version changed and non-documentation files modified - proceeding with publish" 113 | echo "should_publish=true" >> $GITHUB_OUTPUT 114 | fi 115 | 116 | - name: Get package info 117 | id: package 118 | run: | 119 | VERSION=$(node -p "require('./package.json').version") 120 | echo "version=$VERSION" >> $GITHUB_OUTPUT 121 | echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT 122 | echo "≡ƒôª Package version: $VERSION" 123 | 124 | - name: Build VSIX package 125 | id: build 126 | if: steps.verify.outputs.should_publish == 'true' 127 | run: | 128 | npm install -g @vscode/vsce@latest 129 | rm -f *.vsix 130 | vsce package 131 | 132 | VSIX_NAME="simple-coding-time-tracker-${{ steps.package.outputs.version }}.vsix" 133 | echo "vsix_name=$VSIX_NAME" >> $GITHUB_OUTPUT 134 | 135 | # Validate VSIX 136 | if [ ! -f "$VSIX_NAME" ]; then 137 | echo "Γ¥î VSIX build failed" 138 | exit 1 139 | fi 140 | 141 | # Verify the package can be listed 142 | vsce ls "$VSIX_NAME" 143 | 144 | SIZE=$(stat -c%s "$VSIX_NAME" 2>/dev/null || stat -f%z "$VSIX_NAME") 145 | SIZE_MB=$((SIZE / 1024 / 1024)) 146 | 147 | if [ $SIZE_MB -gt 10 ]; then 148 | echo "ΓÜá∩╕Å Warning: Package size is ${SIZE_MB}MB (consider optimizing)" 149 | else 150 | echo "Γ£à Package size: ${SIZE_MB}MB" 151 | fi 152 | 153 | echo "Γ£à VSIX built successfully - $VSIX_NAME" 154 | 155 | - name: Upload VSIX artifact 156 | if: steps.verify.outputs.should_publish == 'true' 157 | uses: actions/upload-artifact@v4 158 | with: 159 | name: extension-vsix-${{ steps.package.outputs.version }} 160 | path: ${{ steps.build.outputs.vsix_name }} 161 | retention-days: 90 162 | 163 | # 2∩╕ÅΓâú DEPLOY TO VS CODE MARKETPLACE 164 | publish-vscode: 165 | needs: build 166 | if: | 167 | needs.build.outputs.should_publish == 'true' && ( 168 | github.event_name != 'workflow_dispatch' || 169 | github.event.inputs.publish_vscode == 'true' 170 | ) 171 | runs-on: ubuntu-latest 172 | environment: VSC EXT 173 | permissions: 174 | contents: write 175 | actions: read 176 | 177 | steps: 178 | - name: Log VS Code Marketplace Publishing 179 | run: | 180 | echo "≡ƒÅ¬ Publishing to VS Code Marketplace" 181 | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then 182 | echo "≡ƒôï Manual trigger - VS Code Marketplace: ${{ github.event.inputs.publish_vscode }}" 183 | else 184 | echo "≡ƒôï Automatic trigger - VS Code Marketplace: enabled" 185 | fi 186 | - name: Checkout code 187 | uses: actions/checkout@v4 188 | 189 | - name: Download VSIX artifact 190 | uses: actions/download-artifact@v4 191 | with: 192 | name: extension-vsix-${{ needs.build.outputs.version }} 193 | 194 | - name: Install vsce CLI 195 | run: npm install -g @vscode/vsce@latest 196 | 197 | - name: Check if tag exists 198 | id: check_tag 199 | run: | 200 | if git rev-parse "v${{ needs.build.outputs.version }}" >/dev/null 2>&1; then 201 | echo "Tag v${{ needs.build.outputs.version }} already exists" 202 | echo "tag_exists=true" >> $GITHUB_OUTPUT 203 | else 204 | echo "tag_exists=false" >> $GITHUB_OUTPUT 205 | fi 206 | 207 | - name: Publish to VS Code Marketplace 208 | id: marketplace_publish 209 | uses: nick-fields/retry@v3 210 | with: 211 | timeout_minutes: 5 212 | max_attempts: 3 213 | retry_wait_seconds: 30 214 | command: vsce publish -p $VSC_PAT --packagePath ${{ needs.build.outputs.vsix_name }} 215 | env: 216 | VSC_PAT: ${{ secrets.VSC_PAT }} 217 | 218 | - name: Generate Release Notes 219 | id: release_notes 220 | run: | 221 | # Get the previous tag 222 | PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "") 223 | 224 | if [ -n "$PREV_TAG" ]; then 225 | # Generate changelog from commits since last tag 226 | CHANGELOG=$(git log ${PREV_TAG}..HEAD --pretty=format:"- %s" --no-merges) 227 | 228 | # Create release body with actual changes 229 | cat << EOF > release_body.md 230 | ## ≡ƒÜÇ What's New in v${{ needs.build.outputs.version }} 231 | 232 | ${CHANGELOG} 233 | 234 | ## ≡ƒôª Installation 235 | - **VS Code Marketplace**: [Install from VS Code](https://marketplace.visualstudio.com/items?itemName=noorashuvo.simple-coding-time-tracker) 236 | - **Open VSX Registry**: [Install for VS Codium](https://open-vsx.org/extension/noorashuvo/simple-coding-time-tracker) 237 | 238 | ## ≡ƒöù Links 239 | - [≡ƒôï Changelog](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/blob/main/README.md#changelog) 240 | - [≡ƒôÜ Documentation Wiki](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/wiki) 241 | - [≡ƒÉ¢ Report Issues](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/issues) 242 | 243 | --- 244 | 245 | **Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...v${{ needs.build.outputs.version }} 246 | EOF 247 | else 248 | cat << EOF > release_body.md 249 | ## ≡ƒÜÇ What's New in v${{ needs.build.outputs.version }} 250 | 251 | Initial release of Simple Coding Time Tracker 252 | 253 | ## ≡ƒôª Installation 254 | - **VS Code Marketplace**: [Install from VS Code](https://marketplace.visualstudio.com/items?itemName=noorashuvo.simple-coding-time-tracker) 255 | - **Open VSX Registry**: [Install for VS Codium](https://open-vsx.org/extension/noorashuvo/simple-coding-time-tracker) 256 | 257 | ## ≡ƒöù Links 258 | - [≡ƒôÜ Documentation Wiki](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/wiki) 259 | - [≡ƒÉ¢ Report Issues](https://github.com/twentyTwo/vsc-ext-coding-time-tracker/issues) 260 | EOF 261 | fi 262 | 263 | - name: Create GitHub Release 264 | id: create_release 265 | if: steps.check_tag.outputs.tag_exists == 'false' && steps.marketplace_publish.outcome == 'success' 266 | uses: softprops/action-gh-release@v2 267 | with: 268 | tag_name: v${{ needs.build.outputs.version }} 269 | name: Release v${{ needs.build.outputs.version }} 270 | body_path: release_body.md 271 | files: ${{ needs.build.outputs.vsix_name }} 272 | draft: false 273 | prerelease: false 274 | generate_release_notes: false 275 | env: 276 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 277 | 278 | # 3∩╕ÅΓâú DEPLOY TO OPEN VSX (Parallel) 279 | publish-openvsx: 280 | needs: build 281 | if: | 282 | needs.build.outputs.should_publish == 'true' && ( 283 | github.event_name != 'workflow_dispatch' || 284 | github.event.inputs.publish_openvsx == 'true' 285 | ) 286 | runs-on: ubuntu-latest 287 | environment: Open VSX 288 | permissions: 289 | contents: read 290 | actions: read 291 | 292 | steps: 293 | - name: Log Open VSX Publishing 294 | run: | 295 | echo "≡ƒîÉ Publishing to Open VSX Registry" 296 | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then 297 | echo "≡ƒôï Manual trigger - Open VSX: ${{ github.event.inputs.publish_openvsx }}" 298 | else 299 | echo "≡ƒôï Automatic trigger - Open VSX: enabled" 300 | fi 301 | - name: Download VSIX artifact 302 | uses: actions/download-artifact@v4 303 | with: 304 | name: extension-vsix-${{ needs.build.outputs.version }} 305 | 306 | - name: Install ovsx CLI 307 | run: npm install -g ovsx@latest 308 | 309 | - name: Publish to Open VSX Registry 310 | id: openvsx_publish 311 | uses: nick-fields/retry@v3 312 | with: 313 | timeout_minutes: 5 314 | max_attempts: 3 315 | retry_wait_seconds: 30 316 | command: ovsx publish ${{ needs.build.outputs.vsix_name }} --pat $OPENVSX_PAT 317 | env: 318 | OPENVSX_PAT: ${{ secrets.OPENVSX_PAT }} 319 | 320 | - name: Verify Publication 321 | if: steps.openvsx_publish.outcome == 'success' 322 | run: | 323 | echo "≡ƒÄë Successfully published to Open VSX Registry!" 324 | echo "≡ƒôª Extension: simple-coding-time-tracker v${{ needs.build.outputs.version }}" 325 | echo "≡ƒîÉ Open VSX: https://open-vsx.org/extension/noorashuvo/simple-coding-time-tracker" 326 | 327 | # 4∩╕ÅΓâú REPORT FINAL STATUS 328 | report-status: 329 | needs: [build, publish-vscode, publish-openvsx] 330 | if: always() && needs.build.outputs.should_publish == 'true' 331 | runs-on: ubuntu-latest 332 | 333 | steps: 334 | - name: Report Final Status 335 | run: | 336 | echo "## ≡ƒôè Publication Status Report" 337 | echo "**Version**: v${{ needs.build.outputs.version }}" 338 | 339 | # Show what was requested 340 | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then 341 | echo "**Trigger**: Manual (workflow_dispatch)" 342 | echo "**VS Code Marketplace**: ${{ github.event.inputs.publish_vscode }}" 343 | echo "**Open VSX Registry**: ${{ github.event.inputs.publish_openvsx }}" 344 | else 345 | echo "**Trigger**: Automatic (push to main)" 346 | echo "**VS Code Marketplace**: enabled" 347 | echo "**Open VSX Registry**: enabled" 348 | fi 349 | echo "" 350 | 351 | # VS Code Marketplace status 352 | if [ "${{ needs.publish-vscode.result }}" = "success" ]; then 353 | echo "Γ£à **VS Code Marketplace**: Successfully published" 354 | elif [ "${{ needs.publish-vscode.result }}" = "skipped" ]; then 355 | echo "ΓÅ¡∩╕Å **VS Code Marketplace**: Skipped (disabled in manual trigger)" 356 | else 357 | echo "Γ¥î **VS Code Marketplace**: Failed to publish" 358 | fi 359 | 360 | # Open VSX status 361 | if [ "${{ needs.publish-openvsx.result }}" = "success" ]; then 362 | echo "Γ£à **Open VSX Registry**: Successfully published" 363 | elif [ "${{ needs.publish-openvsx.result }}" = "skipped" ]; then 364 | echo "ΓÅ¡∩╕Å **Open VSX Registry**: Skipped (disabled in manual trigger)" 365 | else 366 | echo "Γ¥î **Open VSX Registry**: Failed to publish" 367 | fi 368 | 369 | # Overall status 370 | VSCODE_SUCCESS=${{ needs.publish-vscode.result == 'success' }} 371 | VSCODE_SKIPPED=${{ needs.publish-vscode.result == 'skipped' }} 372 | OPENVSX_SUCCESS=${{ needs.publish-openvsx.result == 'success' }} 373 | OPENVSX_SKIPPED=${{ needs.publish-openvsx.result == 'skipped' }} 374 | 375 | if [ "$VSCODE_SUCCESS" = "true" ] && [ "$OPENVSX_SUCCESS" = "true" ]; then 376 | echo "" 377 | echo "≡ƒÄë **Overall Status**: All requested publications successful!" 378 | echo "::notice title=Publication Complete::Version v${{ needs.build.outputs.version }} published to both marketplaces" 379 | elif [ "$VSCODE_SUCCESS" = "true" ] || [ "$OPENVSX_SUCCESS" = "true" ]; then 380 | echo "" 381 | echo "Γ£à **Overall Status**: Partial success - some publications completed" 382 | echo "::notice title=Partial Publication::Version v${{ needs.build.outputs.version }} published to some marketplaces" 383 | elif [ "$VSCODE_SKIPPED" = "true" ] && [ "$OPENVSX_SKIPPED" = "true" ]; then 384 | echo "" 385 | echo "ΓÅ¡∩╕Å **Overall Status**: All publications skipped (both disabled)" 386 | echo "::warning title=No Publications::All marketplaces were disabled in manual trigger" 387 | else 388 | echo "" 389 | echo "Γ¥î **Overall Status**: Publication failures occurred" 390 | echo "::error title=Publication Failed::Some publications failed - check logs above" 391 | exit 1 392 | fi 393 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | /* Root Variables */ 2 | :root { 3 | --primary-color: #007acc; 4 | --primary-dark: #005a9e; 5 | --secondary-color: #68217a; 6 | --accent-color: #f9826c; 7 | --text-dark: #1e1e1e; 8 | --text-light: #ffffff; 9 | --text-gray: #6e6e6e; 10 | --bg-light: #ffffff; 11 | --bg-gray: #f5f5f5; 12 | --bg-dark: #1e1e1e; 13 | --border-color: #e0e0e0; 14 | --shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 15 | --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.15); 16 | --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 17 | } 18 | 19 | /* Reset and Base Styles */ 20 | * { 21 | margin: 0; 22 | padding: 0; 23 | box-sizing: border-box; 24 | } 25 | 26 | html { 27 | scroll-behavior: smooth; 28 | } 29 | 30 | body { 31 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; 32 | line-height: 1.6; 33 | color: var(--text-dark); 34 | background-color: var(--bg-light); 35 | } 36 | 37 | .container { 38 | max-width: 1200px; 39 | margin: 0 auto; 40 | padding: 0 20px; 41 | } 42 | 43 | /* Navigation */ 44 | .navbar { 45 | background-color: var(--bg-light); 46 | box-shadow: var(--shadow); 47 | position: sticky; 48 | top: 0; 49 | z-index: 1000; 50 | transition: var(--transition); 51 | } 52 | 53 | .nav-container { 54 | display: flex; 55 | justify-content: space-between; 56 | align-items: center; 57 | padding: 1rem 20px; 58 | } 59 | 60 | .logo { 61 | display: flex; 62 | align-items: center; 63 | gap: 12px; 64 | font-size: 1.2rem; 65 | font-weight: 700; 66 | color: var(--primary-color); 67 | text-decoration: none; 68 | } 69 | 70 | .logo-img { 71 | width: 40px; 72 | height: 40px; 73 | border-radius: 8px; 74 | } 75 | 76 | .logo-text { 77 | display: none; 78 | } 79 | 80 | .nav-links { 81 | display: flex; 82 | list-style: none; 83 | gap: 2rem; 84 | align-items: center; 85 | } 86 | 87 | .nav-links a { 88 | color: var(--text-dark); 89 | text-decoration: none; 90 | font-weight: 500; 91 | transition: var(--transition); 92 | padding: 0.5rem 0; 93 | border-bottom: 2px solid transparent; 94 | } 95 | 96 | .nav-links a:hover { 97 | color: var(--primary-color); 98 | border-bottom-color: var(--primary-color); 99 | } 100 | 101 | .github-link { 102 | background-color: var(--text-dark); 103 | color: var(--text-light) !important; 104 | padding: 0.5rem 1rem !important; 105 | border-radius: 6px; 106 | border-bottom: none !important; 107 | } 108 | 109 | .github-link:hover { 110 | background-color: var(--primary-color) !important; 111 | transform: translateY(-2px); 112 | } 113 | 114 | .mobile-menu-toggle { 115 | display: none; 116 | flex-direction: column; 117 | background: none; 118 | border: none; 119 | cursor: pointer; 120 | padding: 0.5rem; 121 | } 122 | 123 | .mobile-menu-toggle span { 124 | width: 25px; 125 | height: 3px; 126 | background-color: var(--text-dark); 127 | margin: 3px 0; 128 | transition: var(--transition); 129 | } 130 | 131 | /* Hero Section */ 132 | .hero { 133 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 134 | color: var(--text-light); 135 | padding: 100px 0 80px; 136 | text-align: center; 137 | position: relative; 138 | overflow: hidden; 139 | } 140 | 141 | .hero::before { 142 | content: ''; 143 | position: absolute; 144 | top: 0; 145 | left: 0; 146 | right: 0; 147 | bottom: 0; 148 | background: url('data:image/svg+xml,'); 149 | opacity: 0.3; 150 | } 151 | 152 | .hero-content { 153 | position: relative; 154 | z-index: 1; 155 | } 156 | 157 | .hero-title { 158 | font-size: 3.5rem; 159 | font-weight: 800; 160 | margin-bottom: 1.5rem; 161 | line-height: 1.2; 162 | animation: fadeInUp 0.8s ease-out; 163 | } 164 | 165 | .hero-subtitle { 166 | font-size: 1.3rem; 167 | max-width: 700px; 168 | margin: 0 auto 2.5rem; 169 | opacity: 0.95; 170 | animation: fadeInUp 0.8s ease-out 0.2s both; 171 | } 172 | 173 | .hero-buttons { 174 | display: flex; 175 | gap: 1.5rem; 176 | justify-content: center; 177 | flex-wrap: wrap; 178 | animation: fadeInUp 0.8s ease-out 0.4s both; 179 | } 180 | 181 | .btn { 182 | display: inline-flex; 183 | align-items: center; 184 | gap: 10px; 185 | padding: 14px 28px; 186 | font-size: 1rem; 187 | font-weight: 600; 188 | text-decoration: none; 189 | border-radius: 8px; 190 | transition: var(--transition); 191 | cursor: pointer; 192 | border: none; 193 | } 194 | 195 | .btn-primary { 196 | background-color: var(--text-light); 197 | color: var(--primary-color); 198 | box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); 199 | } 200 | 201 | .btn-primary:hover { 202 | transform: translateY(-3px); 203 | box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); 204 | } 205 | 206 | .btn-secondary { 207 | background-color: rgba(255, 255, 255, 0.2); 208 | color: var(--text-light); 209 | border: 2px solid var(--text-light); 210 | } 211 | 212 | .btn-secondary:hover { 213 | background-color: var(--text-light); 214 | color: var(--primary-color); 215 | transform: translateY(-3px); 216 | } 217 | 218 | .btn-large { 219 | padding: 16px 32px; 220 | font-size: 1.1rem; 221 | } 222 | 223 | .hero-stats { 224 | display: flex; 225 | justify-content: center; 226 | gap: 4rem; 227 | margin-top: 4rem; 228 | animation: fadeInUp 0.8s ease-out 0.6s both; 229 | } 230 | 231 | .stat { 232 | text-align: center; 233 | } 234 | 235 | .stat-number { 236 | font-size: 2.5rem; 237 | font-weight: 800; 238 | margin-bottom: 0.5rem; 239 | } 240 | 241 | .stat-label { 242 | font-size: 1rem; 243 | opacity: 0.9; 244 | } 245 | 246 | /* Features Section */ 247 | .features { 248 | padding: 100px 0; 249 | background-color: var(--bg-light); 250 | } 251 | 252 | .section-title { 253 | text-align: center; 254 | font-size: 2.8rem; 255 | font-weight: 800; 256 | margin-bottom: 1rem; 257 | color: var(--text-dark); 258 | } 259 | 260 | .section-subtitle { 261 | text-align: center; 262 | font-size: 1.2rem; 263 | color: var(--text-gray); 264 | margin-bottom: 4rem; 265 | } 266 | 267 | .features-grid { 268 | display: grid; 269 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 270 | gap: 2.5rem; 271 | } 272 | 273 | .feature-card { 274 | background: var(--bg-light); 275 | padding: 2.5rem; 276 | border-radius: 12px; 277 | box-shadow: var(--shadow); 278 | transition: var(--transition); 279 | border: 1px solid var(--border-color); 280 | } 281 | 282 | .feature-card:hover { 283 | transform: translateY(-8px); 284 | box-shadow: var(--shadow-lg); 285 | border-color: var(--primary-color); 286 | } 287 | 288 | .feature-icon { 289 | font-size: 3rem; 290 | margin-bottom: 1.5rem; 291 | } 292 | 293 | .feature-card h3 { 294 | font-size: 1.5rem; 295 | margin-bottom: 1rem; 296 | color: var(--text-dark); 297 | } 298 | 299 | .feature-card p { 300 | color: var(--text-gray); 301 | line-height: 1.7; 302 | } 303 | 304 | /* Screenshots Section */ 305 | .screenshots { 306 | padding: 100px 0; 307 | background-color: var(--bg-gray); 308 | } 309 | 310 | .screenshot-showcase { 311 | margin-top: 4rem; 312 | } 313 | 314 | .screenshot-item { 315 | margin-bottom: 5rem; 316 | text-align: center; 317 | } 318 | 319 | .screenshot-item h3 { 320 | font-size: 1.8rem; 321 | margin-bottom: 1.5rem; 322 | color: var(--primary-color); 323 | } 324 | 325 | .screenshot-img { 326 | width: 100%; 327 | max-width: 1000px; 328 | border-radius: 12px; 329 | box-shadow: var(--shadow-lg); 330 | margin-bottom: 1.5rem; 331 | transition: var(--transition); 332 | } 333 | 334 | .screenshot-img:hover { 335 | transform: scale(1.02); 336 | box-shadow: 0 15px 40px rgba(0, 0, 0, 0.2); 337 | } 338 | 339 | .screenshot-item p { 340 | font-size: 1.1rem; 341 | color: var(--text-gray); 342 | max-width: 700px; 343 | margin: 0 auto; 344 | } 345 | 346 | .screenshot-features { 347 | display: grid; 348 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 349 | gap: 3rem; 350 | margin-top: 4rem; 351 | } 352 | 353 | .screenshot-feature { 354 | text-align: center; 355 | } 356 | 357 | .screenshot-feature h4 { 358 | font-size: 1.3rem; 359 | margin-bottom: 1rem; 360 | color: var(--text-dark); 361 | } 362 | 363 | .screenshot-small { 364 | max-width: 400px; 365 | width: 100%; 366 | border-radius: 8px; 367 | box-shadow: var(--shadow); 368 | margin-bottom: 1rem; 369 | transition: var(--transition); 370 | } 371 | 372 | .screenshot-small:hover { 373 | transform: scale(1.05); 374 | } 375 | 376 | .screenshot-feature p { 377 | color: var(--text-gray); 378 | font-size: 1rem; 379 | } 380 | 381 | /* Installation Section */ 382 | .installation { 383 | padding: 100px 0; 384 | background-color: var(--bg-light); 385 | } 386 | 387 | .installation-methods { 388 | display: grid; 389 | grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); 390 | gap: 3rem; 391 | margin-top: 4rem; 392 | } 393 | 394 | .install-method { 395 | background: var(--bg-gray); 396 | padding: 3rem; 397 | border-radius: 12px; 398 | box-shadow: var(--shadow); 399 | } 400 | 401 | .install-method h3 { 402 | font-size: 1.8rem; 403 | margin-bottom: 1.5rem; 404 | color: var(--primary-color); 405 | } 406 | 407 | .install-description { 408 | color: var(--text-gray); 409 | font-style: italic; 410 | margin-bottom: 1.5rem; 411 | } 412 | 413 | .install-method ol { 414 | margin-left: 1.5rem; 415 | margin-bottom: 2rem; 416 | } 417 | 418 | .install-method li { 419 | margin-bottom: 1rem; 420 | color: var(--text-gray); 421 | line-height: 1.7; 422 | } 423 | 424 | .install-method code { 425 | background-color: var(--bg-light); 426 | padding: 2px 8px; 427 | border-radius: 4px; 428 | font-family: 'Courier New', monospace; 429 | color: var(--secondary-color); 430 | } 431 | 432 | .quick-start { 433 | margin-top: 5rem; 434 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 435 | padding: 4rem; 436 | border-radius: 12px; 437 | color: var(--text-light); 438 | } 439 | 440 | .quick-start h3 { 441 | font-size: 2rem; 442 | margin-bottom: 3rem; 443 | text-align: center; 444 | } 445 | 446 | .quick-start-steps { 447 | display: flex; 448 | justify-content: space-between; 449 | gap: 2rem; 450 | } 451 | 452 | .quick-step { 453 | flex: 1; 454 | display: flex; 455 | flex-direction: column; 456 | align-items: center; 457 | text-align: center; 458 | } 459 | 460 | .step-number { 461 | width: 60px; 462 | height: 60px; 463 | background-color: var(--text-light); 464 | color: var(--primary-color); 465 | border-radius: 50%; 466 | display: flex; 467 | align-items: center; 468 | justify-content: center; 469 | font-size: 1.8rem; 470 | font-weight: 800; 471 | margin-bottom: 1.5rem; 472 | } 473 | 474 | .step-content h4 { 475 | font-size: 1.3rem; 476 | margin-bottom: 0.8rem; 477 | } 478 | 479 | .step-content p { 480 | opacity: 0.95; 481 | } 482 | 483 | .step-content code { 484 | background-color: rgba(255, 255, 255, 0.2); 485 | padding: 2px 8px; 486 | border-radius: 4px; 487 | font-family: 'Courier New', monospace; 488 | } 489 | 490 | /* Use Cases Section */ 491 | .use-cases { 492 | padding: 100px 0; 493 | background-color: var(--bg-gray); 494 | } 495 | 496 | .use-cases-grid { 497 | display: grid; 498 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 499 | gap: 2.5rem; 500 | margin-top: 4rem; 501 | } 502 | 503 | .use-case { 504 | background: var(--bg-light); 505 | padding: 2.5rem; 506 | border-radius: 12px; 507 | text-align: center; 508 | box-shadow: var(--shadow); 509 | transition: var(--transition); 510 | } 511 | 512 | .use-case:hover { 513 | transform: translateY(-5px); 514 | box-shadow: var(--shadow-lg); 515 | } 516 | 517 | .use-case-icon { 518 | font-size: 3.5rem; 519 | margin-bottom: 1.5rem; 520 | } 521 | 522 | .use-case h3 { 523 | font-size: 1.5rem; 524 | margin-bottom: 1rem; 525 | color: var(--text-dark); 526 | } 527 | 528 | .use-case p { 529 | color: var(--text-gray); 530 | } 531 | 532 | /* CTA Section */ 533 | .cta { 534 | padding: 100px 0; 535 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 536 | color: var(--text-light); 537 | text-align: center; 538 | } 539 | 540 | .cta h2 { 541 | font-size: 2.8rem; 542 | font-weight: 800; 543 | margin-bottom: 1.5rem; 544 | } 545 | 546 | .cta p { 547 | font-size: 1.3rem; 548 | margin-bottom: 3rem; 549 | opacity: 0.95; 550 | } 551 | 552 | .cta-buttons { 553 | display: flex; 554 | gap: 1.5rem; 555 | justify-content: center; 556 | flex-wrap: wrap; 557 | } 558 | 559 | /* Footer */ 560 | .footer { 561 | background-color: var(--bg-dark); 562 | color: var(--text-light); 563 | padding: 4rem 0 2rem; 564 | } 565 | 566 | .footer-content { 567 | display: grid; 568 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 569 | gap: 3rem; 570 | margin-bottom: 3rem; 571 | } 572 | 573 | .footer-section h4 { 574 | font-size: 1.3rem; 575 | margin-bottom: 1.5rem; 576 | color: var(--text-light); 577 | } 578 | 579 | .footer-section p { 580 | color: rgba(255, 255, 255, 0.7); 581 | margin-bottom: 1.5rem; 582 | } 583 | 584 | .footer-links { 585 | display: flex; 586 | gap: 1.5rem; 587 | } 588 | 589 | .footer-links a, 590 | .footer-section a { 591 | color: rgba(255, 255, 255, 0.7); 592 | text-decoration: none; 593 | transition: var(--transition); 594 | } 595 | 596 | .footer-links a:hover, 597 | .footer-section a:hover { 598 | color: var(--primary-color); 599 | } 600 | 601 | .footer-section ul { 602 | list-style: none; 603 | } 604 | 605 | .footer-section li { 606 | margin-bottom: 0.8rem; 607 | } 608 | 609 | .footer-bottom { 610 | text-align: center; 611 | padding-top: 2rem; 612 | border-top: 1px solid rgba(255, 255, 255, 0.1); 613 | color: rgba(255, 255, 255, 0.6); 614 | } 615 | 616 | /* Animations */ 617 | @keyframes fadeInUp { 618 | from { 619 | opacity: 0; 620 | transform: translateY(30px); 621 | } 622 | to { 623 | opacity: 1; 624 | transform: translateY(0); 625 | } 626 | } 627 | 628 | /* Responsive Design */ 629 | @media (max-width: 1024px) { 630 | .hero-title { 631 | font-size: 2.8rem; 632 | } 633 | 634 | .hero-subtitle { 635 | font-size: 1.1rem; 636 | } 637 | 638 | .hero-stats { 639 | gap: 2rem; 640 | } 641 | 642 | .stat-number { 643 | font-size: 2rem; 644 | } 645 | } 646 | 647 | @media (max-width: 768px) { 648 | .logo-text { 649 | display: inline; 650 | } 651 | 652 | .nav-links { 653 | display: none; 654 | position: absolute; 655 | top: 100%; 656 | left: 0; 657 | right: 0; 658 | background-color: var(--bg-light); 659 | flex-direction: column; 660 | padding: 1rem; 661 | box-shadow: var(--shadow); 662 | } 663 | 664 | .nav-links.active { 665 | display: flex; 666 | } 667 | 668 | .mobile-menu-toggle { 669 | display: flex; 670 | } 671 | 672 | .hero { 673 | padding: 60px 0 40px; 674 | } 675 | 676 | .hero-title { 677 | font-size: 2.2rem; 678 | } 679 | 680 | .hero-subtitle { 681 | font-size: 1rem; 682 | } 683 | 684 | .hero-buttons { 685 | flex-direction: column; 686 | align-items: stretch; 687 | } 688 | 689 | .hero-stats { 690 | flex-direction: column; 691 | gap: 1.5rem; 692 | } 693 | 694 | .features, 695 | .screenshots, 696 | .installation, 697 | .use-cases, 698 | .cta { 699 | padding: 60px 0; 700 | } 701 | 702 | .section-title { 703 | font-size: 2rem; 704 | } 705 | 706 | .features-grid { 707 | grid-template-columns: 1fr; 708 | } 709 | 710 | .installation-methods { 711 | grid-template-columns: 1fr; 712 | } 713 | 714 | .quick-start { 715 | padding: 2rem; 716 | } 717 | 718 | .quick-start-steps { 719 | flex-direction: column; 720 | } 721 | 722 | .screenshot-features { 723 | grid-template-columns: 1fr; 724 | } 725 | 726 | .use-cases-grid { 727 | grid-template-columns: 1fr; 728 | } 729 | 730 | .cta h2 { 731 | font-size: 2rem; 732 | } 733 | 734 | .cta p { 735 | font-size: 1.1rem; 736 | } 737 | 738 | .footer-content { 739 | grid-template-columns: 1fr; 740 | } 741 | } 742 | 743 | @media (max-width: 480px) { 744 | .logo-text { 745 | font-size: 0.9rem; 746 | } 747 | 748 | .hero-title { 749 | font-size: 1.8rem; 750 | } 751 | 752 | .hero-subtitle { 753 | font-size: 0.95rem; 754 | } 755 | 756 | .btn { 757 | padding: 12px 20px; 758 | font-size: 0.9rem; 759 | } 760 | 761 | .section-title { 762 | font-size: 1.8rem; 763 | } 764 | 765 | .feature-card { 766 | padding: 1.5rem; 767 | } 768 | } 769 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Simple Coding Time Tracker - Track Your Coding Time in VS Code 10 | 11 | 12 | 13 | 14 | 15 | 35 | 36 | 37 |
38 |
39 |
40 |

Track Your Coding Time with Precision

41 |

A powerful VS Code extension that helps you monitor and analyze your coding time across projects, branches, and programming languages

42 | 56 |
57 |
58 |
50+
59 |
Languages Supported
60 |
61 |
62 |
Auto
63 |
Branch Tracking
64 |
65 |
66 |
Smart
67 |
Activity Detection
68 |
69 |
70 |
71 |
72 |
73 | 74 | 75 |
76 |
77 |

Powerful Features

78 |

Everything you need to understand your coding habits

79 | 80 |
81 |
82 |
⏱️
83 |

Automatic Time Tracking

84 |

Seamlessly tracks your coding time in the background. No manual start/stop needed.

85 |
86 | 87 |
88 |
🌿
89 |

Git Branch Tracking

90 |

Automatically tracks time spent on different Git branches for comprehensive analysis.

91 |
92 | 93 |
94 |
💻
95 |

Language Detection

96 |

Supports 50+ programming languages with automatic detection based on file extensions.

97 |
98 | 99 |
100 |
🎯
101 |

Smart Activity Detection

102 |

Automatically pauses tracking during periods of inactivity with configurable timeouts.

103 |
104 | 105 |
106 |
🔍
107 |

Advanced Filtering

108 |

Filter data by date range, project, branch, and programming language for detailed insights.

109 |
110 | 111 |
112 |
📊
113 |

Interactive Visualizations

114 |

Beautiful charts including project distribution, daily timeline, activity heatmap, and language breakdown.

115 |
116 | 117 |
118 |
🎨
119 |

Theme-Aware Design

120 |

Automatically adapts to VS Code's light and dark themes for a seamless experience.

121 |
122 | 123 |
124 |
💪
125 |

Health Notifications

126 |

Proactive reminders for eye rest (20-20-20 rule), stretching, and breaks based on scientific intervals.

127 |
128 | 129 |
130 |
⚙️
131 |

Dedicated Settings View

132 |

Easy-to-use interface for configuring all extension options with validation and descriptions.

133 |
134 |
135 |
136 |
137 | 138 | 139 |
140 |
141 |

See It In Action

142 |

Beautiful, intuitive interface that adapts to your theme

143 | 144 |
145 |
146 |

Light Theme - Comprehensive Dashboard

147 | Summary View Light Theme 148 |

View detailed reports with interactive charts, project distribution, and daily activity timeline

149 |
150 | 151 |
152 |

Dark Theme - Seamless Integration

153 | Summary View Dark Theme 154 |

Theme-aware visualizations that perfectly match your VS Code appearance

155 |
156 | 157 |
158 |
159 |

Status Bar Integration

160 | Status Bar 161 |

Real-time display of today's coding time, resets daily at midnight

162 |
163 | 164 |
165 |

Detailed Tooltips

166 | Tooltip 167 |

Hover for weekly, monthly, and all-time statistics

168 |
169 | 170 |
171 |

Easy Settings Management

172 | Settings View 173 |

Intuitive settings interface with clear descriptions and validation

174 |
175 |
176 |
177 |
178 |
179 | 180 | 181 |
182 |
183 |

Easy Installation

184 |

Get started in seconds

185 | 186 |
187 |
188 |

Method 1: VS Code Marketplace

189 |
    190 |
  1. Open Visual Studio Code
  2. 191 |
  3. Go to Extensions view (Ctrl+Shift+X or Cmd+Shift+X on macOS)
  4. 192 |
  5. Search for "Simple Coding Time Tracker"
  6. 193 |
  7. Click "Install"
  8. 194 |
195 | Install from Marketplace 196 |
197 | 198 |
199 |

Method 2: Open VSX Registry

200 |

For Cursor, Windsurf, Trae, VS Codium, and other compatible editors

201 |
    202 |
  1. Open your editor's extensions view
  2. 203 |
  3. Search for "Simple Coding Time Tracker"
  4. 204 |
  5. Click "Install"
  6. 205 |
206 | Install from Open VSX 207 |
208 |
209 | 210 |
211 |

Quick Start Guide

212 |
213 |
214 |
1
215 |
216 |

Install the Extension

217 |

Use either VS Code Marketplace or Open VSX Registry

218 |
219 |
220 |
221 |
2
222 |
223 |

Start Coding

224 |

The extension automatically starts tracking when you begin coding

225 |
226 |
227 |
228 |
3
229 |
230 |

View Your Stats

231 |

Click the status bar or run SCTT: Show Coding Time Summary

232 |
233 |
234 |
235 |
236 |
237 |
238 | 239 | 240 |
241 |
242 |

Perfect For

243 |
244 |
245 |
👨‍💻
246 |

Freelance Developers

247 |

Track billable hours accurately across multiple client projects

248 |
249 |
250 |
🏢
251 |

Development Teams

252 |

Understand project time allocation and improve team productivity

253 |
254 |
255 |
📚
256 |

Students & Learners

257 |

Monitor learning progress and maintain consistent coding practice

258 |
259 |
260 |
📈
261 |

Productivity Enthusiasts

262 |

Analyze coding habits and optimize workflow efficiency

263 |
264 |
265 |
266 |
267 | 268 | 269 |
270 |
271 |

Start Tracking Your Coding Time Today

272 |

Join developers worldwide who are gaining insights into their coding habits

273 | 277 |
278 |
279 | 280 | 281 | 315 | 316 | 317 | 318 | 319 | --------------------------------------------------------------------------------