Added ui:set-item-annotator

This commit is contained in:
Leon Mika 2025-11-01 10:39:10 +11:00
parent a733a47d5c
commit c11560e6cd
7 changed files with 205 additions and 20 deletions

View file

@ -110,7 +110,7 @@ func main() {
tableService := tables.NewService(dynamoProvider, settingStore)
workspaceService := viewsnapshot.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(nil, uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo)
itemRendererService := itemrenderer.NewService(uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo)
jobsService := jobs.NewService(eventBus)
inputHistoryService := inputhistory.New(inputHistoryStore)
@ -177,6 +177,7 @@ func main() {
keyBindingController,
pasteboardProvider,
settingsController,
itemRendererService,
)
commandController, err := commandctrl.NewCommandController(inputHistoryService, stdCommands)

View file

@ -2,11 +2,14 @@ package cmdpacks
import (
"context"
"fmt"
tea "github.com/charmbracelet/bubbletea"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/events"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/itemrenderer"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
"ucl.lmika.dev/ucl"
)
@ -16,6 +19,7 @@ type uiModule struct {
state *controllers.State
ckb *customKeyBinding
readController *controllers.TableReadController
itemRenderer *itemrenderer.Service
}
func (m *uiModule) uiCommand(ctx context.Context, args ucl.CallArgs) (any, error) {
@ -191,15 +195,35 @@ func (m *uiModule) uiFilter(ctx context.Context, args ucl.CallArgs) (any, error)
return nil, nil
}
func (m *uiModule) uiSetItemAnnotator(ctx context.Context, args ucl.CallArgs) (any, error) {
var inv ucl.Invokable
if err := args.Bind(&inv); err != nil {
return nil, err
}
m.itemRenderer.SetAnnotation(itemrenderer.AnnotationFunc(func(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string {
v, err := inv.Invoke(ctx, newResultSetProxy(rs), itemProxy{rs, 0, item}, attrPathProxy{attrPath: &path})
if err != nil {
return ""
} else if v == nil {
return ""
}
return fmt.Sprint(v)
}))
return nil, nil
}
func moduleUI(
tableService *tables.Service,
state *controllers.State,
readController *controllers.TableReadController,
itemRenderer *itemrenderer.Service,
) (ucl.Module, controllers.CustomKeyBindingSource) {
m := &uiModule{
tableService: tableService,
state: state,
readController: readController,
itemRenderer: itemRenderer,
ckb: &customKeyBinding{
bindings: map[string]tea.Cmd{},
keyBindings: map[string]string{},
@ -209,14 +233,15 @@ func moduleUI(
return ucl.Module{
Name: "ui",
Builtins: map[string]ucl.BuiltinHandler{
"command": m.uiCommand,
"prompt": m.uiPrompt,
"prompt-table": m.uiPromptTable,
"prompt-keypress": m.uiInKey,
"confirm": m.uiConfirm,
"query": m.uiQuery,
"filter": m.uiFilter,
"bind": m.uiBind,
"command": m.uiCommand,
"prompt": m.uiPrompt,
"prompt-table": m.uiPromptTable,
"prompt-keypress": m.uiInKey,
"confirm": m.uiConfirm,
"query": m.uiQuery,
"filter": m.uiFilter,
"bind": m.uiBind,
"set-item-annotator": m.uiSetItemAnnotator,
},
}, m.ckb
}

View file

@ -234,6 +234,49 @@ func (tp itemProxy) Each(fn func(k string, v ucl.Object) error) error {
return nil
}
type attrPathProxy struct {
attrPath *models.AttrPathNode
}
func (ap attrPathProxy) String() string {
return "RSItem()"
}
func (ap attrPathProxy) Truthy() bool {
return true
}
func (ap attrPathProxy) Len() (l int) {
for p := ap.attrPath; p != nil; p = p.Parent {
l++
}
return
}
func (ap attrPathProxy) Index(k int) ucl.Object {
if k == -1 {
return ucl.StringObject(ap.attrPath.Key)
}
if k >= 0 {
k = ap.Len() - k - 1
} else {
k = -k - 1
}
if k < 0 {
return nil
}
for p := ap.attrPath; p != nil; p = p.Parent {
if k <= 0 {
return ucl.StringObject(p.Key)
}
k -= 1
}
return nil
}
type attributeValueProxy struct {
value types.AttributeValue
}

View file

@ -0,0 +1,101 @@
package cmdpacks
import (
"testing"
"github.com/stretchr/testify/assert"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
)
func TestAttrPathProxy_Index(t *testing.T) {
tests := []struct {
descr string
attrPath models.AttrPathNode
index int
want string
wantNil bool
}{
{
descr: "return leaf 1",
attrPath: models.AttrPathNode{Key: "leaf"},
index: -1,
want: "leaf",
},
{
descr: "return leaf 2",
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent"}},
index: -1,
want: "leaf",
},
{
descr: "return parent 1",
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent"}},
index: -2,
want: "parent",
},
{
descr: "return parent 1",
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent", Parent: &models.AttrPathNode{Key: "grandparent"}}},
index: -2,
want: "parent",
},
{
descr: "return parent 3",
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent", Parent: &models.AttrPathNode{Key: "grandparent"}}},
index: -3,
want: "grandparent",
},
{
descr: "return root 1",
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent", Parent: &models.AttrPathNode{Key: "grandparent"}}},
index: 0,
want: "grandparent",
},
{
descr: "return root 2",
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent"}},
index: 0,
want: "parent",
},
{
descr: "return root 3",
attrPath: models.AttrPathNode{Key: "leaf"},
index: 0,
want: "leaf",
},
{
descr: "return first child 1",
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent", Parent: &models.AttrPathNode{Key: "grandparent"}}},
index: 1,
want: "parent",
},
{
descr: "return first child 2",
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent"}},
index: 1,
want: "leaf",
},
{
descr: "return nil 1",
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent", Parent: &models.AttrPathNode{Key: "grandparent"}}},
index: -5,
wantNil: true,
},
{
descr: "return nil 2",
attrPath: models.AttrPathNode{Key: "leaf", Parent: &models.AttrPathNode{Key: "parent"}},
index: 56,
wantNil: true,
},
}
for _, tt := range tests {
t.Run(tt.descr, func(t *testing.T) {
proxy := attrPathProxy{&tt.attrPath}
if tt.wantNil {
assert.Nil(t, proxy.Index(tt.index))
} else {
assert.Equal(t, tt.want, proxy.Index(tt.index).String())
}
})
}
}

View file

@ -9,6 +9,7 @@ import (
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/itemrenderer"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
"ucl.lmika.dev/repl"
"ucl.lmika.dev/ucl"
@ -23,6 +24,7 @@ type StandardCommands struct {
KeyBindingController *controllers.KeyBindingController
PBProvider services.PasteboardProvider
SettingsController *controllers.SettingsController
ItemRenderer *itemrenderer.Service
modUI ucl.Module
}
@ -36,8 +38,9 @@ func NewStandardCommands(
keyBindingController *controllers.KeyBindingController,
pbProvider services.PasteboardProvider,
settingsController *controllers.SettingsController,
itemRenderer *itemrenderer.Service,
) StandardCommands {
modUI, ckbs := moduleUI(tableService, state, readController)
modUI, ckbs := moduleUI(tableService, state, readController, itemRenderer)
keyBindingController.SetCustomKeyBindingSource(ckbs)
return StandardCommands{
@ -49,6 +52,7 @@ func NewStandardCommands(
KeyBindingController: keyBindingController,
PBProvider: pbProvider,
SettingsController: settingsController,
ItemRenderer: itemRenderer,
modUI: modUI,
}
}

View file

@ -2,6 +2,8 @@ package cmdpacks_test
import (
"fmt"
"testing"
tea "github.com/charmbracelet/bubbletea"
bus "github.com/lmika/events"
"github.com/stretchr/testify/assert"
@ -21,7 +23,6 @@ import (
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/keybindings"
"lmika.dev/cmd/dynamo-browse/test/testdynamo"
"lmika.dev/cmd/dynamo-browse/test/testworkspace"
"testing"
)
func TestStdCmds_Mark(t *testing.T) {
@ -162,6 +163,7 @@ func newService(t *testing.T, opts ...serviceOpt) *services {
keyBindingController,
testPB,
settingsController,
itemRendererService,
),
)

View file

@ -10,17 +10,22 @@ import (
)
type Service struct {
annotations Annotation
styles styleRenderer
annotation Annotation
styles styleRenderer
}
func NewService(
annotations Annotation,
fileTypeStyle StyleRenderer,
metaInfoStyle StyleRenderer,
) *Service {
if fileTypeStyle == nil {
fileTypeStyle = plainTextStyleRenderer{}
}
if metaInfoStyle == nil {
metaInfoStyle = plainTextStyleRenderer{}
}
return &Service{
annotations: testAnnotation{},
annotation: nil,
styles: styleRenderer{
fileTypeRenderer: fileTypeStyle,
metaInfoRenderer: metaInfoStyle,
@ -28,6 +33,10 @@ func NewService(
}
}
func (s *Service) SetAnnotation(a Annotation) {
s.annotation = a
}
func (s *Service) RenderItem(w io.Writer, item models.Item, resultSet *models.ResultSet, plainText bool) {
styles := s.styles
if plainText {
@ -71,9 +80,9 @@ func (m *Service) renderItem(
fmt.Fprint(w, "\t")
fmt.Fprint(w, r.StringValue())
fmt.Fprint(w, sr.metaInfoRenderer.Render(r.MetaInfo()))
if m.annotations != nil {
if m.annotation != nil {
fmt.Fprint(w, " ")
fmt.Fprint(w, sr.metaInfoRenderer.Render(m.annotations.AnnotateAttribute(resultSet, item, path)))
fmt.Fprint(w, sr.metaInfoRenderer.Render(m.annotation.AnnotateAttribute(resultSet, item, path)))
}
fmt.Fprint(w, "\n")
@ -94,8 +103,8 @@ type Annotation interface {
AnnotateAttribute(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string
}
type testAnnotation struct{}
type AnnotationFunc func(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string
func (t testAnnotation) AnnotateAttribute(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string {
return "( annotation of " + path.Key + " )"
func (af AnnotationFunc) AnnotateAttribute(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string {
return af(rs, item, path)
}