mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
support Kitty images
This commit is contained in:
parent
54e1fdb69f
commit
f49d55b19f
9 changed files with 84 additions and 20 deletions
|
|
@ -57,6 +57,7 @@ header:
|
|||
# Tiki settings
|
||||
tiki:
|
||||
maxPoints: 10 # Maximum story points for tasks
|
||||
maxImageRows: 40 # Maximum rows for inline images (Kitty protocol)
|
||||
|
||||
# Logging settings
|
||||
logging:
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ type Config struct {
|
|||
|
||||
// Tiki configuration
|
||||
Tiki struct {
|
||||
MaxPoints int `mapstructure:"maxPoints"`
|
||||
MaxPoints int `mapstructure:"maxPoints"`
|
||||
MaxImageRows int `mapstructure:"maxImageRows"`
|
||||
} `mapstructure:"tiki"`
|
||||
|
||||
// Appearance configuration
|
||||
|
|
@ -110,6 +111,7 @@ func setDefaults() {
|
|||
|
||||
// Tiki defaults
|
||||
viper.SetDefault("tiki.maxPoints", 10)
|
||||
viper.SetDefault("tiki.maxImageRows", 40)
|
||||
|
||||
// Appearance defaults
|
||||
viper.SetDefault("appearance.theme", "auto")
|
||||
|
|
@ -277,6 +279,15 @@ func GetMaxPoints() int {
|
|||
return maxPoints
|
||||
}
|
||||
|
||||
// GetMaxImageRows returns the maximum rows for inline image rendering
|
||||
func GetMaxImageRows() int {
|
||||
rows := viper.GetInt("tiki.maxImageRows")
|
||||
if rows < 1 {
|
||||
return 40
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// saveConfig writes the current viper configuration to config.yaml
|
||||
func saveConfig() error {
|
||||
configFile := viper.ConfigFileUsed()
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -3,7 +3,7 @@ module github.com/boolean-maybe/tiki
|
|||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/boolean-maybe/navidown v0.2.4
|
||||
github.com/boolean-maybe/navidown v0.3.0
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/glamour v0.10.0
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -25,8 +25,8 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v
|
|||
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/boolean-maybe/navidown v0.2.4 h1:If3LrcoLv4T/6VnWOjiVhseL9YR74Uc8gLv9Vta9bIc=
|
||||
github.com/boolean-maybe/navidown v0.2.4/go.mod h1:WdI3A007LaGuaJTOwEVO25kMojD9u4SCEVqAT8H9rwQ=
|
||||
github.com/boolean-maybe/navidown v0.3.0 h1:xhOvo7o+8MFyXb8scbWk+63PnJrvrlfzdtOnEWGruME=
|
||||
github.com/boolean-maybe/navidown v0.3.0/go.mod h1:WdI3A007LaGuaJTOwEVO25kMojD9u4SCEVqAT8H9rwQ=
|
||||
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
|
||||
|
|
|
|||
|
|
@ -34,10 +34,17 @@ func Run(input InputSpec) error {
|
|||
statusBar.SetDynamicColors(true)
|
||||
statusBar.SetTextAlign(tview.AlignLeft)
|
||||
|
||||
// Set up image rendering for Kitty-compatible terminals
|
||||
resolver := nav.NewImageResolver(input.SearchRoots)
|
||||
imgMgr := navtview.NewImageManager(resolver, 8, 16)
|
||||
imgMgr.SetMaxRows(config.GetMaxImageRows())
|
||||
imgMgr.SetSupported(util.SupportsKittyGraphics())
|
||||
|
||||
// Create NavigableMarkdown - OnStateChange is set after creation to avoid forward reference
|
||||
md := view.NewNavigableMarkdown(view.NavigableMarkdownConfig{
|
||||
Provider: provider,
|
||||
SearchRoots: input.SearchRoots,
|
||||
Provider: provider,
|
||||
SearchRoots: input.SearchRoots,
|
||||
ImageManager: imgMgr,
|
||||
})
|
||||
md.SetStateChangedHandler(func() {
|
||||
updateStatusBar(statusBar, md.Viewer())
|
||||
|
|
|
|||
23
util/terminal.go
Normal file
23
util/terminal.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package util
|
||||
|
||||
import "os"
|
||||
|
||||
// SupportsKittyGraphics returns true if the terminal supports the Kitty
|
||||
// graphics protocol (Unicode placeholders). Detection is env-var based:
|
||||
// Kitty sets KITTY_WINDOW_ID, WezTerm sets WEZTERM_EXECUTABLE, Ghostty
|
||||
// sets GHOSTTY_RESOURCES_DIR, and Konsole identifies via TERM_PROGRAM.
|
||||
func SupportsKittyGraphics() bool {
|
||||
if os.Getenv("KITTY_WINDOW_ID") != "" {
|
||||
return true
|
||||
}
|
||||
if os.Getenv("WEZTERM_EXECUTABLE") != "" {
|
||||
return true
|
||||
}
|
||||
if os.Getenv("GHOSTTY_RESOURCES_DIR") != "" {
|
||||
return true
|
||||
}
|
||||
if os.Getenv("TERM_PROGRAM") == "Konsole" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"github.com/boolean-maybe/navidown/loaders"
|
||||
nav "github.com/boolean-maybe/navidown/navidown"
|
||||
navtview "github.com/boolean-maybe/navidown/navidown/tview"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
|
@ -28,23 +29,26 @@ var customMd string
|
|||
|
||||
// DokiView renders a documentation plugin (navigable markdown)
|
||||
type DokiView struct {
|
||||
root *tview.Flex
|
||||
titleBar tview.Primitive
|
||||
markdown *NavigableMarkdown
|
||||
pluginDef *plugin.DokiPlugin
|
||||
registry *controller.ActionRegistry
|
||||
renderer renderer.MarkdownRenderer
|
||||
root *tview.Flex
|
||||
titleBar tview.Primitive
|
||||
markdown *NavigableMarkdown
|
||||
pluginDef *plugin.DokiPlugin
|
||||
registry *controller.ActionRegistry
|
||||
renderer renderer.MarkdownRenderer
|
||||
imageManager *navtview.ImageManager
|
||||
}
|
||||
|
||||
// NewDokiView creates a doki view
|
||||
func NewDokiView(
|
||||
pluginDef *plugin.DokiPlugin,
|
||||
mdRenderer renderer.MarkdownRenderer,
|
||||
imageManager *navtview.ImageManager,
|
||||
) *DokiView {
|
||||
dv := &DokiView{
|
||||
pluginDef: pluginDef,
|
||||
registry: controller.NewActionRegistry(),
|
||||
renderer: mdRenderer,
|
||||
pluginDef: pluginDef,
|
||||
registry: controller.NewActionRegistry(),
|
||||
renderer: mdRenderer,
|
||||
imageManager: imageManager,
|
||||
}
|
||||
|
||||
dv.build()
|
||||
|
|
@ -81,6 +85,7 @@ func (dv *DokiView) build() {
|
|||
Provider: provider,
|
||||
SearchRoots: searchRoots,
|
||||
OnStateChange: dv.UpdateNavigationActions,
|
||||
ImageManager: dv.imageManager,
|
||||
})
|
||||
|
||||
case "internal":
|
||||
|
|
@ -95,12 +100,14 @@ func (dv *DokiView) build() {
|
|||
dv.markdown = NewNavigableMarkdown(NavigableMarkdownConfig{
|
||||
Provider: provider,
|
||||
OnStateChange: dv.UpdateNavigationActions,
|
||||
ImageManager: dv.imageManager,
|
||||
})
|
||||
|
||||
default:
|
||||
content = "Error: Unknown fetcher type"
|
||||
dv.markdown = NewNavigableMarkdown(NavigableMarkdownConfig{
|
||||
OnStateChange: dv.UpdateNavigationActions,
|
||||
ImageManager: dv.imageManager,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,14 @@ package view
|
|||
import (
|
||||
"log/slog"
|
||||
|
||||
nav "github.com/boolean-maybe/navidown/navidown"
|
||||
navtview "github.com/boolean-maybe/navidown/navidown/tview"
|
||||
"github.com/boolean-maybe/tiki/config"
|
||||
"github.com/boolean-maybe/tiki/controller"
|
||||
"github.com/boolean-maybe/tiki/model"
|
||||
"github.com/boolean-maybe/tiki/plugin"
|
||||
"github.com/boolean-maybe/tiki/store"
|
||||
"github.com/boolean-maybe/tiki/util"
|
||||
"github.com/boolean-maybe/tiki/view/renderer"
|
||||
"github.com/boolean-maybe/tiki/view/taskdetail"
|
||||
)
|
||||
|
|
@ -16,8 +20,9 @@ import (
|
|||
|
||||
// ViewFactory creates views on demand
|
||||
type ViewFactory struct {
|
||||
taskStore store.Store
|
||||
renderer renderer.MarkdownRenderer
|
||||
taskStore store.Store
|
||||
renderer renderer.MarkdownRenderer
|
||||
imageManager *navtview.ImageManager
|
||||
// Plugin support
|
||||
pluginConfigs map[string]*model.PluginConfig
|
||||
pluginDefs map[string]plugin.Plugin
|
||||
|
|
@ -35,9 +40,15 @@ func NewViewFactory(taskStore store.Store) *ViewFactory {
|
|||
mdRenderer = glamourRenderer
|
||||
}
|
||||
|
||||
resolver := nav.NewImageResolver(nil)
|
||||
imgMgr := navtview.NewImageManager(resolver, 8, 16)
|
||||
imgMgr.SetMaxRows(config.GetMaxImageRows())
|
||||
imgMgr.SetSupported(util.SupportsKittyGraphics())
|
||||
|
||||
return &ViewFactory{
|
||||
taskStore: taskStore,
|
||||
renderer: mdRenderer,
|
||||
taskStore: taskStore,
|
||||
renderer: mdRenderer,
|
||||
imageManager: imgMgr,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +106,7 @@ func (f *ViewFactory) CreateView(viewID model.ViewID, params map[string]interfac
|
|||
slog.Error("plugin controller type mismatch", "plugin", pluginName)
|
||||
}
|
||||
} else if dokiPlugin, ok := pluginDef.(*plugin.DokiPlugin); ok {
|
||||
v = NewDokiView(dokiPlugin, f.renderer)
|
||||
v = NewDokiView(dokiPlugin, f.renderer, f.imageManager)
|
||||
} else {
|
||||
slog.Error("unknown plugin type or missing config", "plugin", pluginName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ type NavigableMarkdownConfig struct {
|
|||
Provider nav.ContentProvider
|
||||
SearchRoots []string
|
||||
OnStateChange func() // called on navigation state changes
|
||||
ImageManager *navtview.ImageManager
|
||||
}
|
||||
|
||||
// NewNavigableMarkdown creates a new navigable markdown viewer.
|
||||
|
|
@ -36,6 +37,9 @@ func NewNavigableMarkdown(cfg NavigableMarkdownConfig) *NavigableMarkdown {
|
|||
nm.viewer.SetAnsiConverter(navutil.NewAnsiConverter(true))
|
||||
nm.viewer.SetRenderer(nav.NewANSIRendererWithStyle(config.GetEffectiveTheme()))
|
||||
nm.viewer.SetBackgroundColor(config.GetContentBackgroundColor())
|
||||
if cfg.ImageManager != nil && cfg.ImageManager.Supported() {
|
||||
nm.viewer.SetImageManager(cfg.ImageManager)
|
||||
}
|
||||
nm.viewer.SetStateChangedHandler(func(_ *navtview.TextViewViewer) {
|
||||
if nm.onStateChange != nil {
|
||||
nm.onStateChange()
|
||||
|
|
|
|||
Loading…
Reference in a new issue