timecoder
Version:
TimeCoder is a VS Code extension designed to boost your productivity while coding. It features a stopwatch to track how long you take to solve problems or complete tasks, and a Pomodoro timer to challenge yourself to finish tasks within a set time — all w
423 lines (357 loc) • 13.1 kB
JavaScript
const vscode = require('vscode');
let mode = 'stopwatch'; // 'stopwatch' or 'pomodoro'
let pomodoroDuration = 1200; // Pomodoro timer in seconds
let pomodoroTime = 1200; // Pomodoro timer in seconds
let stopwatchTime = 0; // Stopwatch timer in seconds
let sessionStartTime;
let sessionInterval;
let getContext;
// Independent states for each mode
let stopwatchState = {
timerRunning: false,
interval: null,
};
let pomodoroState = {
timerRunning: false,
interval: null,
};
let statusBarItems = {}; // Holds the status bar items
let analyticsPanel;
let dashboardPanel;
// Maintain Historical Data
const storeHistory = (/** @type {string} */ mode, /** @type {string} */ elapsedSeconds) => {
const historyKey =
mode === "stopwatch"
? "stopwatchHistory"
: mode === "pomodoro"
? "pomodoroHistory"
: "sessionHistory";
const existingHistory = JSON.parse(getContext.globalState.get(historyKey) || "[]");
const newEntry = {
id: Date.now(),
mode,
elapsedSeconds,
timestamp: new Date().toISOString(),
};
getContext.globalState.update(historyKey, JSON.stringify([...existingHistory, newEntry]));
};
// Helper function to format time in hh:mm:ss
/**
* @param {number} seconds
*/
function formatTime(seconds) {
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${String(hrs).padStart(2, '0')} : ${String(mins).padStart(2, '0')} : ${String(secs).padStart(2, '0')}`;
}
// Update the status bar with the proper text
function updateStatusBar() {
if (!statusBarItems || Object.keys(statusBarItems).length === 0) {
vscode.window.showErrorMessage('Status bar items are not initialized.');
return;
}
// Update session elapsed time
const elapsedSeconds = Math.floor((Date.now() - sessionStartTime) / 1000);
statusBarItems.session.text = `Session ⏳ ${elapsedSeconds > 0 ? formatTime(elapsedSeconds) : "starting..."}`;
statusBarItems.session.tooltip = 'Elapsed time since the session started';
if (mode === 'pomodoro') {
const pomodoroTimeFormatted = formatTime(pomodoroTime / 10);
const { timerRunning } = pomodoroState;
statusBarItems.mode.text = `⏰`;
statusBarItems.mode.tooltip = 'Switch to Stopwatch mode'; // Tooltip
statusBarItems.mode.command = 'timeCoder.toggleMode';
statusBarItems.time.text = `${pomodoroTime === 0 ? 'Pomodoro' : pomodoroTimeFormatted}`;
statusBarItems.time.tooltip = `${pomodoroTime === 0 ? 'TimeCoder by DjArtimus' : 'Remaining time for the Pomodoro timer'}`; // Tooltip
statusBarItems.time.command = 'timeCoder.openWebview';
statusBarItems.toggle.text = timerRunning ? `⏸` : `▶`;
statusBarItems.toggle.tooltip = timerRunning ? 'Pause the Pomodoro timer' : 'Start the Pomodoro timer'; // Tooltip
statusBarItems.toggle.command = 'timeCoder.toggleTimer';
statusBarItems.reset.text = `🔄`;
statusBarItems.reset.tooltip = 'Reset the Pomodoro timer'; // Tooltip
statusBarItems.reset.command = 'timeCoder.resetTimer';
statusBarItems.decrease.text = `⏬`;
statusBarItems.decrease.tooltip = 'Decrease the Pomodoro timer duration'; // Tooltip
statusBarItems.decrease.command = 'timeCoder.adjustPomodoroTimeDecrease';
statusBarItems.increase.text = `⏫`;
statusBarItems.increase.tooltip = 'Increase the Pomodoro timer duration'; // Tooltip
statusBarItems.increase.command = 'timeCoder.adjustPomodoroTimeIncrease';
} else {
const stopwatchTimeFormatted = formatTime(stopwatchTime / 10);
const { timerRunning } = stopwatchState;
statusBarItems.mode.text = `⏱`;
statusBarItems.mode.tooltip = 'Switch to Pomodoro mode'; // Tooltip
statusBarItems.mode.command = 'timeCoder.toggleMode';
statusBarItems.time.text = `${stopwatchTime === 0 ? 'StopWatch' : stopwatchTimeFormatted}`;
statusBarItems.time.tooltip = `${stopwatchTime === 0 ? 'TimeCoder by DjArtimus' : 'Elapsed time for current task'}`; // Tooltip
statusBarItems.time.command = 'timeCoder.openWebview';
statusBarItems.toggle.text = timerRunning ? `⏸` : `▶`;
statusBarItems.toggle.tooltip = timerRunning ? 'Pause the stopwatch' : 'Start the stopwatch'; // Tooltip
statusBarItems.toggle.command = 'timeCoder.toggleTimer';
statusBarItems.reset.text = `🔄`;
statusBarItems.reset.tooltip = 'Reset the stopwatch'; // Tooltip
statusBarItems.reset.command = 'timeCoder.resetTimer';
statusBarItems.increase.text = ``;
statusBarItems.decrease.text = ``;
}
for (const item in statusBarItems) {
statusBarItems[item].show();
}
}
function startSessionTimer() {
sessionInterval = setInterval(() => {
updateStatusBar();
}, 500);
}
// Toggle the timer (start/stop)
function toggleTimer() {
if (mode === 'stopwatch') {
const state = stopwatchState;
// Increment Stopwatch count
if (state.timerRunning) {
clearInterval(state.interval);
} else {
state.interval = setInterval(() => {
stopwatchTime++;
updateStatusBar();
}, 100);
}
state.timerRunning = !state.timerRunning;
} else if (mode === 'pomodoro') {
const state = pomodoroState;
if (pomodoroTime / 100 > 0) {
if (state.timerRunning) {
clearInterval(state.interval);
} else {
state.interval = setInterval(() => {
if (pomodoroTime / 100 > 0) {
pomodoroTime--;
updateStatusBar();
} else {
clearInterval(state.interval);
state.timerRunning = false;
updateStatusBar();
vscode.window.showInformationMessage('Pomodoro timer completed!');
}
}, 100);
}
state.timerRunning = !state.timerRunning;
} else {
vscode.window.showErrorMessage('Pomodoro time not set!');
}
}
updateStatusBar();
}
// Reset the timer
function resetTimer() {
if (mode === 'stopwatch') {
const elapsed = formatTime(stopwatchTime / 10); // Elapsed time
storeHistory("stopwatch", elapsed);
stopwatchTime = 0;
clearInterval(stopwatchState.interval);
stopwatchState.timerRunning = false;
} else if (mode === 'pomodoro') {
const remaining = formatTime((pomodoroDuration - pomodoroTime) / 10);
storeHistory("pomodoro", remaining);
pomodoroTime = 1200;
clearInterval(pomodoroState.interval);
pomodoroState.timerRunning = false;
} else if (mode === 'session') {
const elapsed = formatTime(Math.floor((Date.now() - sessionStartTime) / 1000));
storeHistory("session", elapsed);
sessionStartTime = Date.now();
}
updateStatusBar();
}
// Adjust Pomodoro time (increase or decrease)
/**
* @param {boolean} increase
*/
function adjustPomodoroTime(increase, mins = 1) {
if (increase) { pomodoroTime += mins * 600; pomodoroDuration += mins * 600; }
else { if (pomodoroTime > 0 && pomodoroTime > mins * 580) { pomodoroTime -= mins * 600; pomodoroDuration -= mins * 600; } else { vscode.window.showErrorMessage('Pomodoro time not sufficient to decrease!') } }
updateStatusBar();
}
// Switch between Stopwatch and Pomodoro modes
function toggleMode() {
mode === 'stopwatch' ? mode = 'pomodoro' : mode = 'stopwatch';
updateStatusBar();
}
/**
* @param {vscode.WebviewPanel | vscode.WebviewView} webviewPanel
*/
function Communicator(webviewPanel) {
webviewPanel.webview.onDidReceiveMessage(
(/** @type {{ command: any; }} */ message) => {
const command = message.command
switch (command) {
case 'start-stop-Stopwatch':
mode = 'stopwatch';
toggleTimer(); // Reuse existing function
break;
case 'resetStopwatch':
mode = 'stopwatch';
resetTimer();
break;
case 'start-stop-Pomodoro':
mode = 'pomodoro';
toggleTimer();
break;
case 'resetPomodoro':
mode = 'pomodoro';
resetTimer();
break;
case 'adjustPomodoroIncrease':
mode = 'pomodoro';
adjustPomodoroTime(true);
break;
case 'adjustPomodoroDecrease':
mode = 'pomodoro';
adjustPomodoroTime(false);
break;
case 'resetSession':
mode = 'session';
resetTimer();
break;
case 'saveSession':
vscode.window.showInformationMessage('Session saved!');
break;
default:
const adjustTimer = (/** @type {boolean} */ operation) => adjustPomodoroTime(operation, Number(command.split("_")[1]))
if (command.includes("adjustPomodoro")) {
command.includes("adjustPomodoroIncrease") ? adjustTimer(true) : adjustTimer(false);
} else {
vscode.window.showErrorMessage('Unknown command');
}
}
},
null,
[]
);
const updateTimersInWebview = () => {
const stopwatch = { timmerRunning: stopwatchState.timerRunning, time: formatTime(stopwatchTime / 10) };
const pomodoro = { timmerRunning: pomodoroState.timerRunning, time: formatTime(pomodoroTime / 10) }
const statusBarData = {
mode,
stopwatch,
pomodoro,
sessionElapsed: formatTime(Math.floor((Date.now() - sessionStartTime) / 1000)),
};
webviewPanel && webviewPanel.webview.postMessage({ command: 'updateTimers', data: statusBarData });
};
setInterval(updateTimersInWebview, 100);
}
// Open the WebView
const { generateWebviewHtml } = require('./webviewContent.js');
function openWebview() {
if (dashboardPanel) {
dashboardPanel.reveal(vscode.ViewColumn.One);
} else {
dashboardPanel = vscode.window.createWebviewPanel(
'timeCoderWebview',
'TimeCoder - Dashboard',
vscode.ViewColumn.One,
{
enableScripts: true,
localResourceRoots: [],
}
);
dashboardPanel.webview.html = generateWebviewHtml();
dashboardPanel.onDidDispose(
() => {
dashboardPanel = undefined;
},
undefined,
getContext.subscriptions
);
Communicator(dashboardPanel);
}
};
const { generateAnalyticsHtml } = require('./analyticsWebview.js');
function openAnalyticsWebview() {
// Check if the panel is already open
if (analyticsPanel) {
analyticsPanel.reveal(vscode.ViewColumn.One);
return;
}
analyticsPanel = vscode.window.createWebviewPanel(
'timeCoderAnalytics',
'TimeCoder - Analytics',
vscode.ViewColumn.One,
{ enableScripts: true }
);
analyticsPanel.webview.html = generateAnalyticsHtml(getContext);
// Handle when the webview is closed
analyticsPanel.onDidDispose(() => {
analyticsPanel = null;
});
}
const { getSidePanelHtmlContent } = require("./sidePanelContent.js")
class TimeCoderSidePanelProvider {
/**
* @param {vscode.Uri} extensionUri
*/
constructor(extensionUri) {
this.extensionUri = extensionUri;
}
/**
* @param {vscode.WebviewView} webviewView
*/
resolveWebviewView(webviewView) {
webviewView.webview.options = {
// Allow scripts in the webview
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(this.extensionUri, "media")],
};
// Set HTML content for the webview
webviewView.webview.html = getSidePanelHtmlContent();
Communicator(webviewView);
}
/**
* Generates HTML content for the webview
* @returns {string} HTML content
*/
}
// Activate the extension
/**
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
getContext = context;
sessionStartTime = Date.now();
statusBarItems.session = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100000);
statusBarItems.mode = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1000);
statusBarItems.time = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 999);
statusBarItems.toggle = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 998);
statusBarItems.reset = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 997);
statusBarItems.decrease = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 996);
statusBarItems.increase = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 995);
console.log('Activating extension...');
updateStatusBar(); // Check if this runs multiple times
console.log('Status bar updated.');
startSessionTimer();
context.subscriptions.push(
vscode.commands.registerCommand('timeCoder.startSessionTimer', startSessionTimer),
vscode.commands.registerCommand('timeCoder.toggleTimer', toggleTimer),
vscode.commands.registerCommand('timeCoder.resetTimer', resetTimer),
vscode.commands.registerCommand('timeCoder.adjustPomodoroTimeIncrease', () => adjustPomodoroTime(true)),
vscode.commands.registerCommand('timeCoder.adjustPomodoroTimeDecrease', () => adjustPomodoroTime(false)),
vscode.commands.registerCommand('timeCoder.toggleMode', toggleMode),
vscode.commands.registerCommand('timeCoder.openWebview', openWebview),
vscode.commands.registerCommand('timeCoder.openAnalytics', openAnalyticsWebview),
vscode.window.registerWebviewViewProvider("timeCoderSidePanel", new TimeCoderSidePanelProvider(context.extensionUri))
// vscode.window.registerTreeDataProvider('timeCoderView', new TimeCoderTreeDataProvider()),
// vscode.window.registerTreeDataProvider("timeCoderView", new EnhancedTreeDataProvider())
);
// vscode.window.showInformationMessage('TimeCoder is ready!');
}
// Deactivate the extension
function deactivate() {
clearInterval(stopwatchState.interval);
clearInterval(pomodoroState.interval);
clearInterval(sessionInterval);
}
module.exports = {
activate,
deactivate,
};