9 Commits

Author SHA1 Message Date
67255b346c fix(health,checker): fix plugin display bug and remove redundant version checks
- health.lua: referenced non-existent binary.plugin field instead of
  binary.plugins array, causing plugin names to never display in
  :checkhealth output
- checker.lua: removed redundant first loop in check() that called
  get_installed_version without using the result, and eliminated dead
  remaining counter that was immediately overwritten
2026-03-02 09:03:05 +00:00
93acbc55c5 update menu labels 2026-02-19 01:18:11 -05:00
d94fee916b handle duplicate binaries 2026-02-19 01:07:47 -05:00
ea73ed51b1 Update badge URLs in README.md 2026-02-18 23:33:18 -05:00
5867abd592 Update copyright year in LICENSE file 2026-02-18 23:27:29 -05:00
ded73a5073 large update 2026-02-18 23:21:33 -05:00
7ab9da9c56 feat: add auto_update config, clarify auto_install vs auto_update vs auto_check
- auto_install (default: true) — install missing binaries on register
- auto_check (default: true) — check for available updates periodically
- auto_update (default: false) — auto-update when newer versions found
- Disabling auto_check also disables auto_update
- Updated README and help docs
2026-02-19 03:24:40 +00:00
c905edc162 feat: add auto-install for missing binaries on register
- New config: auto_install.enabled (default true) and auto_install.silent
- When a plugin registers a binary via glaze.register() and it's missing,
  glaze automatically installs it in the background
- Can be disabled with auto_install = { enabled = false }
- Updated README and help docs
2026-02-19 03:23:15 +00:00
6927b0b914 feat: add health check for Go availability and registered binaries 2026-02-19 03:12:29 +00:00
21 changed files with 1028 additions and 206 deletions

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.lua]
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Git LFS tracking
docs/*.gif filter=lfs diff=lfs merge=lfs -text

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: taigrr # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]

21
.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# Logs
*.log
# OS files
.DS_Store
Thumbs.db
# Editor files
*.swp
*.swo
*~
.idea/
.vscode/
# Neovim
.nvim.lua
.nvimrc
.exrc
# Test artifacts
.tests/

15
.luarc.json Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"runtime": {
"version": "LuaJIT"
},
"diagnostics": {
"globals": ["vim"]
},
"workspace": {
"library": [
"${3rd}/luv/library"
],
"checkThirdParty": false
}
}

188
AGENTS.md Normal file
View File

@@ -0,0 +1,188 @@
# AGENTS.md
AI agent guide for working in glaze.nvim.
## Project Overview
**glaze.nvim** is a Neovim plugin that provides centralized management for Go binaries used by other Neovim plugins. Think "Mason for Go binaries" with a lazy.nvim-style UI.
- **Language**: Lua (Neovim plugin)
- **Requirements**: Neovim >= 0.9, Go installed
- **Author**: Tai Groot (taigrr)
## Directory Structure
```
lua/glaze/
├── init.lua # Main module: setup, config, registration API, binary path utilities
├── runner.lua # Task runner: parallel go install with concurrency control
├── checker.lua # Update checker: version comparison, auto-check scheduling
├── view.lua # UI: lazy.nvim-style floating window, keybinds, rendering
├── float.lua # Float window abstraction: backdrop, keymaps, resize handling
├── text.lua # Text rendering: buffered text with highlight segments
├── colors.lua # Highlight definitions: doughnut-inspired pink/magenta theme
├── health.lua # Health check: :checkhealth glaze
doc/
└── glaze.txt # Vim help documentation
```
## Key Concepts
### Binary Registration
Plugins register binaries via `require("glaze").register(name, url, opts)`. Registration stores metadata in `M._binaries` table. Auto-install triggers if enabled and binary is missing.
### Task Runner
`runner.lua` manages parallel `go install` jobs with configurable concurrency. Tasks have states: `pending → running → done|error`. The runner opens the UI automatically when tasks start.
### Update Checking
`checker.lua` compares installed versions (`go version -m`) against latest (`go list -m -json`). State persisted to `vim.fn.stdpath("data") .. "/glaze/state.json"`.
### UI Architecture
- `float.lua`: Generic floating window with backdrop (inspired by lazy.nvim)
- `view.lua`: Glaze-specific rendering, keybinds, spinner animation
- `text.lua`: Buffered text builder with highlight segments and extmarks
## Commands
| Command | Description |
| ---------------------- | ---------------------------------- |
| `:Glaze` | Open the UI |
| `:GlazeUpdate [name]` | Update all or specific binary |
| `:GlazeInstall [name]` | Install missing or specific binary |
| `:GlazeCheck` | Manual update check |
| `:checkhealth glaze` | Run health check |
## Testing
**No automated tests exist.** Manual testing workflow:
```lua
-- In Neovim:
:luafile % -- Reload current file
:Glaze -- Test UI
:checkhealth glaze -- Verify setup
```
Test with actual binaries:
```lua
require("glaze").setup({})
require("glaze").register("glow", "github.com/charmbracelet/glow")
:GlazeInstall glow
```
## Code Conventions
### Module Pattern
All modules return a table `M` with public functions. Private functions prefixed with `_`:
```lua
local M = {}
M._private_state = {}
function M.public_fn() end
function M._private_fn() end
return M
```
### Type Annotations
Uses LuaCATS (`---@class`, `---@param`, `---@return`) for type hints. LSP warnings about `vim` being undefined are expected (Neovim globals).
### Async Patterns
- `vim.fn.jobstart()` for external commands
- `vim.schedule()` to defer to main loop
- `vim.defer_fn()` for delayed execution
### UI Updates
- Runner notifies via `M._on_update` callback
- View subscribes and re-renders on notification
- Timer drives spinner animation (100ms interval)
## Highlight Groups
All highlights prefixed with `Glaze`. Key groups:
- `GlazeH1`, `GlazeH2` - Headers
- `GlazeBinary`, `GlazeUrl`, `GlazePlugin` - Content
- `GlazeDone`, `GlazeError`, `GlazeRunning` - Status
- `GlazeProgressDone`, `GlazeProgressTodo` - Progress bar
Colors follow doughnut aesthetic (pink `#FF6AD5` as primary accent).
## Configuration
Default config in `init.lua:53-78`. Key options:
- `concurrency`: Parallel jobs (default 4)
- `auto_install.enabled`: Install on register
- `auto_check.enabled/frequency`: Update checking
- `auto_update.enabled`: Auto-apply updates
## Common Tasks
### Adding a New Config Option
1. Add to `GlazeConfig` type definition in `init.lua`
2. Add default value in `M.config`
3. Document in `README.md` and `doc/glaze.txt`
### Adding a New Command
1. Create in `M.setup()` via `vim.api.nvim_create_user_command()`
2. Document in README and help file
### Modifying UI
1. Edit `view.lua:render()` for content changes
2. Edit `view.lua:open()` to add keybinds
3. Edit `colors.lua` for new highlight groups
### Adding Health Checks
Add checks in `health.lua:check()` using `vim.health.ok/warn/error/info`.
## Gotchas
1. **vim global**: All `vim.*` calls show LSP warnings - this is normal for Neovim plugins without proper Lua LSP config.
2. **Race conditions**: `runner.lua` rejects new tasks if already running. Check `runner.is_running()` first.
3. **Timer cleanup**: `view.lua._timer` must be stopped on close, or spinner keeps running.
4. **State file**: `checker.lua` persists to data dir. If corrupt, delete `~/.local/share/nvim/glaze/state.json`.
5. **GOBIN detection**: Checks multiple locations: `$GOBIN`, `$GOPATH/bin`, `~/go/bin`. See `init.lua:is_installed()`.
6. **goenv support**: Auto-detected in setup, changes `go_cmd` to `{ "goenv", "exec", "go" }`.
## API Reference
Main module (`require("glaze")`):
- `setup(opts)` - Initialize with config
- `register(name, url, opts)` - Register binary
- `unregister(name)` - Remove binary
- `binaries()` - Get all registered
- `is_installed(name)` - Check if binary exists
- `bin_path(name)` - Get full path
- `status(name)` - Get "installed"/"missing"/"unknown"
Runner (`require("glaze.runner")`):
- `update(names)` / `update_all()` - Update binaries
- `install(names)` / `install_missing()` - Install binaries
- `abort()` - Stop all tasks
- `is_running()` / `tasks()` / `stats()` - Query state
Checker (`require("glaze.checker")`):
- `check(opts)` - Check for updates
- `auto_check()` - Check if interval passed
- `get_update_info()` - Get cached version info

27
LICENSE
View File

@@ -1,21 +1,14 @@
MIT License BSD Zero Clause License
Copyright (c) 2026 Tai Groot Copyright (c) 2026 Tai Groot
Permission is hereby granted, free of charge, to any person obtaining a copy Permission to use, copy, modify, and/or distribute this software for any
of this software and associated documentation files (the "Software"), to deal purpose with or without fee is hereby granted.
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
copies or substantial portions of the Software. REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER PERFORMANCE OF THIS SOFTWARE.
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11
Makefile Normal file
View File

@@ -0,0 +1,11 @@
.PHONY: demo clean
PLUGIN_PATH := $(shell pwd)
demo:
@echo "Recording demo.gif..."
PLUGIN_PATH=$(PLUGIN_PATH) vhs docs/demo.tape
@echo "Done! Output: docs/demo.gif"
clean:
rm -rf /tmp/vhs-glaze-config /tmp/vhs-glaze-data /tmp/glaze-demo

267
README.md
View File

@@ -1,123 +1,164 @@
# 🍩 glaze.nvim <picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/taigrr/glaze.nvim/raw/master/docs/glaze-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/taigrr/glaze.nvim/raw/master/docs/glaze-light.svg">
<img alt="glaze.nvim" src="https://github.com/taigrr/glaze.nvim/raw/master/docs/glaze-dark.svg" width="400">
</picture>
> **Go + Lazy = Glaze** — A Mason/Lazy-style manager for Go binaries in Neovim. <p>
> Charmbracelet-inspired aesthetic. Zero duplication. One source of truth. <a href="https://github.com/taigrr/glaze.nvim/releases/latest">
> <img alt="Latest release" src="https://img.shields.io/github/v/release/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=FF6AD5&logoColor=D9E0EE&labelColor=302D41&sort=semver&include_prerelease">
> *Like a fresh doughnut glaze — smooth, sweet, and holds everything together.* </a>
<a href="https://github.com/taigrr/glaze.nvim/pulse">
<img alt="Last commit" src="https://img.shields.io/github/last-commit/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=8bd5ca&labelColor=302D41&logoColor=D9E0EE">
</a>
<a href="https://github.com/taigrr/glaze.nvim/blob/master/LICENSE">
<img alt="License" src="https://img.shields.io/github/license/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=ee999f&labelColor=302D41&logoColor=D9E0EE">
</a>
<a href="https://github.com/taigrr/glaze.nvim/stargazers">
<img alt="Stars" src="https://img.shields.io/github/stars/taigrr/glaze.nvim?style=for-the-badge&logo=starship&color=c69ff5&labelColor=302D41&logoColor=D9E0EE">
</a>
</p>
![Lua](https://img.shields.io/badge/Lua-2C2D72?style=flat&logo=lua&logoColor=white) **Go + Lazy = Glaze** — A centralized manager for Go binaries in Neovim.
![Neovim](https://img.shields.io/badge/Neovim%200.9+-57A143?style=flat&logo=neovim&logoColor=white)
![Go Required](https://img.shields.io/badge/Go-required-00ADD8?style=flat&logo=go&logoColor=white)
![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
## 🤔 Why Glaze? <img alt="glaze.nvim demo" src="https://github.com/taigrr/glaze.nvim/raw/master/docs/demo.gif" width="700">
Every Go-based Neovim plugin reinvents the wheel: each one ships its own ## The Problem
`go install` wrapper, its own update command, its own version checking.
**Glaze stops the madness.** Register your binaries once, manage them all from Every Go-based Neovim plugin reinvents the wheel:
one beautiful UI. Plugin authors get a two-line integration. Users get a single
`:Glaze` command. - **freeze.nvim** needs `freeze` → ships its own installer
- **glow.nvim** needs `glow` → ships its own installer
- **mods.nvim** needs `mods` → ships its own installer
Each plugin implements `go install`, update checking, and version management from scratch.
Users run different update commands for each plugin. Plugin authors duplicate code.
## The Solution
**Glaze** provides a single source of truth for Go binaries:
```lua
-- Plugin authors: two lines
local ok, glaze = pcall(require, "glaze")
if ok then glaze.register("freeze", "github.com/charmbracelet/freeze") end
-- Users: one command
:Glaze
```
## ✨ Features ## ✨ Features
- **Centralized binary management** — Register binaries from any plugin, update them all at once - 📦 **Centralized management** — Register binaries from any plugin, manage from one UI
- **Lazy.nvim-style UI** — Floating window with progress bars, spinners, and status indicators - 🚀 **Parallel installations** — Configurable concurrency for fast updates
- **Cursor-aware keybinds** — `u` updates the binary under your cursor, `U` updates all - 🎯 **Cursor-aware keybinds**`u` updates binary under cursor, `U` updates all
- **Parallel installations** — Configurable concurrency for fast updates - 🔄 **Auto-update checking**Daily/weekly checks with non-intrusive notifications
- **Auto-update checking** — Daily/weekly checks with non-intrusive notifications - 📍 **Smart binary detection**Finds binaries in PATH, GOBIN, and GOPATH
- **GOBIN/GOPATH awareness** — Finds binaries even if not in PATH - 🎨 **Sugary-sweet aesthetic**Pink/magenta theme reminding you of doughnuts...
- **Detail expansion** — Press `<CR>` to see URL, install path, version info - 🔔 **Callback support**Get notified when your binary is updated
- **Charmbracelet aesthetic** — Pink/magenta color scheme that matches the Charm toolchain - **Zero config for plugins** — Register and go
- **Zero config for dependents** — Just register and go
- **Callback support** — Get notified when your binary is updated
## 📦 Installation ## 📦 Installation
Using [lazy.nvim](https://github.com/folke/lazy.nvim): ### [lazy.nvim](https://github.com/folke/lazy.nvim)
```lua ```lua
{ {
"taigrr/glaze.nvim", "taigrr/glaze.nvim",
config = function() config = function()
require("glaze").setup({}) require("glaze").setup()
end, end,
} }
``` ```
### [packer.nvim](https://github.com/wbthomason/packer.nvim)
```lua
use {
"taigrr/glaze.nvim",
config = function()
require("glaze").setup()
end,
}
```
## ⚡ Requirements
- Neovim >= **0.9.0**
- Go >= **1.18** (for `go install` support)
- Optional: [goenv](https://github.com/syndbg/goenv) (auto-detected)
## 🚀 Quick Start ## 🚀 Quick Start
```lua ```lua
local glaze = require("glaze") local glaze = require("glaze")
-- Setup (usually in your plugin config) -- Setup
glaze.setup({}) glaze.setup()
-- Register binaries -- Register binaries
glaze.register("freeze", "github.com/charmbracelet/freeze") glaze.register("freeze", "github.com/charmbracelet/freeze")
glaze.register("glow", "github.com/charmbracelet/glow") glaze.register("glow", "github.com/charmbracelet/glow")
glaze.register("mods", "github.com/charmbracelet/mods") glaze.register("gum", "github.com/charmbracelet/gum")
-- Open the UI
vim.cmd("Glaze")
``` ```
## 📖 Usage ## 📖 Commands
### Commands | Command | Description |
| ---------------------- | ------------------------------------ |
| `:Glaze` | Open the Glaze UI |
| `:GlazeUpdate [name]` | Update all or specific binary |
| `:GlazeInstall [name]` | Install missing or specific binary |
| `:GlazeCheck` | Manually check for available updates |
| Command | Description | ## ⌨️ Keybinds
|---------|-------------|
| `:Glaze` | Open the Glaze UI |
| `:GlazeUpdate [name]` | Update all or specific binary |
| `:GlazeInstall [name]` | Install missing or specific binary |
| `:GlazeCheck` | Manually check for available updates |
### Keybinds (in Glaze UI) | Key | Action |
| ------------- | ---------------------------- |
| Key | Action | | `U` | Update all binaries |
|-----|--------| | `u` | Update binary under cursor |
| `U` | Update ALL binaries | | `I` | Install all missing binaries |
| `u` | Update binary under cursor | | `i` | Install binary under cursor |
| `I` | Install all missing binaries | | `x` | Abort running tasks |
| `i` | Install binary under cursor | | `<CR>` | Toggle details |
| `x` | Abort running tasks | | `q` / `<Esc>` | Close window |
| `<CR>` | Toggle details (URL, path, version) |
| `q` / `<Esc>` | Close window |
## 🔌 For Plugin Authors ## 🔌 For Plugin Authors
Register your plugin's binaries as a dependency — two lines is all it takes: Make your plugin a Glaze consumer with two lines:
```lua ```lua
-- In your plugin's setup or init: -- In your plugin's setup:
local ok, glaze = pcall(require, "glaze") local ok, glaze = pcall(require, "glaze")
if ok then if ok then
glaze.register("mytool", "github.com/me/mytool", { glaze.register("mytool", "github.com/me/mytool", {
plugin = "myplugin.nvim", plugin = "myplugin.nvim", -- Shows in UI
callback = function(success) callback = function(success)
if success then if success then vim.notify("mytool updated!") end
vim.notify("mytool updated!")
end
end, end,
}) })
end end
``` ```
### Providing Update Commands ### Example Consumers
You can still expose plugin-specific commands that delegate to Glaze: These plugins use Glaze to manage their Go binaries:
```lua - [**neocrush.nvim**](https://github.com/taigrr/neocrush.nvim) — AI-powered coding assistant
vim.api.nvim_create_user_command("MyPluginUpdate", function() - [**freeze.nvim**](https://github.com/taigrr/freeze.nvim) — Screenshot code with freeze
require("glaze.runner").update({ "mytool" }) - [**blast.nvim**](https://github.com/taigrr/blast.nvim) — Code activity tracking
end, {})
```
## ⚙️ Configuration ## ⚙️ Configuration
```lua ```lua
require("glaze").setup({ require("glaze").setup({
-- UI settings
ui = { ui = {
border = "rounded", -- "none", "single", "double", "rounded", "solid", "shadow" border = "rounded", -- "none", "single", "double", "rounded", "solid", "shadow"
size = { width = 0.7, height = 0.8 }, -- Percentage of screen size = { width = 0.7, height = 0.8 },
icons = { icons = {
pending = "", pending = "",
running = "", running = "",
@@ -125,33 +166,34 @@ require("glaze").setup({
error = "", error = "",
binary = "󰆍", binary = "󰆍",
}, },
use_system_theming = false, -- Use nvim theme instead of doughnut colors
}, },
concurrency = 4, -- Max parallel installations
go_cmd = { "go" }, -- Auto-detects goenv if available -- Parallel installations
concurrency = 4,
-- Go command (auto-detects goenv)
go_cmd = { "go" },
-- Auto-install missing binaries on register
auto_install = {
enabled = true,
silent = false,
},
-- Auto-check for updates
auto_check = { auto_check = {
enabled = true, -- Auto-check for updates enabled = true,
frequency = "daily", -- "daily", "weekly", or hours as number frequency = "daily", -- "daily", "weekly", or hours as number
},
-- Auto-update when newer versions found
auto_update = {
enabled = false, -- Requires auto_check
}, },
}) })
``` ```
## 🎨 Highlight Groups
Glaze defines these highlight groups (all prefixed with `Glaze`):
| Group | Description |
|-------|-------------|
| `GlazeH1` | Main title (pink) |
| `GlazeH2` | Section headers |
| `GlazeBinary` | Binary names |
| `GlazeUrl` | Module URLs |
| `GlazePlugin` | Plugin names |
| `GlazeDone` | Success status |
| `GlazeError` | Error status |
| `GlazeRunning` | In-progress status |
| `GlazeProgressDone` | Progress bar (filled) |
| `GlazeProgressTodo` | Progress bar (empty) |
## 📋 API ## 📋 API
```lua ```lua
@@ -163,36 +205,53 @@ glaze.unregister(name) -- Remove a binary
glaze.binaries() -- Get all registered binaries glaze.binaries() -- Get all registered binaries
-- Status -- Status
glaze.is_installed(name) -- Check if binary exists (PATH + GOBIN + GOPATH) glaze.is_installed(name) -- Check if binary exists
glaze.bin_path(name) -- Get full path to binary glaze.bin_path(name) -- Get full path to binary
glaze.status(name) -- "installed", "missing", or "unknown" glaze.status(name) -- "installed", "missing", or "unknown"
-- Runner (for programmatic control) -- Runner
local runner = require("glaze.runner") local runner = require("glaze.runner")
runner.update({ "freeze", "glow" }) -- Update specific binaries runner.update({ "freeze" }) -- Update specific binaries
runner.update_all() -- Update all runner.update_all() -- Update all
runner.install({ "freeze" }) -- Install specific runner.install_missing() -- Install all missing
runner.install_missing() -- Install all missing runner.abort() -- Stop all tasks
runner.abort() -- Stop all tasks
runner.is_running() -- Check if tasks are running
runner.tasks() -- Get current task list
runner.stats() -- Get { total, done, error, running, pending }
-- Update checker -- Checker
local checker = require("glaze.checker") local checker = require("glaze.checker")
checker.check() -- Check for updates (with notifications) checker.check() -- Check for updates
checker.auto_check() -- Check only if enough time has passed checker.get_update_info() -- Get cached update info
checker.get_update_info() -- Get cached update info
``` ```
## 🎨 Highlight Groups
| Group | Description |
| ------------------- | --------------------- |
| `GlazeH1` | Main title |
| `GlazeH2` | Section headers |
| `GlazeBinary` | Binary names |
| `GlazeUrl` | Module URLs |
| `GlazePlugin` | Plugin names |
| `GlazeDone` | Success status |
| `GlazeError` | Error status |
| `GlazeRunning` | In-progress status |
| `GlazeProgressDone` | Progress bar (filled) |
| `GlazeProgressTodo` | Progress bar (empty) |
## 🩺 Health Check
```vim
:checkhealth glaze
```
Verifies Go installation, GOBIN configuration, and registered binary status.
## 🤝 Related Projects ## 🤝 Related Projects
- [freeze.nvim](https://github.com/taigrr/freeze.nvim) — Screenshot code with freeze Glaze is inspired by:
- [neocrush.nvim](https://github.com/taigrr/neocrush.nvim) — AI-powered coding assistant
- [blast.nvim](https://github.com/taigrr/blast.nvim) — Code activity tracking - [lazy.nvim](https://github.com/folke/lazy.nvim) — UI patterns and aesthetics
- [lazy.nvim](https://github.com/folke/lazy.nvim) — UI inspiration - [mason.nvim](https://github.com/williamboman/mason.nvim) — Centralized tool management concept
- [mason.nvim](https://github.com/williamboman/mason.nvim) — Concept inspiration
## 📄 License ## 📄 License
MIT © [Tai Groot](https://github.com/taigrr) [0BSD](LICENSE) © [Tai Groot](https://github.com/taigrr)

View File

@@ -1,7 +1,7 @@
*glaze.txt* 🍩 Centralized Go binary management for Neovim plugins *glaze.txt* 🍩 Centralized Go binary management for Neovim plugins
Author: Tai Groot <tai@taigrr.com> Author: Tai Groot <tai@taigrr.com>
License: MIT License: 0BSD
Homepage: https://github.com/taigrr/glaze.nvim Homepage: https://github.com/taigrr/glaze.nvim
============================================================================== ==============================================================================
@@ -124,6 +124,7 @@ Runner API (require("glaze.runner")):
runner.is_running() Check if tasks are running runner.is_running() Check if tasks are running
runner.tasks() Get current task list runner.tasks() Get current task list
runner.stats() Get task statistics runner.stats() Get task statistics
runner.on_update({fn}) Register callback for task state changes
*glaze-checker-api* *glaze-checker-api*
Checker API (require("glaze.checker")): Checker API (require("glaze.checker")):
@@ -148,33 +149,58 @@ Default configuration:
error = "✗", error = "✗",
binary = "󰆍", binary = "󰆍",
}, },
use_system_theming = false, -- Use nvim theme instead of doughnut colors
}, },
concurrency = 4, concurrency = 4,
go_cmd = { "go" }, -- Auto-detects goenv go_cmd = { "go" }, -- Auto-detects goenv
auto_install = {
enabled = true, -- Auto-install missing binaries on register
silent = false, -- Suppress install notifications
},
auto_check = { auto_check = {
enabled = true, -- Auto-check for updates on setup enabled = true, -- Auto-check for updates (disabling also disables auto_update)
frequency = "daily", -- "daily", "weekly", or hours (number) frequency = "daily", -- "daily", "weekly", or hours (number)
}, },
auto_update = {
enabled = false, -- Auto-update when newer versions found (requires auto_check)
},
}) })
< <
============================================================================== ==============================================================================
7. HIGHLIGHTS *glaze-highlights* 7. HIGHLIGHTS *glaze-highlights*
All highlight groups are prefixed with "Glaze": All highlight groups are prefixed with "Glaze". When `use_system_theming` is
false (default), a doughnut-themed pink/magenta colors are used.
Group Description ~
GlazeH1 Main title GlazeH1 Main title
GlazeH2 Section headers GlazeH2 Section headers
GlazeTitle Window title
GlazeBinary Binary names GlazeBinary Binary names
GlazeUrl Module URLs GlazeUrl Module URLs (italic)
GlazePlugin Plugin names GlazePlugin Plugin names
GlazeDone Success status GlazeDone Success status
GlazeError Error status GlazeError Error status
GlazeRunning In-progress status GlazeRunning In-progress status
GlazePending Pending status
GlazeProgressDone Progress bar (filled) GlazeProgressDone Progress bar (filled)
GlazeProgressTodo Progress bar (empty) GlazeProgressTodo Progress bar (empty)
GlazeVersion Version info GlazeVersion Version info
GlazeTime Timing info GlazeTime Timing info (italic)
GlazeNormal Normal text (links to NormalFloat)
GlazeComment Comments (links to Comment)
GlazeDimmed Dimmed text (links to Conceal)
GlazeBorder Window border
GlazeButton Inactive button (links to CursorLine)
GlazeButtonActive Active/selected button
GlazeKey Keybind hints
GlazeIcon Default icon color
GlazeIconDone Done icon
GlazeIconError Error icon
GlazeIconRunning Running/spinner icon
GlazeBold Bold text
GlazeItalic Italic text
============================================================================== ==============================================================================
8. AUTO-UPDATE CHECKING *glaze-auto-check* 8. AUTO-UPDATE CHECKING *glaze-auto-check*

3
docs/demo.gif Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1e33f21d35c9b869528d908207e8d66fd5770d1ff5570ab487ef3f13081348a7
size 89918

86
docs/demo.tape Normal file
View File

@@ -0,0 +1,86 @@
# glaze.nvim Demo
# Records the main UI and update flow
#
# Requirements:
# - vhs (brew install vhs)
# - nvim >= 0.9
# - go installed
#
# Run: vhs docs/demo.tape
Output docs/demo.gif
Require nvim
Require go
Set Shell bash
Set FontSize 14
Set Width 1000
Set Height 600
Set Theme "Catppuccin Mocha"
Set WindowBar Colorful
Set Padding 15
Set TypingSpeed 30ms
Set CursorBlink false
# Hidden setup - isolate config and set up environment
Hide
Type "export XDG_CONFIG_HOME=/tmp/vhs-glaze-config"
Enter
Type "export XDG_DATA_HOME=/tmp/vhs-glaze-data"
Enter
Type "mkdir -p /tmp/vhs-glaze-config /tmp/vhs-glaze-data /tmp/glaze-demo && cd /tmp/glaze-demo"
Enter
Sleep 200ms
# Create empty file to open
Type "touch demo.lua"
Enter
Sleep 200ms
Type "clear"
Enter
Sleep 200ms
# Start nvim with minimal config (PLUGIN_PATH set by Makefile or manually)
Type `nvim --clean -u $PLUGIN_PATH/scripts/vhs_init.lua --cmd "set rtp+=$PLUGIN_PATH" demo.lua`
Enter
Sleep 2s
Show
# Open Glaze UI
Type ":Glaze"
Enter
Sleep 2s
# Let the UI fully render before navigating
Sleep 500ms
# Navigate down through binaries
Type "j"
Sleep 300ms
Type "j"
Sleep 300ms
# Expand details on current binary
Enter
Sleep 1.2s
# Collapse details
Enter
Sleep 600ms
# Update ALL binaries with U
Type "U"
Sleep 10s
# Show the completed state
Sleep 2s
# Close and quit
Hide
Type "q"
Sleep 300ms
Type ":qa!"
Enter
Sleep 300ms

36
docs/glaze-dark.svg Normal file
View File

@@ -0,0 +1,36 @@
<svg width="400" height="100" viewBox="0 0 400 100" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="glazeGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#FF6AD5"/>
<stop offset="100%" style="stop-color:#C774E8"/>
</linearGradient>
</defs>
<!-- Donut icon -->
<g transform="translate(20, 15)">
<!-- Outer ring -->
<circle cx="35" cy="35" r="32" fill="url(#glazeGradient)"/>
<!-- Inner hole -->
<circle cx="35" cy="35" r="14" fill="#1E1E2E"/>
<!-- Glaze drips -->
<ellipse cx="20" cy="58" rx="6" ry="10" fill="url(#glazeGradient)"/>
<ellipse cx="35" cy="62" rx="5" ry="8" fill="url(#glazeGradient)"/>
<ellipse cx="50" cy="58" rx="6" ry="10" fill="url(#glazeGradient)"/>
<!-- Sprinkles -->
<rect x="22" y="20" width="8" height="3" rx="1" fill="#A6E3A1" transform="rotate(-30 26 21.5)"/>
<rect x="45" y="18" width="8" height="3" rx="1" fill="#89B4FA" transform="rotate(20 49 19.5)"/>
<rect x="38" y="38" width="8" height="3" rx="1" fill="#F9E2AF" transform="rotate(-10 42 39.5)"/>
<rect x="18" y="40" width="8" height="3" rx="1" fill="#F38BA8" transform="rotate(40 22 41.5)"/>
<rect x="50" y="35" width="8" height="3" rx="1" fill="#94E2D5" transform="rotate(-45 54 36.5)"/>
</g>
<!-- Text -->
<text x="105" y="62" font-family="JetBrains Mono, SF Mono, Consolas, monospace" font-size="48" font-weight="bold" fill="#CDD6F4">
glaze<tspan fill="#6C7086">.nvim</tspan>
</text>
<!-- Tagline -->
<text x="105" y="85" font-family="JetBrains Mono, SF Mono, Consolas, monospace" font-size="14" fill="#6C7086">
Go binary manager for Neovim
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

36
docs/glaze-light.svg Normal file
View File

@@ -0,0 +1,36 @@
<svg width="400" height="100" viewBox="0 0 400 100" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="glazeGradientLight" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#D946EF"/>
<stop offset="100%" style="stop-color:#A855F7"/>
</linearGradient>
</defs>
<!-- Donut icon -->
<g transform="translate(20, 15)">
<!-- Outer ring -->
<circle cx="35" cy="35" r="32" fill="url(#glazeGradientLight)"/>
<!-- Inner hole -->
<circle cx="35" cy="35" r="14" fill="#FAFAFA"/>
<!-- Glaze drips -->
<ellipse cx="20" cy="58" rx="6" ry="10" fill="url(#glazeGradientLight)"/>
<ellipse cx="35" cy="62" rx="5" ry="8" fill="url(#glazeGradientLight)"/>
<ellipse cx="50" cy="58" rx="6" ry="10" fill="url(#glazeGradientLight)"/>
<!-- Sprinkles -->
<rect x="22" y="20" width="8" height="3" rx="1" fill="#22C55E" transform="rotate(-30 26 21.5)"/>
<rect x="45" y="18" width="8" height="3" rx="1" fill="#3B82F6" transform="rotate(20 49 19.5)"/>
<rect x="38" y="38" width="8" height="3" rx="1" fill="#EAB308" transform="rotate(-10 42 39.5)"/>
<rect x="18" y="40" width="8" height="3" rx="1" fill="#EF4444" transform="rotate(40 22 41.5)"/>
<rect x="50" y="35" width="8" height="3" rx="1" fill="#14B8A6" transform="rotate(-45 54 36.5)"/>
</g>
<!-- Text -->
<text x="105" y="62" font-family="JetBrains Mono, SF Mono, Consolas, monospace" font-size="48" font-weight="bold" fill="#1E293B">
glaze<tspan fill="#64748B">.nvim</tspan>
</text>
<!-- Tagline -->
<text x="105" y="85" font-family="JetBrains Mono, SF Mono, Consolas, monospace" font-size="14" fill="#64748B">
Go binary manager for Neovim
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -127,6 +127,60 @@ function M.get_update_info()
return M._update_info return M._update_info
end end
---Refresh version info for a single binary (called after install/update).
---@param name string Binary name
---@param callback? fun() Optional callback when done
function M.refresh_version(name, callback)
local glaze = require("glaze")
local binary = glaze._binaries[name]
if not binary then
if callback then
callback()
end
return
end
get_installed_version(name, function(installed)
local info = M._update_info[name] or {
name = name,
installed_version = nil,
latest_version = nil,
has_update = false,
}
info.installed_version = installed
-- If we have latest version cached, check if still needs update
if info.latest_version and installed then
info.has_update = installed ~= info.latest_version
else
info.has_update = false
end
M._update_info[name] = info
-- Persist to state
local state = read_state()
state.update_info = state.update_info or {}
state.update_info[name] = {
installed_version = info.installed_version,
latest_version = info.latest_version,
has_update = info.has_update,
}
write_state(state)
-- Refresh UI if open
vim.schedule(function()
local ok, view = pcall(require, "glaze.view")
if ok and view._float and view._float:valid() then
view.render()
end
if callback then
callback()
end
end)
end)
end
---Check for updates on all registered binaries. ---Check for updates on all registered binaries.
---@param opts? { silent?: boolean } ---@param opts? { silent?: boolean }
function M.check(opts) function M.check(opts)
@@ -149,12 +203,9 @@ function M.check(opts)
end end
M._checking = true M._checking = true
local remaining = 0
local updates_found = 0 local updates_found = 0
local remaining = vim.tbl_count(binaries)
for name, binary in pairs(binaries) do for name, binary in pairs(binaries) do
remaining = remaining + 2 -- installed version + latest version
local info = { local info = {
name = name, name = name,
installed_version = nil, installed_version = nil,
@@ -163,16 +214,6 @@ function M.check(opts)
} }
M._update_info[name] = info M._update_info[name] = info
get_installed_version(name, function()
-- callback receives version from the jobstart; re-check via closure
end)
end
-- Simplified: check each binary sequentially-ish
remaining = vim.tbl_count(binaries)
for name, binary in pairs(binaries) do
local info = M._update_info[name]
get_installed_version(name, function(installed) get_installed_version(name, function(installed)
info.installed_version = installed info.installed_version = installed
@@ -201,10 +242,25 @@ function M.check(opts)
end end
write_state(state) write_state(state)
if not opts.silent then -- Auto-update if enabled (requires auto_check to be enabled)
if updates_found > 0 and glaze.config.auto_update.enabled and glaze.config.auto_check.enabled then
vim.schedule(function()
local to_update = {}
for n, i in pairs(M._update_info) do
if i.has_update then
table.insert(to_update, n)
end
end
vim.notify("Glaze: auto-updating " .. #to_update .. " binary(ies)…", vim.log.levels.INFO)
require("glaze.runner").update(to_update)
end)
elseif not opts.silent then
if updates_found > 0 then if updates_found > 0 then
vim.schedule(function() vim.schedule(function()
vim.notify("Glaze: " .. updates_found .. " update(s) available", vim.log.levels.INFO) vim.notify(
"Glaze: " .. updates_found .. " update(s) available — run :GlazeUpdate",
vim.log.levels.INFO
)
end) end)
else else
vim.schedule(function() vim.schedule(function()
@@ -213,7 +269,7 @@ function M.check(opts)
end end
elseif updates_found > 0 then elseif updates_found > 0 then
vim.schedule(function() vim.schedule(function()
vim.notify("Glaze: " .. updates_found .. " update(s) available", vim.log.levels.INFO) vim.notify("Glaze: " .. updates_found .. " update(s) available — run :GlazeUpdate", vim.log.levels.INFO)
end) end)
end end

View File

@@ -1,50 +1,110 @@
---@brief [[ ---@brief [[
--- glaze.nvim color definitions --- glaze.nvim color definitions
--- Charmbracelet-inspired palette with Lazy.nvim structure --- Doughnut-inspired palette with Lazy.nvim structure
--- When use_system_theming is enabled, uses standard nvim highlight groups instead
---@brief ]] ---@brief ]]
local M = {} local M = {}
-- Charmbracelet-inspired colors (pink/magenta theme) -- Doughnut-inspired palette
local palette = {
frosting = "#FF6AD5", -- Primary pink accent
lavender = "#C4A7E7", -- Soft purple
mint = "#9FFFCB", -- Success green
honey = "#FFD866", -- Warning yellow
coral = "#FF6B6B", -- Error red
sky = "#7DCFFF", -- URL blue
grape = "#BB9AF7", -- Plugin purple
ash = "#6E6A86", -- Muted gray
shadow = "#3B3A52", -- Dark purple-gray
ink = "#1A1B26", -- Background dark
}
-- Doughnut-inspired colors (pink/magenta theme)
M.colors = { M.colors = {
-- Headers and accents -- Headers and accents
H1 = { fg = "#FF6AD5", bold = true }, -- Charm pink H1 = { fg = palette.frosting, bold = true },
H2 = { fg = "#C4A7E7", bold = true }, -- Soft purple H2 = { fg = palette.lavender, bold = true },
Title = { fg = "#FF6AD5", bold = true }, Title = { fg = palette.frosting, bold = true },
-- Status indicators -- Status indicators
Done = { fg = "#9FFFCB" }, -- Mint green (success) Done = { fg = palette.mint },
Running = { fg = "#FFD866" }, -- Warm yellow (in progress) Running = { fg = palette.honey },
Pending = { fg = "#6E6A86" }, -- Muted gray Pending = { fg = palette.ash },
Error = { fg = "#FF6B6B", bold = true }, -- Soft red Error = { fg = palette.coral, bold = true },
-- Content -- Content
Normal = "NormalFloat", Normal = "NormalFloat",
Binary = { fg = "#FF6AD5" }, -- Charm pink for binary names Binary = { fg = palette.frosting },
Url = { fg = "#7DCFFF", italic = true }, -- Bright blue for URLs Url = { fg = palette.sky, italic = true },
Plugin = { fg = "#BB9AF7" }, -- Purple for plugin names Plugin = { fg = palette.grape },
Comment = "Comment", Comment = "Comment",
Dimmed = "Conceal", Dimmed = "Conceal",
-- Progress bar -- Progress bar
ProgressDone = { fg = "#FF6AD5" }, -- Charm pink ProgressDone = { fg = palette.frosting },
ProgressTodo = { fg = "#3B3A52" }, -- Dark purple-gray ProgressTodo = { fg = palette.shadow },
-- UI elements -- UI elements
Border = { fg = "#FF6AD5" }, Border = { fg = palette.frosting },
Button = "CursorLine", Button = "CursorLine",
ButtonActive = { bg = "#FF6AD5", fg = "#1A1B26", bold = true }, ButtonActive = { bg = palette.frosting, fg = palette.ink, bold = true },
Key = { fg = "#FFD866", bold = true }, -- Keybind highlights Key = { fg = palette.honey, bold = true },
-- Version info -- Version info
Version = { fg = "#9FFFCB" }, Version = { fg = palette.mint },
Time = { fg = "#6E6A86", italic = true }, Time = { fg = palette.ash, italic = true },
-- Icons -- Icons
Icon = { fg = "#FF6AD5" }, Icon = { fg = palette.frosting },
IconDone = { fg = "#9FFFCB" }, IconDone = { fg = palette.mint },
IconError = { fg = "#FF6B6B" }, IconError = { fg = palette.coral },
IconRunning = { fg = "#FFD866" }, IconRunning = { fg = palette.honey },
Bold = { bold = true },
Italic = { italic = true },
}
-- System theme colors (links to standard nvim groups)
M.system_colors = {
-- Headers and accents
H1 = "Title",
H2 = "Title",
Title = "Title",
-- Status indicators
Done = "DiagnosticOk",
Running = "DiagnosticWarn",
Pending = "Comment",
Error = "DiagnosticError",
-- Content
Normal = "NormalFloat",
Binary = "Identifier",
Url = "Underlined",
Plugin = "Special",
Comment = "Comment",
Dimmed = "Conceal",
-- Progress bar
ProgressDone = "DiagnosticOk",
ProgressTodo = "Comment",
-- UI elements
Border = "FloatBorder",
Button = "CursorLine",
ButtonActive = "PmenuSel",
Key = "SpecialKey",
-- Version info
Version = "DiagnosticOk",
Time = "Comment",
-- Icons
Icon = "Special",
IconDone = "DiagnosticOk",
IconError = "DiagnosticError",
IconRunning = "DiagnosticWarn",
Bold = { bold = true }, Bold = { bold = true },
Italic = { italic = true }, Italic = { italic = true },
@@ -53,7 +113,10 @@ M.colors = {
M.did_setup = false M.did_setup = false
function M.set_hl() function M.set_hl()
for name, def in pairs(M.colors) do local glaze = require("glaze")
local colors = glaze.config.ui.use_system_theming and M.system_colors or M.colors
for name, def in pairs(colors) do
local hl = type(def) == "table" and def or { link = def } local hl = type(def) == "table" and def or { link = def }
hl.default = true hl.default = true
vim.api.nvim_set_hl(0, "Glaze" .. name, hl) vim.api.nvim_set_hl(0, "Glaze" .. name, hl)

90
lua/glaze/health.lua Normal file
View File

@@ -0,0 +1,90 @@
---@brief [[
--- glaze.nvim health check
--- Run with :checkhealth glaze
---@brief ]]
local M = {}
function M.check()
vim.health.start("glaze.nvim")
-- Check Neovim version
if vim.fn.has("nvim-0.9") == 1 then
vim.health.ok("Neovim >= 0.9")
else
vim.health.error("Neovim >= 0.9 required", { "Upgrade Neovim to 0.9 or later" })
end
-- Check Go (required)
if vim.fn.executable("go") == 1 then
local go_version = vim.fn.system("go version"):gsub("%s+$", "")
vim.health.ok(go_version)
elseif vim.fn.executable("goenv") == 1 then
local go_version = vim.fn.system("goenv exec go version 2>/dev/null"):gsub("%s+$", "")
if go_version ~= "" and not go_version:match("not found") then
vim.health.ok(go_version .. " (via goenv)")
else
vim.health.error("goenv found but no Go version installed", {
"Run: goenv install <version>",
"Or install Go directly: https://go.dev/dl/",
})
end
else
vim.health.error("Go not found", {
"Go is required for installing and updating binaries",
"Install from: https://go.dev/dl/",
})
end
-- Check GOBIN / GOPATH
local gobin = vim.env.GOBIN
if not gobin or gobin == "" then
local gopath = vim.env.GOPATH
if gopath and gopath ~= "" then
gobin = gopath .. "/bin"
else
gobin = vim.env.HOME .. "/go/bin"
end
end
if vim.fn.isdirectory(gobin) == 1 then
-- Check if GOBIN is in PATH
local path = vim.env.PATH or ""
if path:find(gobin, 1, true) then
vim.health.ok("GOBIN in PATH: " .. gobin)
else
vim.health.warn("GOBIN exists but is not in PATH: " .. gobin, {
'Add to PATH: export PATH="' .. gobin .. ':$PATH"',
"Binaries installed by Glaze may not be found without this",
})
end
else
vim.health.info("GOBIN directory does not exist yet: " .. gobin)
end
-- Check registered binaries
local glaze = require("glaze")
local binaries = glaze.binaries()
local count = vim.tbl_count(binaries)
if count == 0 then
vim.health.info("No binaries registered (plugins will register on setup)")
else
vim.health.ok(count .. " binary(ies) registered")
for name, binary in pairs(binaries) do
local pi = (binary.plugins and #binary.plugins > 0)
and (" (" .. table.concat(binary.plugins, ", ") .. ")")
or ""
if glaze.is_installed(name) then
vim.health.ok(" " .. name .. " — installed" .. pi)
else
vim.health.warn(" " .. name .. " — missing" .. pi, {
"Run :GlazeInstall " .. name,
"Or: go install " .. binary.url .. "@latest",
})
end
end
end
end
return M

View File

@@ -15,30 +15,40 @@ local M = {}
---@class GlazeBinary ---@class GlazeBinary
---@field name string Binary name (executable name) ---@field name string Binary name (executable name)
---@field url string Go module URL (without @version) ---@field url string Go module URL (without @version)
---@field plugin? string Plugin that registered this binary ---@field plugins string[] Plugins that registered this binary
---@field callback? fun(success: boolean) Optional callback after install/update ---@field callbacks table<string, fun(success: boolean)> Callbacks keyed by plugin name
---@class GlazeAutoCheckConfig ---@class GlazeAutoCheckConfig
---@field enabled boolean Whether to auto-check for updates ---@field enabled? boolean Whether to auto-check for updates
---@field frequency string|number Frequency: "daily", "weekly", or hours as number ---@field frequency? string|number Frequency: "daily", "weekly", or hours as number
---@class GlazeAutoInstallConfig
---@field enabled? boolean Whether to auto-install missing binaries on register
---@field silent? boolean Suppress notifications during auto-install
---@class GlazeAutoUpdateConfig
---@field enabled? boolean Whether to auto-update binaries when newer versions are found (requires auto_check)
---@class GlazeConfig ---@class GlazeConfig
---@field ui GlazeUIConfig ---@field ui? GlazeUIConfig
---@field concurrency number Max parallel installations ---@field concurrency? number Max parallel installations
---@field go_cmd string[] Go command (supports goenv) ---@field go_cmd? string[] Go command (supports goenv)
---@field auto_check GlazeAutoCheckConfig ---@field auto_install? GlazeAutoInstallConfig
---@field auto_check? GlazeAutoCheckConfig
---@field auto_update? GlazeAutoUpdateConfig
---@class GlazeUIConfig ---@class GlazeUIConfig
---@field border string Border style ---@field border? string Border style
---@field size { width: number, height: number } ---@field size? { width: number, height: number }
---@field icons GlazeIcons ---@field icons? GlazeIcons
---@field use_system_theming? boolean Use nvim highlight groups instead of doughnut colors
---@class GlazeIcons ---@class GlazeIcons
---@field pending string ---@field pending? string
---@field running string ---@field running? string
---@field done string ---@field done? string
---@field error string ---@field error? string
---@field binary string ---@field binary? string
---@type GlazeConfig ---@type GlazeConfig
M.config = { M.config = {
@@ -52,13 +62,21 @@ M.config = {
error = "", error = "",
binary = "󰆍", binary = "󰆍",
}, },
use_system_theming = false,
}, },
concurrency = 4, concurrency = 4,
go_cmd = { "go" }, go_cmd = { "go" },
auto_install = {
enabled = true,
silent = false,
},
auto_check = { auto_check = {
enabled = true, enabled = true,
frequency = "daily", frequency = "daily",
}, },
auto_update = {
enabled = false,
},
} }
---@type table<string, GlazeBinary> ---@type table<string, GlazeBinary>
@@ -123,17 +141,60 @@ function M.setup(opts)
end end
---Register a binary for management. ---Register a binary for management.
---If auto_install is enabled and the binary is missing, it will be installed automatically.
---@param name string Binary/executable name ---@param name string Binary/executable name
---@param url string Go module URL (e.g., "github.com/charmbracelet/freeze") ---@param url string Go module URL (e.g., "github.com/charmbracelet/freeze")
---@param opts? { plugin?: string, callback?: fun(success: boolean) } ---@param opts? { plugin?: string, callback?: fun(success: boolean) }
function M.register(name, url, opts) function M.register(name, url, opts)
opts = opts or {} opts = opts or {}
local plugin = opts.plugin or "unknown"
-- Check if this URL is already registered under a different name
for existing_name, binary in pairs(M._binaries) do
if binary.url == url and existing_name ~= name then
-- Same URL, different name - merge into existing entry
if not vim.tbl_contains(binary.plugins, plugin) then
table.insert(binary.plugins, plugin)
end
if opts.callback then
binary.callbacks[plugin] = opts.callback
end
return
end
end
-- Check if this name is already registered
local existing = M._binaries[name]
if existing then
-- Merge plugin into existing entry
if not vim.tbl_contains(existing.plugins, plugin) then
table.insert(existing.plugins, plugin)
end
if opts.callback then
existing.callbacks[plugin] = opts.callback
end
return
end
-- New binary registration
M._binaries[name] = { M._binaries[name] = {
name = name, name = name,
url = url, url = url,
plugin = opts.plugin, plugins = opts.plugin and { opts.plugin } or {},
callback = opts.callback, callbacks = opts.callback and { [plugin] = opts.callback } or {},
} }
-- Auto-install if enabled and binary is missing
if M.config.auto_install.enabled and not M.is_installed(name) then
vim.defer_fn(function()
if not M.is_installed(name) then
if not M.config.auto_install.silent then
vim.notify("[glaze] Auto-installing " .. name .. "", vim.log.levels.INFO)
end
require("glaze.runner").install({ name })
end
end, 100)
end
end end
---Unregister a binary. ---Unregister a binary.

View File

@@ -93,10 +93,17 @@ local function run_task(task)
task.status = code == 0 and "done" or "error" task.status = code == 0 and "done" or "error"
task.job_id = nil task.job_id = nil
-- Call binary callback if set -- Refresh version info on success
if task.binary.callback then if code == 0 then
require("glaze.checker").refresh_version(task.binary.name)
end
-- Call all registered callbacks
if task.binary.callbacks then
vim.schedule(function() vim.schedule(function()
task.binary.callback(code == 0) for _, cb in pairs(task.binary.callbacks) do
cb(code == 0)
end
end) end)
end end

View File

@@ -1,6 +1,6 @@
---@brief [[ ---@brief [[
--- glaze.nvim view/UI --- glaze.nvim view/UI
--- Lazy.nvim-style floating window with Charmbracelet aesthetic --- Lazy.nvim-style floating window with doughnut aesthetic
---@brief ]] ---@brief ]]
local M = {} local M = {}
@@ -32,13 +32,16 @@ function M._get_cursor_binary()
end end
---Open the Glaze UI. ---Open the Glaze UI.
---If already open, just re-render without recreating the window.
function M.open() function M.open()
local Float = require("glaze.float").Float -- If window is already open and valid, just render and return
if M._float and M._float:valid() then if M._float and M._float:valid() then
M._float:close() M.render()
return
end end
local Float = require("glaze.float").Float
M._float = Float.new({ M._float = Float.new({
title = " 🍩 Glaze ", title = " 🍩 Glaze ",
}) })
@@ -161,16 +164,16 @@ function M.render()
text:append(" ", nil, { indent = 2 }) text:append(" ", nil, { indent = 2 })
text:append(" U ", "GlazeButtonActive"):append(" Update All ", "GlazeButton") text:append(" U ", "GlazeButtonActive"):append(" Update All ", "GlazeButton")
text:append(" ") text:append(" ")
text:append(" u ", "GlazeButtonActive"):append(" Update ", "GlazeButton") text:append(" u ", "GlazeButtonActive"):append(" Update Selected ", "GlazeButton")
text:append(" ") text:append(" ")
text:append(" I ", "GlazeButtonActive"):append(" Install All ", "GlazeButton") text:append(" I ", "GlazeButtonActive"):append(" Install All ", "GlazeButton")
text:append(" ") text:append(" ")
text:append(" i ", "GlazeButtonActive"):append(" Install ", "GlazeButton") text:append(" i ", "GlazeButtonActive"):append(" Install Selected ", "GlazeButton")
text:nl() text:nl()
text:append(" ", nil, { indent = 2 }) text:append(" ", nil, { indent = 2 })
text:append(" x ", "GlazeButtonActive"):append(" Abort ", "GlazeButton") text:append(" x ", "GlazeButtonActive"):append(" Abort ", "GlazeButton")
text:append(" ") text:append(" ")
text:append(" ", "GlazeButtonActive"):append(" Details ", "GlazeButton") text:append(" <enter> ", "GlazeButtonActive"):append(" Details ", "GlazeButton")
text:append(" ") text:append(" ")
text:append(" q ", "GlazeButtonActive"):append(" Close ", "GlazeButton") text:append(" q ", "GlazeButtonActive"):append(" Close ", "GlazeButton")
text:nl():nl() text:nl():nl()
@@ -190,17 +193,16 @@ function M.render()
local binaries = glaze.binaries() local binaries = glaze.binaries()
local binary_count = vim.tbl_count(binaries) local binary_count = vim.tbl_count(binaries)
-- Count updates available -- Count updates available (only binaries with has_update = true)
local updates_available = 0 local updates_available = 0
local update_info = checker.get_update_info() local update_info = checker.get_update_info()
for _ in pairs(update_info) do for _, info in pairs(update_info) do
updates_available = updates_available + 1 if info.has_update then
updates_available = updates_available + 1
end
end end
local header_suffix = " (" .. binary_count .. ")" local header_suffix = " (" .. binary_count .. ")"
if updates_available > 0 then
header_suffix = header_suffix
end
text:append("Binaries", "GlazeH2"):append(header_suffix, "GlazeComment") text:append("Binaries", "GlazeH2"):append(header_suffix, "GlazeComment")
if updates_available > 0 then if updates_available > 0 then
text:append(" " .. updates_available .. " update(s) available", "GlazeRunning") text:append(" " .. updates_available .. " update(s) available", "GlazeRunning")
@@ -321,8 +323,8 @@ function M._render_binary(text, binary, icons, update_info)
text:append(" " .. icon .. " ", icon_hl) text:append(" " .. icon .. " ", icon_hl)
text:append(binary.name, "GlazeBinary") text:append(binary.name, "GlazeBinary")
if binary.plugin then if binary.plugins and #binary.plugins > 0 then
text:append(" (" .. binary.plugin .. ")", "GlazePlugin") text:append(" (" .. table.concat(binary.plugins, ", ") .. ")", "GlazePlugin")
end end
-- Show update available indicator -- Show update available indicator
@@ -351,9 +353,9 @@ function M._render_binary(text, binary, icons, update_info)
text:append(bin_path, "GlazeUrl"):nl() text:append(bin_path, "GlazeUrl"):nl()
end end
if binary.plugin then if binary.plugins and #binary.plugins > 0 then
text:append("Plugin: ", "GlazeComment", { indent = 6 }) text:append("Plugins: ", "GlazeComment", { indent = 6 })
text:append(binary.plugin, "GlazePlugin"):nl() text:append(table.concat(binary.plugins, ", "), "GlazePlugin"):nl()
end end
-- Show last error output from tasks -- Show last error output from tasks

46
scripts/vhs_init.lua Normal file
View File

@@ -0,0 +1,46 @@
-- Minimal init.lua for VHS recordings
-- Loads only glaze.nvim with no colorscheme or other plugins
-- Usage: nvim --clean -u /path/to/vhs_init.lua --cmd "set rtp+=/path/to/glaze.nvim"
-- Basic settings for clean recording
vim.o.number = true
vim.o.relativenumber = false
vim.o.signcolumn = "yes"
vim.o.termguicolors = true
vim.o.showmode = false
vim.o.ruler = false
vim.o.laststatus = 2
vim.o.cmdheight = 1
vim.o.updatetime = 100
vim.o.timeoutlen = 300
vim.o.swapfile = false
vim.o.backup = false
vim.o.writebackup = false
vim.o.autoread = true
-- Disable intro message and other noise
vim.opt.shortmess:append("I")
vim.opt.shortmess:append("c")
-- Simple statusline
vim.o.statusline = " %f %m%=%l:%c "
-- Set leader key
vim.g.mapleader = " "
-- Source the plugin file (--clean doesn't auto-load plugin/ dir)
vim.cmd("runtime! plugin/glaze.lua")
-- Load glaze with some demo binaries pre-registered
local glaze = require("glaze")
glaze.setup({
auto_install = { enabled = false },
auto_check = { enabled = false },
})
-- Register some binaries for the demo
glaze.register("freeze", "github.com/charmbracelet/freeze")
glaze.register("glow", "github.com/charmbracelet/glow")
glaze.register("gum", "github.com/charmbracelet/gum")
glaze.register("mods", "github.com/charmbracelet/mods")
glaze.register("vhs", "github.com/charmbracelet/vhs")