diff --git a/internal/app/fullscreen/help.go b/internal/app/fullscreen/help.go index a86f2248..e9fcfaa7 100644 --- a/internal/app/fullscreen/help.go +++ b/internal/app/fullscreen/help.go @@ -54,6 +54,7 @@ func newHelp() results.Model { for _, str := range strings.Split(helpMsg, "\n") { x.Push(str) } + x.MaxWrapWidth(65) return x } diff --git a/internal/app/fullscreen/model.go b/internal/app/fullscreen/model.go index 1116f3c1..db60a6f4 100644 --- a/internal/app/fullscreen/model.go +++ b/internal/app/fullscreen/model.go @@ -2,6 +2,8 @@ package fullscreen import ( "context" + "github.com/janderland/fdbq/internal/app/fullscreen/results" + "github.com/janderland/fdbq/internal/app/fullscreen/stack" "io" "time" @@ -12,37 +14,9 @@ import ( "github.com/janderland/fdbq/engine" "github.com/janderland/fdbq/internal/app/fullscreen/manager" - "github.com/janderland/fdbq/internal/app/fullscreen/results" "github.com/janderland/fdbq/parser/format" ) -type Mode int - -const ( - modeScroll Mode = iota - modeInput - modeHelp - modeQuit -) - -type Style struct { - results lip.Style - input lip.Style -} - -type Model struct { - qm manager.QueryManager - log zerolog.Logger - latest time.Time - mode Mode - - style Style - results results.Model - help results.Model - quit results.Model - input textinput.Model -} - type App struct { Engine engine.Engine Format format.Format @@ -58,16 +32,14 @@ func (x *App) Run(ctx context.Context) error { input := textinput.New() input.Placeholder = "Query" - model := Model{ - qm: manager.New( - ctx, - x.Engine, - manager.WithSingleOpts(x.SingleOpts), - manager.WithRangeOpts(x.RangeOpts), - manager.WithWrite(x.Write)), + resultsStack := stack.ResultsStack{} + resultsStack.Push(results.New( + results.WithFormat(x.Format), + results.WithLogger(x.Log))) - log: x.Log, + model := Model{ mode: modeScroll, + log: x.Log, style: Style{ results: lip.NewStyle(). @@ -77,10 +49,16 @@ func (x *App) Run(ctx context.Context) error { Border(lip.RoundedBorder()). Padding(0, 1), }, - results: results.New(results.WithFormat(x.Format), results.WithLogger(x.Log)), - help: newHelp(), - quit: newQuit(), + + results: resultsStack, input: input, + + qm: manager.New( + ctx, + x.Engine, + manager.WithSingleOpts(x.SingleOpts), + manager.WithRangeOpts(x.RangeOpts), + manager.WithWrite(x.Write)), } _, err := tea.NewProgram( @@ -91,3 +69,28 @@ func (x *App) Run(ctx context.Context) error { ).Run() return err } + +type Mode int + +const ( + modeScroll Mode = iota + modeInput + modeHelp + modeQuit +) + +type Style struct { + results lip.Style + input lip.Style +} + +type Model struct { + mode Mode + latest time.Time + log zerolog.Logger + + style Style + input textinput.Model + results stack.ResultsStack + qm manager.QueryManager +} diff --git a/internal/app/fullscreen/results/results.go b/internal/app/fullscreen/results/results.go index 25062acd..13c18a8c 100644 --- a/internal/app/fullscreen/results/results.go +++ b/internal/app/fullscreen/results/results.go @@ -8,6 +8,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/janderland/fdbq/internal/app/fullscreen/results/wrap" "github.com/rs/zerolog" + "math" "strings" "github.com/janderland/fdbq/engine/stream" @@ -90,6 +91,11 @@ type Model struct { // line is wrapped. 0 disables wrapping. wrapWidth int + // maxWrapWidth caps the wrapWidth value. + // If a higher value is set the maxWrapWidth + // is used instead. + maxWrapWidth int + // spaced determines if a blank line // appears between each item. spaced bool @@ -126,11 +132,12 @@ type Model struct { func New(opts ...Option) Model { x := Model{ - log: zerolog.Nop(), - keyMap: defaultKeyMap(), - format: format.New(), - builder: &strings.Builder{}, - list: list.New(), + log: zerolog.Nop(), + keyMap: defaultKeyMap(), + format: format.New(), + maxWrapWidth: math.MaxInt, + builder: &strings.Builder{}, + list: list.New(), } for _, option := range opts { option(&x) @@ -170,12 +177,19 @@ func (x *Model) Height(height int) { } func (x *Model) WrapWidth(width int) { + if width > x.maxWrapWidth { + width = x.maxWrapWidth + } x.log.Log().Int("wrapWidth", width).Msg("setting") x.wrapWidth = width x.subCursor = 0 x.updateCursors() } +func (x *Model) MaxWrapWidth(width int) { + x.maxWrapWidth = width +} + func (x *Model) PushMany(list *list.List) { x.log.Log().Int("n", list.Len()).Msg("pushing many") for cursor := list.Front(); cursor != nil; cursor = cursor.Next() { @@ -327,7 +341,7 @@ func (x *Model) str(item any) string { } } -func (x *Model) Update(msg tea.Msg) Model { +func (x *Model) Scroll(msg tea.Msg) { switch msg := msg.(type) { case tea.KeyMsg: switch { @@ -365,8 +379,6 @@ func (x *Model) Update(msg tea.Msg) Model { x.scrollUpItems(1) } } - - return *x } func (x *Model) scrollDownItems(n int) bool { diff --git a/internal/app/fullscreen/stack/stack.go b/internal/app/fullscreen/stack/stack.go new file mode 100644 index 00000000..909e6395 --- /dev/null +++ b/internal/app/fullscreen/stack/stack.go @@ -0,0 +1,43 @@ +package stack + +import "github.com/janderland/fdbq/internal/app/fullscreen/results" + +type ResultsStack struct { + stack []results.Model + + height int + wrapWidth int +} + +func (x *ResultsStack) Push(model results.Model) { + model.Height(x.height) + model.WrapWidth(x.wrapWidth) + x.stack = append(x.stack, model) +} + +func (x *ResultsStack) Pop() { + if len(x.stack) != 0 { + x.stack = x.stack[:len(x.stack)-1] + } +} + +func (x *ResultsStack) Top() *results.Model { + if len(x.stack) == 0 { + return nil + } + return &x.stack[len(x.stack)-1] +} + +func (x *ResultsStack) Height(height int) { + x.height = height + for i := range x.stack { + x.stack[i].Height(height) + } +} + +func (x *ResultsStack) WrapWidth(width int) { + x.wrapWidth = width + for i := range x.stack { + x.stack[i].WrapWidth(width) + } +} diff --git a/internal/app/fullscreen/stack/stack_test.go b/internal/app/fullscreen/stack/stack_test.go new file mode 100644 index 00000000..42a4761f --- /dev/null +++ b/internal/app/fullscreen/stack/stack_test.go @@ -0,0 +1,26 @@ +package stack + +import ( + "github.com/janderland/fdbq/internal/app/fullscreen/results" + "github.com/stretchr/testify/require" + "testing" +) + +func TestResultsStack(t *testing.T) { + var ( + x ResultsStack + r1 = results.New() + r2 = results.New() + ) + + require.Nil(t, x.Top()) + + x.Push(r1) + require.Equal(t, &r1, x.Top()) + + x.Push(r2) + require.Equal(t, &r2, x.Top()) + + x.Pop() + require.Equal(t, &r1, x.Top()) +} diff --git a/internal/app/fullscreen/update.go b/internal/app/fullscreen/update.go index 39e5862c..aa11eb70 100644 --- a/internal/app/fullscreen/update.go +++ b/internal/app/fullscreen/update.go @@ -64,15 +64,17 @@ func (x Model) updateKey(msg tea.KeyMsg) (Model, tea.Cmd) { case "?": x.mode = modeHelp + x.results.Push(newHelp()) return x, nil case "q": x.mode = modeQuit + x.results.Push(newQuit()) return x, nil } } - x.results = x.results.Update(msg) + x.results.Top().Scroll(msg) return x, nil case modeInput: @@ -90,7 +92,7 @@ func (x Model) updateKey(msg tea.KeyMsg) (Model, tea.Cmd) { return x, nil case tea.KeyUp, tea.KeyDown, tea.KeyPgUp, tea.KeyPgDown: - x.results = x.results.Update(msg) + x.results.Top().Scroll(msg) } var cmd tea.Cmd @@ -101,22 +103,25 @@ func (x Model) updateKey(msg tea.KeyMsg) (Model, tea.Cmd) { switch msg.Type { case tea.KeyEscape: x.mode = modeScroll + x.results.Pop() return x, nil } - x.help = x.help.Update(msg) + x.results.Top().Scroll(msg) return x, nil case modeQuit: switch msg.Type { case tea.KeyEscape: x.mode = modeScroll + x.results.Pop() return x, nil case tea.KeyRunes: switch msg.String() { case "n", "N": x.mode = modeScroll + x.results.Pop() return x, nil case "y", "Y": @@ -124,7 +129,7 @@ func (x Model) updateKey(msg tea.KeyMsg) (Model, tea.Cmd) { } } - x.quit = x.quit.Update(msg) + x.results.Top().Scroll(msg) return x, nil default: @@ -135,7 +140,7 @@ func (x Model) updateKey(msg tea.KeyMsg) (Model, tea.Cmd) { func (x Model) updateMouse(msg tea.MouseMsg) (Model, tea.Cmd) { var cmd tea.Cmd x.input, cmd = x.input.Update(msg) - x.results = x.results.Update(msg) + x.results.Top().Scroll(msg) return x, cmd } @@ -144,12 +149,12 @@ func (x Model) updateAsyncQuery(msg manager.AsyncQueryMsg) (Model, tea.Cmd) { return x, nil } if x.latest.Before(msg.StartedAt) { - x.results.Reset() + x.results.Top().Reset() x.latest = msg.StartedAt } buf, done := msg.Buffer.Get() - x.results.PushMany(buf) + x.results.Top().PushMany(buf) if !done { return x, tea.Tick(50*time.Millisecond, func(_ time.Time) tea.Msg { return msg @@ -159,8 +164,8 @@ func (x Model) updateAsyncQuery(msg manager.AsyncQueryMsg) (Model, tea.Cmd) { } func (x Model) updateSingle(msg any) (Model, tea.Cmd) { - x.results.Reset() - x.results.Push(msg) + x.results.Top().Reset() + x.results.Top().Push(msg) return x, nil } @@ -169,25 +174,15 @@ func (x Model) updateSize(msg tea.WindowSizeMsg) Model { const cursorChar = 1 inputHeight := x.style.input.GetVerticalFrameSize() + inputLine - // TODO: Clean up calls to GetXXXFrameSize(). x.style.results.Height(msg.Height - x.style.results.GetVerticalFrameSize() - inputHeight) x.style.results.Width(msg.Width - x.style.results.GetHorizontalFrameSize()) + x.results.Height(x.style.results.GetHeight() - x.style.results.GetVerticalFrameSize()) x.results.WrapWidth(x.style.results.GetWidth() - x.style.results.GetHorizontalFrameSize()) x.input.Width = msg.Width - x.style.input.GetHorizontalFrameSize() - len(x.input.Prompt) - cursorChar - 2 x.style.input.Width(msg.Width - x.style.input.GetHorizontalFrameSize()) - x.help.Height(x.style.results.GetHeight() - x.style.results.GetVerticalFrameSize()) - helpWidth := x.style.results.GetWidth() - x.style.results.GetHorizontalFrameSize() - if helpWidth > 80 { - helpWidth = 65 - } - x.help.WrapWidth(helpWidth) - - x.quit.Height(x.style.results.GetHeight() - x.style.results.GetVerticalFrameSize()) - x.quit.WrapWidth(x.style.results.GetWidth() - x.style.results.GetHorizontalFrameSize()) - return x } diff --git a/internal/app/fullscreen/view.go b/internal/app/fullscreen/view.go index e4d2223b..ad8fbc1b 100644 --- a/internal/app/fullscreen/view.go +++ b/internal/app/fullscreen/view.go @@ -2,27 +2,10 @@ package fullscreen import ( lip "github.com/charmbracelet/lipgloss" - "github.com/pkg/errors" ) func (x Model) View() string { - switch x.mode { - case modeHelp: - return lip.JoinVertical(lip.Left, - x.style.results.Render(x.help.View()), - x.style.input.Render(x.input.View())) - - case modeInput, modeScroll: - return lip.JoinVertical(lip.Left, - x.style.results.Render(x.results.View()), - x.style.input.Render(x.input.View())) - - case modeQuit: - return lip.JoinVertical(lip.Left, - x.style.results.Render(x.quit.View()), - x.style.input.Render(x.input.View())) - - default: - panic(errors.Errorf("unexpected mode '%v'", x.mode)) - } + return lip.JoinVertical(lip.Left, + x.style.results.Render(x.results.Top().View()), + x.style.input.Render(x.input.View())) }