diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index b0d957a..ef41c27 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -126,9 +126,7 @@ var ( PaddingLeft(1). PaddingRight(1). Background(lipgloss.Color("0")) - footerStyle = lipgloss.NewStyle(). - BorderTop(true). - BorderStyle(lipgloss.NormalBorder()) + footerStyle = lipgloss.NewStyle() ) func (m model) Init() tea.Cmd { @@ -206,7 +204,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.width = msg.Width m.height = msg.Height m.content.Width = msg.Width - m.input.SetWidth(msg.Width) + m.input.SetWidth(msg.Width-m.input.FocusedStyle.Base.GetHorizontalBorderSize()) m.rebuildMessageCache() m.updateContent() case msgConversationLoaded: @@ -295,6 +293,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } + + prevInputLineCnt := m.input.LineCount() inputCaptured := false m.input, cmd = m.input.Update(msg) if cmd != nil { @@ -313,12 +313,42 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.cache.header = m.headerView() m.cache.footer = m.footerView() m.cache.error = m.errorView() + + fixedHeight := height(m.cache.header) + height(m.cache.error) + height(m.cache.footer) + + // calculate clamped input height to accomodate input text + newHeight := max(4, min((m.height-fixedHeight-1)/2, m.input.LineCount())) + m.input.SetHeight(newHeight) m.cache.input = m.inputView() - fixedHeight := height(m.cache.header) + height(m.cache.error) + height(m.cache.input) + height(m.cache.footer) - m.content.Height = m.height - fixedHeight + + m.content.Height = m.height - height(m.cache.input) - fixedHeight m.cache.content = m.contentView() } + // this is a pretty nasty hack to ensure the input area viewport doesn't + // scroll below its content, which can happen when the input viewport + // height has grown, or previously entered lines have been deleted + if prevInputLineCnt != m.input.LineCount() { + // dist is the distance we'd need to scroll up from the current cursor + // position to position the last input line at the bottom of the + // viewport. if negative, we're already scrolled above the bottom + dist := m.input.Line() - (m.input.LineCount() - m.input.Height()) + if dist > 0 { + for i := 0; i < dist; i++ { + // move cursor up until content reaches the bottom of the viewport + m.input.CursorUp() + } + m.input, cmd = m.input.Update(nil) + cmds = append(cmds, cmd) + for i := 0; i < dist; i++ { + // move cursor back down to its previous position + m.input.CursorDown() + } + m.input, cmd = m.input.Update(nil) + cmds = append(cmds, cmd) + } + } + return m, tea.Batch(cmds...) } @@ -468,16 +498,13 @@ func initialModel(ctx *lmcli.Context, convShortname string) model { m.input.Placeholder = "Enter a message" m.input.ShowLineNumbers = false - m.input.SetHeight(4) m.input.Focus() m.input.FocusedStyle.CursorLine = lipgloss.NewStyle() m.input.FocusedStyle.Base = lipgloss.NewStyle(). - BorderTop(true). - BorderStyle(lipgloss.NormalBorder()) + Border(lipgloss.RoundedBorder(), true, true, true, false) m.input.BlurredStyle.Base = lipgloss.NewStyle(). Faint(true). - BorderTop(true). - BorderStyle(lipgloss.NormalBorder()) + Border(lipgloss.RoundedBorder(), true, true, true, false) m.spinner = spinner.New(spinner.WithSpinner( spinner.Spinner{