Hey, listen! Triforce adds a bit of RPG flavor to your coding — XP, levels, and achievements while you work.
I have ADHD, and coding can sometimes feel like a grind — it’s hard to stay consistent or even get started some days. That’s part of why I fell in love with Neovim: it’s customizable, expressive, and makes the act of writing code feel fun again.
Triforce is actually my first-ever Neovim plugin (and the first plugin I’ve ever built in general). I’d always wanted to make something of my own, but I never really knew where to start. Once I got into Neovim’s Lua ecosystem, I got completely hooked. I started experimenting, tinkering, breaking things, and slowly, Triforce came to life.
I made it to gamify my coding workflow — to turn those long, sometimes frustrating coding sessions into something that feels rewarding. Watching the XP bar fill up, unlocking achievements, and seeing my progress in real time gives me that little dopamine boost that helps me stay focused and motivated.
I named it Triforce just because I love The Legend of Zelda — no deep reason beyond that.
The UI is heavily inspired by siduck’s gorgeous designs and nvzone/typr — their aesthetic sense and clean interface ideas played a huge role in how this turned out. Building it with Volt.nvim made the process so much smoother and helped me focus on bringing those ideas to life.
{
"gisketch/triforce.nvim",
dependencies = {
"nvzone/volt",
},
config = function()
require("triforce").setup({
-- Optional: Add your configuration here
keymap = {
show_profile = "<leader>tp", -- Open profile with <leader>tp
},
})
end,
}
use {
"gisketch/triforce.nvim",
requires = { "nvzone/volt" },
config = function()
require("triforce").setup({
keymap = {
show_profile = "<leader>tp",
},
})
end
}
Plug 'nvzone/volt'
Plug 'gisketch/triforce.nvim'
lua << EOF
require("triforce").setup({
keymap = {
show_profile = "<leader>tp",
},
})
EOF
Triforce comes with sensible defaults, but you can customize everything:
require("triforce").setup({
enabled = true, -- Enable/disable the entire plugin
gamification_enabled = true, -- Enable XP, levels, achievements
-- Notification settings
notifications = {
enabled = true, -- Master toggle for all notifications
level_up = true, -- Show level up notifications
achievements = true, -- Show achievement unlock notifications
},
-- Keymap configuration
keymap = {
show_profile = "<leader>tp", -- Set to nil to disable default keymap
},
-- Auto-save interval (in seconds)
auto_save_interval = 300, -- Save stats every 5 minutes
-- Add custom language support
custom_languages = {
gleam = { icon = "✨", name = "Gleam" },
odin = { icon = "🔷", name = "Odin" },
-- Add more languages...
},
-- Customize level progression (optional)
level_progression = {
tier_1 = { min_level = 1, max_level = 10, xp_per_level = 300 }, -- Levels 1-10
tier_2 = { min_level = 11, max_level = 20, xp_per_level = 500 }, -- Levels 11-20
tier_3 = { min_level = 21, max_level = math.huge, xp_per_level = 1000 }, -- Levels 21+
},
-- Customize XP rewards (optional)
xp_rewards = {
char = 1, -- XP per character typed
line = 1, -- XP per new line
save = 50, -- XP per file save
},
})
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Enable/disable the plugin |
gamification_enabled |
boolean |
true |
Enable gamification features |
notifications.enabled |
boolean |
true |
Master toggle for notifications |
notifications.level_up |
boolean |
true |
Show level up notifications |
notifications.achievements |
boolean |
true |
Show achievement notifications |
auto_save_interval |
number |
300 |
Auto-save interval in seconds |
keymap.show_profile |
string | nil |
nil |
Keymap for opening profile |
custom_languages |
table | nil |
nil |
Custom language definitions |
level_progression |
table | nil |
See below | Custom XP requirements per level tier |
xp_rewards |
table | nil |
See below | Custom XP rewards for actions |
By default, Triforce uses a simple, easy-to-reach leveling system:
Example progression:
You can customize this by overriding level_progression in your setup. For example, to make it even easier:
require("triforce").setup({
level_progression = {
tier_1 = { min_level = 1, max_level = 15, xp_per_level = 200 }, -- Super easy early levels
tier_2 = { min_level = 16, max_level = 30, xp_per_level = 400 },
tier_3 = { min_level = 31, max_level = math.huge, xp_per_level = 800 },
},
})
By default, Triforce awards XP for different coding activities:
You can customize these values to match your preferences. For example, if you want to emphasize quality over quantity and reward saves more:
require("triforce").setup({
xp_rewards = {
char = 0.5, -- Less XP for characters
line = 2, -- More XP for new lines
save = 100, -- Reward file saves heavily
},
})
Or if you prefer to focus on typing volume:
require("triforce").setup({
xp_rewards = {
char = 2, -- More XP per character
line = 5, -- Moderate XP for lines
save = 25, -- Less emphasis on saves
},
})
Triforce provides modular statusline components for lualine.nvim, letting you display your coding stats right in your statusline.
| Component | Default Display (uses NerdFont) | Description |
|---|---|---|
level |
Lv.27 ████░░ |
Level + XP progress bar |
achievements |
🏆 12/18 |
Unlocked/total achievements |
streak |
🔥 5 |
Current coding streak (days) |
session_time |
⏰ 2h 34m |
Current session duration |
Add Triforce components to your lualine configuration:
require('lualine').setup({
sections = {
lualine_x = {
-- Add one or more components
function() return require('triforce.lualine').level() end,
function() return require('triforce.lualine').achievements() end,
'encoding', 'fileformat', 'filetype'
},
}
})
Use the components() helper to get all components at once:
local triforce = require('triforce.lualine').components()
require('lualine').setup({
sections = {
lualine_x = {
triforce.level,
triforce.achievements,
triforce.streak,
triforce.session_time,
'encoding', 'fileformat', 'filetype'
},
}
})
Each component can be customized independently:
-- Default: prefix + level + bar
function()
return require('triforce.lualine').level()
end
-- Result: Lv.27 ████░░
-- Show percentage instead of bar
function()
return require('triforce.lualine').level({
show_bar = false,
show_percent = true,
})
end
-- Result: Lv.27 90%
-- Show everything (XP numbers + percentage)
function()
return require('triforce.lualine').level({
show_bar = true,
show_percent = true,
show_xp = true,
bar_length = 8,
})
end
-- Result: Lv.27 ████████ 90% 450/500
-- Customize bar style
function()
return require('triforce.lualine').level({
bar_chars = { filled = '●', empty = '○' },
bar_length = 10,
})
end
-- Result: Lv.27 ●●●●●●●●●○
-- Custom prefix or no prefix
function()
return require('triforce.lualine').level({
prefix = 'Level ', -- or set to '' for no prefix
})
end
-- Result: Level 27 ████░░
Options:
prefix (string): Text prefix before level number (default: 'Lv.')show_level (boolean): Show level number (default: true)show_bar (boolean): Show progress bar (default: true)show_percent (boolean): Show percentage (default: false)show_xp (boolean): Show XP numbers like 450/500 (default: false)bar_length (number): Progress bar length (default: 6)bar_chars (table): { filled = '█', empty = '░' } (default)-- Default
function()
return require('triforce.lualine').achievements()
end
-- Result: 12/18
-- Custom icon or no icon
function()
return require('triforce.lualine').achievements({
icon = '', -- or '' for no icon
})
end
-- Result: 12/18
Options:
icon (string): Icon to display (default: '' - trophy)show_count (boolean): Show unlocked/total count (default: true)-- Default
function()
return require('triforce.lualine').streak()
end
-- Result: 5
-- Different icon
function()
return require('triforce.lualine').streak({
icon = '',
})
end
-- Result: 5
Options:
icon (string): Icon to display (default: '' - flame)show_days (boolean): Show day count (default: true)Note: The streak component returns an empty string when streak is 0, so it won't clutter your statusline.
-- Default (short format)
function()
return require('triforce.lualine').session_time()
end
-- Result: 2h 34m
-- Long format (2:34:12 instead of 2h 34m)
function()
return require('triforce.lualine').session_time({
format = 'long',
})
end
-- Result: 2:34:12
-- Different icon
function()
return require('triforce.lualine').session_time({
icon = '', -- watch icon
})
end
-- Result: 2h 34m
Options:
icon (string): Icon to display (default: '' - clock)show_duration (boolean): Show time duration (default: true)format (string): 'short' (2h 34m) or 'long' (2:34:12) (default: 'short')Set defaults for all components:
-- Configure defaults
require('triforce.lualine').setup({
level = {
prefix = 'Level ',
bar_length = 8,
show_percent = true,
},
achievements = {
icon = '',
},
streak = {
icon = '',
},
session_time = {
icon = '',
format = 'long',
},
})
-- Then use components normally
local triforce = require('triforce.lualine').components()
require('lualine').setup({
sections = {
lualine_x = {
function() return require('triforce.lualine').level() end,
},
}
})
-- Result: Lv.27 ████░░
local triforce = require('triforce.lualine').components()
require('lualine').setup({
sections = {
lualine_c = { 'filename' },
lualine_x = {
triforce.session_time,
triforce.streak,
triforce.achievements,
triforce.level,
'encoding', 'filetype'
},
}
})
-- Result: 2h 34m 5 12/18 Lv.27 ████░░ ...
require('triforce.lualine').setup({
level = {
prefix = '', -- No prefix, just number
bar_chars = { filled = '●', empty = '○' },
bar_length = 10,
show_percent = true,
},
achievements = {
icon = '', -- medal icon
},
streak = {
icon = '', -- bolt icon
},
})
local triforce = require('triforce.lualine').components()
-- Now all components use your custom config
-- Result: 2h 34m 5 12/18 27 ●●●●●●●●●○ 90%
| Command | Description |
|---|---|
:lua require("triforce").show_profile() |
Open the Triforce profile UI |
:lua require("triforce").get_stats() |
Get current stats programmatically |
:lua require("triforce").reset_stats() |
Reset all stats (useful for testing) |
:lua require("triforce").save_stats() |
Force save stats immediately |
:lua require("triforce").debug_languages() |
Debug language tracking |
The profile has 3 tabs:
Keybindings in Profile:
Tab: Cycle between tabsH / L or ← / →: Navigate achievement pagesq / Esc: Close profileTriforce includes 18 built-in achievements across 5 categories:
Triforce supports 50+ programming languages out of the box, but you can add more:
require("triforce").setup({
custom_languages = {
gleam = {
icon = "✨",
name = "Gleam"
},
zig = {
icon = "⚡",
name = "Zig"
},
},
})
Turn off all notifications or specific types:
require("triforce").setup({
notifications = {
enabled = true, -- Keep enabled
level_up = false, -- Disable level up notifications
achievements = true, -- Keep achievement notifications
},
})
If you prefer to set your own keymap:
require("triforce").setup({
keymap = {
show_profile = nil, -- Don't create default keymap
},
})
-- Set your own keymap
vim.keymap.set("n", "<C-s>", function()
require("triforce").show_profile()
end, { desc = "Show Triforce Stats" })
Stats are saved to:
~/.local/share/nvim/triforce_stats.json
The file is automatically backed up before each save to:
~/.local/share/nvim/triforce_stats.json.bak
{
"xp": 15420,
"level": 12,
"chars_typed": 45230,
"lines_typed": 1240,
"sessions": 42,
"time_coding": 14580,
"achievements": {
"first_100": true,
"level_10": true
},
"chars_by_language": {
"lua": 12000,
"python": 8500
},
"daily_activity": {
"2025-11-07": 145,
"2025-11-08": 203
},
"current_streak": 5,
"longest_streak": 12
}
Have a feature idea? Open an issue on GitHub!
Contributions are welcome! Here's how to help:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)# Clone the repo
git clone https://github.com/gisketch/triforce.nvim.git
cd triforce.nvim
# Symlink to Neovim config for testing
ln -s $(pwd) ~/.local/share/nvim/site/pack/plugins/start/triforce.nvim
MIT License - see LICENSE for details.
Made with ❤️ for the Neovim community
⭐ Star this repo if you find it useful!