I built a water reminder plugin for JetBrains and VS Code
The problem
I spend most of my day inside WebStorm. I code, debug, think, and repeat —
and I constantly forget to drink water. Phone notifications break my focus,
external apps feel like overkill.
I searched the JetBrains Marketplace and found nothing that did it in a
simple, clean way. So I built it myself.
Building the JetBrains plugin
The JetBrains plugin ecosystem is based on the IntelliJ Platform SDK,
and plugins are written in Kotlin.
The timer service
The core of the plugin is a background timer that fires a notification
every X minutes:
fun start() {
val settings = WaterReminderSettings.getInstance().state
if (!settings.enabled) return
scheduler = Executors.newSingleThreadScheduledExecutor()
scheduler?.scheduleAtFixedRate(
{
try {
showNotification()
} catch (e: Exception) {
e.printStackTrace()
}
},
settings.intervalMinutes.toLong(),
settings.intervalMinutes.toLong(),
TimeUnit.MINUTES
)
}
private fun showNotification() {
ApplicationManager.getApplication().invokeLater {
NotificationGroupManager.getInstance()
.getNotificationGroup("Water Reminder")
.createNotification(
"💧 Time to drink water!",
"Stay hydrated! Drink a glass of water.",
NotificationType.INFORMATION
)
.notify(null)
}
}
The status bar widget
The 💧 icon in the bottom right status bar is implemented via
StatusBarWidgetFactory:
class WaterStatusBarWidget(private val project: Project) :
StatusBarWidget, StatusBarWidget.IconPresentation {
override fun getIcon(): javax.swing.Icon {
val s = WaterReminderSettings.getInstance().state
return if (s.enabled)
IconLoader.getIcon("/icons/water_drop.svg", javaClass)
else
IconLoader.getIcon("/icons/water_drop_disabled.svg", javaClass)
}
override fun getTooltipText(): String {
val s = WaterReminderSettings.getInstance().state
return if (s.enabled)
"💧 Water Reminder — every ${s.intervalMinutes} min"
else
"💧 Water Reminder — disabled"
}
override fun getClickConsumer() = Consumer<MouseEvent> {
ShowSettingsUtil.getInstance()
.showSettingsDialog(project, WaterReminderConfigurable::class.java)
}
}
Persistent settings
User preferences are saved using PersistentStateComponent:
@State(name = "WaterReminderSettings", storages = [Storage("WaterReminder.xml")])
class WaterReminderSettings : PersistentStateComponent<WaterReminderSettings.State> {
data class State(
var intervalMinutes: Int = 30,
var enabled: Boolean = true
)
companion object {
fun getInstance(): WaterReminderSettings =
ApplicationManager.getApplication()
.getService(WaterReminderSettings::class.java)
}
}
Porting to VS Code
On VS Code there were already several similar plugins — but I saw it as
a good opportunity to learn how extension development works on a completely
different platform.
VS Code extensions are written in TypeScript and the API is much more
straightforward.
The full extension
The entire plugin fits in a single extension.ts file:
export function activate(context: vscode.ExtensionContext) {
statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right, 100
);
statusBarItem.command = 'waterReminder.openSettings';
statusBarItem.show();
context.subscriptions.push(
vscode.commands.registerCommand('waterReminder.openSettings', () => {
vscode.commands.executeCommand(
'workbench.action.openSettings', 'waterReminder'
);
})
);
startTimer();
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('waterReminder')) {
startTimer();
updateStatusBar();
}
})
);
}
function startTimer() {
if (timer) clearInterval(timer);
const config = vscode.workspace.getConfiguration('waterReminder');
const enabled = config.get<boolean>('enabled', true);
const interval = config.get<number>('intervalMinutes', 30);
if (!enabled) return;
timer = setInterval(() => {
vscode.window.showInformationMessage(
'💧 Time to drink water! Stay hydrated!',
'Thanks! 👍'
);
}, interval * 60 * 1000);
}
Settings declared in package.json
No extra code needed — settings are declared directly in package.json:
"contributes": {
"configuration": {
"title": "Water Reminder",
"properties": {
"waterReminder.enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable the water reminder"
},
"waterReminder.intervalMinutes": {
"type": "number",
"default": 30,
"minimum": 1,
"maximum": 480,
"description": "Reminder interval in minutes"
}
}
}
}
What I learned
Building the same tool on two different ecosystems taught me a lot:
- The JetBrains SDK is powerful but has a steeper learning curve —
you need to understand
plugin.xml, extension points, and service registration - The VS Code API is simpler —
package.jsonhandles most of the configuration and the API surface is smaller - Publishing on JetBrains Marketplace requires a review (1-2 business days)
- Publishing on VS Code Marketplace is nearly instant via
.vsixupload
The result
Water Reminder is now available on both platforms, free and open source:
Stay hydrated! 💧
Top comments (0)