Skip to content

Commit 0201807

Browse files
authored
improve ux around optional proto fields (#15)
1 parent 81f015c commit 0201807

9 files changed

Lines changed: 134 additions & 120 deletions

File tree

cmd/testserver/echo/echo.pb.go

Lines changed: 15 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/testserver/echo/echo.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ message Message {
5656
}
5757

5858
NestedMessage nested_message = 18;
59+
60+
optional string opt_message = 19;
5961
}
6062

6163
service EchoService {

cmd/testserver/echo/echo_grpc.pb.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/testserver/hello/hello.pb.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/testserver/hello/hello_grpc.pb.go

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/cli/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.0.11
1+
0.0.12

internal/tui/call/builder.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func buildFieldGroup(msgDesc protoreflect.MessageDescriptor) *fieldGroup {
158158
continue
159159
}
160160

161-
if field.ContainingOneof() != nil {
161+
if oneof := field.ContainingOneof(); oneof != nil && !oneof.IsSynthetic() {
162162
continue
163163
}
164164

@@ -179,6 +179,9 @@ func buildFieldGroup(msgDesc protoreflect.MessageDescriptor) *fieldGroup {
179179
oneofs := msgDesc.Oneofs()
180180
for i := 0; i < oneofs.Len(); i++ {
181181
oneof := oneofs.Get(i)
182+
if oneof.IsSynthetic() {
183+
continue
184+
}
182185
oneofName := string(oneof.Name())
183186
oneofField := NewOneofField(oneofName, oneof)
184187
if oneofField != nil {

internal/tui/call/field.go

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ const (
2424
)
2525

2626
type Field struct {
27-
name string
28-
kind fieldKind
27+
name string
28+
kind fieldKind
29+
optional bool
30+
present bool
31+
optionalControlFocused bool
32+
width int
2933

3034
textInput textinput.Model
3135
enumPicker enumPicker
@@ -37,6 +41,8 @@ type Field struct {
3741
validate func(string) error
3842
}
3943

44+
const optionalControlWidth = 6
45+
4046
func NewTextField(name string, placeholder string, charLimit int, validate func(string) error) *Field {
4147
ti := textinput.New()
4248
ti.Placeholder = placeholder
@@ -180,7 +186,14 @@ func validateTimestamp(s string) error {
180186
}
181187

182188
func NewFieldFromProto(field protoreflect.FieldDescriptor) *Field {
183-
return newFieldFromProto(field, "")
189+
f := newFieldFromProto(field, "")
190+
if f != nil {
191+
f.optional = field.HasOptionalKeyword()
192+
if f.optional && field.Kind() == protoreflect.StringKind {
193+
f.textInput.Placeholder = "Enter string..."
194+
}
195+
}
196+
return f
184197
}
185198

186199
func newFieldFromProto(field protoreflect.FieldDescriptor, inputRole string) *Field {
@@ -309,7 +322,32 @@ func (f *Field) Name() string {
309322
return f.name
310323
}
311324

325+
func (f *Field) checkbox(focused bool) string {
326+
if !f.optional {
327+
return ""
328+
}
329+
mark := unselectedStyle.Render(" ")
330+
if f.present {
331+
mark = selectedStyle.Render("x")
332+
}
333+
box := bracketStyle.Render("[") + mark + bracketStyle.Render("]")
334+
if focused {
335+
return " " + focusedLabelStyle.Render("> ") + box
336+
}
337+
return " " + box
338+
}
339+
340+
func (f *Field) inputView(prefix string) string {
341+
if f.optional && f.kind == FieldText && f.width > 0 {
342+
f.textInput.Width = max(1, f.width-len(prefix)-len(f.name)-2-optionalControlWidth)
343+
}
344+
return f.View()
345+
}
346+
312347
func (f *Field) AcceptsTextInput() bool {
348+
if f.optionalControlFocused {
349+
return false
350+
}
313351
switch f.kind {
314352
case FieldText:
315353
return true
@@ -330,6 +368,7 @@ func (f *Field) AcceptsTextInput() bool {
330368
}
331369

332370
func (f *Field) Focus() tea.Cmd {
371+
f.optionalControlFocused = false
333372
switch f.kind {
334373
case FieldText:
335374
return f.textInput.Focus()
@@ -354,6 +393,16 @@ func (f *Field) Focus() tea.Cmd {
354393
}
355394

356395
func (f *Field) FocusFromEnd() tea.Cmd {
396+
if f.optional {
397+
f.Blur()
398+
f.optionalControlFocused = true
399+
return nil
400+
}
401+
return f.focusValueFromEnd()
402+
}
403+
404+
func (f *Field) focusValueFromEnd() tea.Cmd {
405+
f.optionalControlFocused = false
357406
switch f.kind {
358407
case FieldText:
359408
return f.textInput.Focus()
@@ -378,6 +427,7 @@ func (f *Field) FocusFromEnd() tea.Cmd {
378427
}
379428

380429
func (f *Field) Blur() {
430+
f.optionalControlFocused = false
381431
switch f.kind {
382432
case FieldText:
383433
f.textInput.Blur()
@@ -445,6 +495,11 @@ func (f *Field) Prev() bool {
445495
}
446496

447497
func (f *Field) HandleKey(msg tea.KeyMsg) (tea.Cmd, bool) {
498+
if f.optionalControlFocused && msg.String() == "enter" {
499+
f.present = !f.present
500+
return nil, true
501+
}
502+
448503
switch f.kind {
449504
case FieldEnum, FieldBool:
450505
switch msg.String() {
@@ -473,10 +528,16 @@ func (f *Field) HandleKey(msg tea.KeyMsg) (tea.Cmd, bool) {
473528
}
474529

475530
func (f *Field) Update(msg tea.Msg) tea.Cmd {
531+
if f.optionalControlFocused {
532+
return nil
533+
}
476534
switch f.kind {
477535
case FieldText:
478536
var cmd tea.Cmd
479537
f.textInput, cmd = f.textInput.Update(msg)
538+
if f.optional && f.textInput.Value() != "" {
539+
f.present = true
540+
}
480541
return cmd
481542
case FieldGroup:
482543
if f.fieldGroup != nil {
@@ -499,6 +560,7 @@ func (f *Field) Update(msg tea.Msg) tea.Cmd {
499560
}
500561

501562
func (f *Field) SetWidth(width int) {
563+
f.width = width
502564
switch f.kind {
503565
case FieldText:
504566
f.textInput.Width = width
@@ -524,52 +586,54 @@ func (f *Field) RenderWithFocus(focused bool, depth int) string {
524586
indent := strings.Repeat(" ", depth)
525587

526588
var prefix string
527-
if focused {
589+
inputFocused := focused && !f.optionalControlFocused
590+
if inputFocused {
528591
prefix = indent + "> "
529592
} else {
530593
prefix = indent + " "
531594
}
532595

533596
switch f.kind {
534597
case FieldGroup:
535-
if focused {
598+
if inputFocused {
536599
b.WriteString(focusedLabelStyle.Render(prefix + f.name + ":"))
537600
} else {
538601
b.WriteString(labelStyle.Render(prefix + f.name + ":"))
539602
}
540603
b.WriteString("\n")
541604
b.WriteString(f.fieldGroup.ViewWithDepth(depth + 1))
542605
case FieldList:
543-
if focused {
606+
if inputFocused {
544607
b.WriteString(focusedLabelStyle.Render(prefix + f.name + ":"))
545608
} else {
546609
b.WriteString(labelStyle.Render(prefix + f.name + ":"))
547610
}
548611
b.WriteString("\n")
549612
b.WriteString(f.listField.ViewWithDepth(depth + 1))
550613
case FieldMap:
551-
if focused {
614+
if inputFocused {
552615
b.WriteString(focusedLabelStyle.Render(prefix + f.name + ":"))
553616
} else {
554617
b.WriteString(labelStyle.Render(prefix + f.name + ":"))
555618
}
556619
b.WriteString("\n")
557620
b.WriteString(f.mapField.ViewWithDepth(depth + 1))
558621
case FieldOneof:
559-
if focused {
622+
if inputFocused {
560623
b.WriteString(focusedLabelStyle.Render(prefix + f.name + ":"))
561624
} else {
562625
b.WriteString(labelStyle.Render(prefix + f.name + ":"))
563626
}
564627
b.WriteString("\n")
565628
b.WriteString(f.oneofField.ViewWithDepth(depth + 1))
566629
default:
567-
if focused {
630+
if inputFocused {
568631
b.WriteString(focusedLabelStyle.Render(prefix + f.name + ": "))
569632
} else {
570633
b.WriteString(labelStyle.Render(prefix + f.name + ": "))
571634
}
572-
b.WriteString(f.View())
635+
b.WriteString(f.inputView(prefix))
636+
b.WriteString(f.checkbox(focused && f.optionalControlFocused))
573637
b.WriteString("\n")
574638
}
575639

0 commit comments

Comments
 (0)