Added ui:set-item-annotator
This commit is contained in:
parent
a733a47d5c
commit
c11560e6cd
|
|
@ -110,7 +110,7 @@ func main() {
|
||||||
|
|
||||||
tableService := tables.NewService(dynamoProvider, settingStore)
|
tableService := tables.NewService(dynamoProvider, settingStore)
|
||||||
workspaceService := viewsnapshot.NewService(resultSetSnapshotStore)
|
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)
|
jobsService := jobs.NewService(eventBus)
|
||||||
inputHistoryService := inputhistory.New(inputHistoryStore)
|
inputHistoryService := inputhistory.New(inputHistoryStore)
|
||||||
|
|
||||||
|
|
@ -177,6 +177,7 @@ func main() {
|
||||||
keyBindingController,
|
keyBindingController,
|
||||||
pasteboardProvider,
|
pasteboardProvider,
|
||||||
settingsController,
|
settingsController,
|
||||||
|
itemRendererService,
|
||||||
)
|
)
|
||||||
|
|
||||||
commandController, err := commandctrl.NewCommandController(inputHistoryService, stdCommands)
|
commandController, err := commandctrl.NewCommandController(inputHistoryService, stdCommands)
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,14 @@ package cmdpacks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
|
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/common/ui/events"
|
"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/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"
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
@ -16,6 +19,7 @@ type uiModule struct {
|
||||||
state *controllers.State
|
state *controllers.State
|
||||||
ckb *customKeyBinding
|
ckb *customKeyBinding
|
||||||
readController *controllers.TableReadController
|
readController *controllers.TableReadController
|
||||||
|
itemRenderer *itemrenderer.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *uiModule) uiCommand(ctx context.Context, args ucl.CallArgs) (any, error) {
|
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
|
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(
|
func moduleUI(
|
||||||
tableService *tables.Service,
|
tableService *tables.Service,
|
||||||
state *controllers.State,
|
state *controllers.State,
|
||||||
readController *controllers.TableReadController,
|
readController *controllers.TableReadController,
|
||||||
|
itemRenderer *itemrenderer.Service,
|
||||||
) (ucl.Module, controllers.CustomKeyBindingSource) {
|
) (ucl.Module, controllers.CustomKeyBindingSource) {
|
||||||
m := &uiModule{
|
m := &uiModule{
|
||||||
tableService: tableService,
|
tableService: tableService,
|
||||||
state: state,
|
state: state,
|
||||||
readController: readController,
|
readController: readController,
|
||||||
|
itemRenderer: itemRenderer,
|
||||||
ckb: &customKeyBinding{
|
ckb: &customKeyBinding{
|
||||||
bindings: map[string]tea.Cmd{},
|
bindings: map[string]tea.Cmd{},
|
||||||
keyBindings: map[string]string{},
|
keyBindings: map[string]string{},
|
||||||
|
|
@ -209,14 +233,15 @@ func moduleUI(
|
||||||
return ucl.Module{
|
return ucl.Module{
|
||||||
Name: "ui",
|
Name: "ui",
|
||||||
Builtins: map[string]ucl.BuiltinHandler{
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
"command": m.uiCommand,
|
"command": m.uiCommand,
|
||||||
"prompt": m.uiPrompt,
|
"prompt": m.uiPrompt,
|
||||||
"prompt-table": m.uiPromptTable,
|
"prompt-table": m.uiPromptTable,
|
||||||
"prompt-keypress": m.uiInKey,
|
"prompt-keypress": m.uiInKey,
|
||||||
"confirm": m.uiConfirm,
|
"confirm": m.uiConfirm,
|
||||||
"query": m.uiQuery,
|
"query": m.uiQuery,
|
||||||
"filter": m.uiFilter,
|
"filter": m.uiFilter,
|
||||||
"bind": m.uiBind,
|
"bind": m.uiBind,
|
||||||
|
"set-item-annotator": m.uiSetItemAnnotator,
|
||||||
},
|
},
|
||||||
}, m.ckb
|
}, m.ckb
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,49 @@ func (tp itemProxy) Each(fn func(k string, v ucl.Object) error) error {
|
||||||
return nil
|
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 {
|
type attributeValueProxy struct {
|
||||||
value types.AttributeValue
|
value types.AttributeValue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
101
internal/common/ui/commandctrl/cmdpacks/proxy_test.go
Normal file
101
internal/common/ui/commandctrl/cmdpacks/proxy_test.go
Normal 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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers"
|
"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/models"
|
||||||
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services"
|
"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"
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
|
||||||
"ucl.lmika.dev/repl"
|
"ucl.lmika.dev/repl"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
|
|
@ -23,6 +24,7 @@ type StandardCommands struct {
|
||||||
KeyBindingController *controllers.KeyBindingController
|
KeyBindingController *controllers.KeyBindingController
|
||||||
PBProvider services.PasteboardProvider
|
PBProvider services.PasteboardProvider
|
||||||
SettingsController *controllers.SettingsController
|
SettingsController *controllers.SettingsController
|
||||||
|
ItemRenderer *itemrenderer.Service
|
||||||
|
|
||||||
modUI ucl.Module
|
modUI ucl.Module
|
||||||
}
|
}
|
||||||
|
|
@ -36,8 +38,9 @@ func NewStandardCommands(
|
||||||
keyBindingController *controllers.KeyBindingController,
|
keyBindingController *controllers.KeyBindingController,
|
||||||
pbProvider services.PasteboardProvider,
|
pbProvider services.PasteboardProvider,
|
||||||
settingsController *controllers.SettingsController,
|
settingsController *controllers.SettingsController,
|
||||||
|
itemRenderer *itemrenderer.Service,
|
||||||
) StandardCommands {
|
) StandardCommands {
|
||||||
modUI, ckbs := moduleUI(tableService, state, readController)
|
modUI, ckbs := moduleUI(tableService, state, readController, itemRenderer)
|
||||||
keyBindingController.SetCustomKeyBindingSource(ckbs)
|
keyBindingController.SetCustomKeyBindingSource(ckbs)
|
||||||
|
|
||||||
return StandardCommands{
|
return StandardCommands{
|
||||||
|
|
@ -49,6 +52,7 @@ func NewStandardCommands(
|
||||||
KeyBindingController: keyBindingController,
|
KeyBindingController: keyBindingController,
|
||||||
PBProvider: pbProvider,
|
PBProvider: pbProvider,
|
||||||
SettingsController: settingsController,
|
SettingsController: settingsController,
|
||||||
|
ItemRenderer: itemRenderer,
|
||||||
modUI: modUI,
|
modUI: modUI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package cmdpacks_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
bus "github.com/lmika/events"
|
bus "github.com/lmika/events"
|
||||||
"github.com/stretchr/testify/assert"
|
"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/internal/dynamo-browse/ui/keybindings"
|
||||||
"lmika.dev/cmd/dynamo-browse/test/testdynamo"
|
"lmika.dev/cmd/dynamo-browse/test/testdynamo"
|
||||||
"lmika.dev/cmd/dynamo-browse/test/testworkspace"
|
"lmika.dev/cmd/dynamo-browse/test/testworkspace"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStdCmds_Mark(t *testing.T) {
|
func TestStdCmds_Mark(t *testing.T) {
|
||||||
|
|
@ -162,6 +163,7 @@ func newService(t *testing.T, opts ...serviceOpt) *services {
|
||||||
keyBindingController,
|
keyBindingController,
|
||||||
testPB,
|
testPB,
|
||||||
settingsController,
|
settingsController,
|
||||||
|
itemRendererService,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,22 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
annotations Annotation
|
annotation Annotation
|
||||||
styles styleRenderer
|
styles styleRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
annotations Annotation,
|
|
||||||
fileTypeStyle StyleRenderer,
|
fileTypeStyle StyleRenderer,
|
||||||
metaInfoStyle StyleRenderer,
|
metaInfoStyle StyleRenderer,
|
||||||
) *Service {
|
) *Service {
|
||||||
|
if fileTypeStyle == nil {
|
||||||
|
fileTypeStyle = plainTextStyleRenderer{}
|
||||||
|
}
|
||||||
|
if metaInfoStyle == nil {
|
||||||
|
metaInfoStyle = plainTextStyleRenderer{}
|
||||||
|
}
|
||||||
return &Service{
|
return &Service{
|
||||||
annotations: testAnnotation{},
|
annotation: nil,
|
||||||
styles: styleRenderer{
|
styles: styleRenderer{
|
||||||
fileTypeRenderer: fileTypeStyle,
|
fileTypeRenderer: fileTypeStyle,
|
||||||
metaInfoRenderer: metaInfoStyle,
|
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) {
|
func (s *Service) RenderItem(w io.Writer, item models.Item, resultSet *models.ResultSet, plainText bool) {
|
||||||
styles := s.styles
|
styles := s.styles
|
||||||
if plainText {
|
if plainText {
|
||||||
|
|
@ -71,9 +80,9 @@ func (m *Service) renderItem(
|
||||||
fmt.Fprint(w, "\t")
|
fmt.Fprint(w, "\t")
|
||||||
fmt.Fprint(w, r.StringValue())
|
fmt.Fprint(w, r.StringValue())
|
||||||
fmt.Fprint(w, sr.metaInfoRenderer.Render(r.MetaInfo()))
|
fmt.Fprint(w, sr.metaInfoRenderer.Render(r.MetaInfo()))
|
||||||
if m.annotations != nil {
|
if m.annotation != nil {
|
||||||
fmt.Fprint(w, " ")
|
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")
|
fmt.Fprint(w, "\n")
|
||||||
|
|
||||||
|
|
@ -94,8 +103,8 @@ type Annotation interface {
|
||||||
AnnotateAttribute(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string
|
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 {
|
func (af AnnotationFunc) AnnotateAttribute(rs *models.ResultSet, item models.Item, path models.AttrPathNode) string {
|
||||||
return "( annotation of " + path.Key + " )"
|
return af(rs, item, path)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue