mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-22 10:14:22 +01:00
fix(tui): layout issues
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/charmbracelet/lipgloss/v2"
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
"github.com/sst/opencode-sdk-go"
|
"github.com/sst/opencode-sdk-go"
|
||||||
"github.com/sst/opencode/internal/app"
|
"github.com/sst/opencode/internal/app"
|
||||||
"github.com/sst/opencode/internal/components/commands"
|
|
||||||
"github.com/sst/opencode/internal/components/dialog"
|
"github.com/sst/opencode/internal/components/dialog"
|
||||||
"github.com/sst/opencode/internal/layout"
|
"github.com/sst/opencode/internal/layout"
|
||||||
"github.com/sst/opencode/internal/styles"
|
"github.com/sst/opencode/internal/styles"
|
||||||
@@ -39,7 +38,6 @@ type messagesComponent struct {
|
|||||||
viewport viewport.Model
|
viewport viewport.Model
|
||||||
spinner spinner.Model
|
spinner spinner.Model
|
||||||
attachments viewport.Model
|
attachments viewport.Model
|
||||||
commands commands.CommandsComponent
|
|
||||||
cache *MessageCache
|
cache *MessageCache
|
||||||
rendering bool
|
rendering bool
|
||||||
showToolDetails bool
|
showToolDetails bool
|
||||||
@@ -49,7 +47,7 @@ type renderFinishedMsg struct{}
|
|||||||
type ToggleToolDetailsMsg struct{}
|
type ToggleToolDetailsMsg struct{}
|
||||||
|
|
||||||
func (m *messagesComponent) Init() tea.Cmd {
|
func (m *messagesComponent) Init() tea.Cmd {
|
||||||
return tea.Batch(m.viewport.Init(), m.spinner.Tick, m.commands.Init())
|
return tea.Batch(m.viewport.Init(), m.spinner.Tick)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
@@ -100,10 +98,6 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.spinner = spinner
|
m.spinner = spinner
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
updated, cmd := m.commands.Update(msg)
|
|
||||||
m.commands = updated.(commands.CommandsComponent)
|
|
||||||
cmds = append(cmds, cmd)
|
|
||||||
|
|
||||||
return m, tea.Batch(cmds...)
|
return m, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,8 +265,8 @@ func (m *messagesComponent) renderView() {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
m.viewport.SetHeight(m.height - lipgloss.Height(m.header()))
|
m.viewport.SetHeight(m.height - lipgloss.Height(m.header()) + 1)
|
||||||
m.viewport.SetContent("\n" + strings.Join(centered, "\n") + "\n")
|
m.viewport.SetContent("\n" + strings.Join(centered, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesComponent) header() string {
|
func (m *messagesComponent) header() string {
|
||||||
@@ -309,9 +303,6 @@ func (m *messagesComponent) header() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesComponent) View() string {
|
func (m *messagesComponent) View() string {
|
||||||
if len(m.app.Messages) == 0 {
|
|
||||||
return m.home()
|
|
||||||
}
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
if m.rendering {
|
if m.rendering {
|
||||||
return lipgloss.Place(
|
return lipgloss.Place(
|
||||||
@@ -334,70 +325,6 @@ func (m *messagesComponent) View() string {
|
|||||||
Render(header + "\n" + m.viewport.View())
|
Render(header + "\n" + m.viewport.View())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesComponent) home() string {
|
|
||||||
t := theme.CurrentTheme()
|
|
||||||
baseStyle := styles.NewStyle().Background(t.Background())
|
|
||||||
base := baseStyle.Render
|
|
||||||
muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render
|
|
||||||
|
|
||||||
open := `
|
|
||||||
█▀▀█ █▀▀█ █▀▀ █▀▀▄
|
|
||||||
█░░█ █░░█ █▀▀ █░░█
|
|
||||||
▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ `
|
|
||||||
code := `
|
|
||||||
█▀▀ █▀▀█ █▀▀▄ █▀▀
|
|
||||||
█░░ █░░█ █░░█ █▀▀
|
|
||||||
▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀`
|
|
||||||
|
|
||||||
logo := lipgloss.JoinHorizontal(
|
|
||||||
lipgloss.Top,
|
|
||||||
muted(open),
|
|
||||||
base(code),
|
|
||||||
)
|
|
||||||
// cwd := app.Info.Path.Cwd
|
|
||||||
// config := app.Info.Path.Config
|
|
||||||
|
|
||||||
versionStyle := styles.NewStyle().
|
|
||||||
Foreground(t.TextMuted()).
|
|
||||||
Background(t.Background()).
|
|
||||||
Width(lipgloss.Width(logo)).
|
|
||||||
Align(lipgloss.Right)
|
|
||||||
version := versionStyle.Render(m.app.Version)
|
|
||||||
|
|
||||||
logoAndVersion := strings.Join([]string{logo, version}, "\n")
|
|
||||||
logoAndVersion = lipgloss.PlaceHorizontal(
|
|
||||||
m.width,
|
|
||||||
lipgloss.Center,
|
|
||||||
logoAndVersion,
|
|
||||||
styles.WhitespaceStyle(t.Background()),
|
|
||||||
)
|
|
||||||
m.commands.SetBackgroundColor(t.Background())
|
|
||||||
commands := lipgloss.PlaceHorizontal(
|
|
||||||
m.width,
|
|
||||||
lipgloss.Center,
|
|
||||||
m.commands.View(),
|
|
||||||
styles.WhitespaceStyle(t.Background()),
|
|
||||||
)
|
|
||||||
|
|
||||||
lines := []string{}
|
|
||||||
lines = append(lines, logoAndVersion)
|
|
||||||
lines = append(lines, "")
|
|
||||||
lines = append(lines, "")
|
|
||||||
// lines = append(lines, base("cwd ")+muted(cwd))
|
|
||||||
// lines = append(lines, base("config ")+muted(config))
|
|
||||||
// lines = append(lines, "")
|
|
||||||
lines = append(lines, commands)
|
|
||||||
|
|
||||||
return lipgloss.Place(
|
|
||||||
m.width,
|
|
||||||
m.height,
|
|
||||||
lipgloss.Center,
|
|
||||||
lipgloss.Center,
|
|
||||||
baseStyle.Render(strings.Join(lines, "\n")),
|
|
||||||
styles.WhitespaceStyle(t.Background()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *messagesComponent) SetSize(width, height int) tea.Cmd {
|
func (m *messagesComponent) SetSize(width, height int) tea.Cmd {
|
||||||
if m.width == width && m.height == height {
|
if m.width == width && m.height == height {
|
||||||
return nil
|
return nil
|
||||||
@@ -412,7 +339,6 @@ func (m *messagesComponent) SetSize(width, height int) tea.Cmd {
|
|||||||
m.viewport.SetHeight(height - lipgloss.Height(m.header()))
|
m.viewport.SetHeight(height - lipgloss.Height(m.header()))
|
||||||
m.attachments.SetWidth(width + 40)
|
m.attachments.SetWidth(width + 40)
|
||||||
m.attachments.SetHeight(3)
|
m.attachments.SetHeight(3)
|
||||||
m.commands.SetSize(width, height)
|
|
||||||
m.renderView()
|
m.renderView()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -476,19 +402,11 @@ func NewMessagesComponent(app *app.App) MessagesComponent {
|
|||||||
attachments := viewport.New()
|
attachments := viewport.New()
|
||||||
vp.KeyMap = viewport.KeyMap{}
|
vp.KeyMap = viewport.KeyMap{}
|
||||||
|
|
||||||
t := theme.CurrentTheme()
|
|
||||||
commandsView := commands.New(
|
|
||||||
app,
|
|
||||||
commands.WithBackground(t.Background()),
|
|
||||||
commands.WithLimit(6),
|
|
||||||
)
|
|
||||||
|
|
||||||
return &messagesComponent{
|
return &messagesComponent{
|
||||||
app: app,
|
app: app,
|
||||||
viewport: vp,
|
viewport: vp,
|
||||||
spinner: s,
|
spinner: s,
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
commands: commandsView,
|
|
||||||
showToolDetails: true,
|
showToolDetails: true,
|
||||||
cache: NewMessageCache(),
|
cache: NewMessageCache(),
|
||||||
tail: true,
|
tail: true,
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type CommandsComponent interface {
|
type CommandsComponent interface {
|
||||||
tea.Model
|
|
||||||
tea.ViewModel
|
tea.ViewModel
|
||||||
SetSize(width, height int) tea.Cmd
|
SetSize(width, height int) tea.Cmd
|
||||||
SetBackgroundColor(color compat.AdaptiveColor)
|
SetBackgroundColor(color compat.AdaptiveColor)
|
||||||
@@ -43,19 +42,6 @@ func (c *commandsComponent) SetBackgroundColor(color compat.AdaptiveColor) {
|
|||||||
c.background = &color
|
c.background = &color
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *commandsComponent) Init() tea.Cmd {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *commandsComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.WindowSizeMsg:
|
|
||||||
c.width = msg.Width
|
|
||||||
c.height = msg.Height
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *commandsComponent) View() string {
|
func (c *commandsComponent) View() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ type helpDialog struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *helpDialog) Init() tea.Cmd {
|
func (h *helpDialog) Init() tea.Cmd {
|
||||||
return tea.Batch(
|
return h.viewport.Init()
|
||||||
h.commandsComponent.Init(),
|
|
||||||
h.viewport.Init(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *helpDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (h *helpDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
@@ -38,10 +35,6 @@ func (h *helpDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
h.commandsComponent.SetSize(msg.Width-4, msg.Height-6)
|
h.commandsComponent.SetSize(msg.Width-4, msg.Height-6)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update commands component first to get the latest content
|
|
||||||
_, cmdCmd := h.commandsComponent.Update(msg)
|
|
||||||
cmds = append(cmds, cmdCmd)
|
|
||||||
|
|
||||||
// Update viewport content
|
// Update viewport content
|
||||||
h.viewport.SetContent(h.commandsComponent.View())
|
h.viewport.SetContent(h.commandsComponent.View())
|
||||||
|
|
||||||
|
|||||||
@@ -73,10 +73,7 @@ func Render(opts FlexOptions, items ...FlexItem) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate available space for grow items
|
// Calculate available space for grow items
|
||||||
availableSpace := mainAxisSize - totalFixedSize
|
availableSpace := max(mainAxisSize-totalFixedSize, 0)
|
||||||
if availableSpace < 0 {
|
|
||||||
availableSpace = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate size for each grow item
|
// Calculate size for each grow item
|
||||||
growItemSize := 0
|
growItemSize := 0
|
||||||
@@ -164,10 +161,7 @@ func Render(opts FlexOptions, items ...FlexItem) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply justification
|
// Apply justification
|
||||||
remainingSpace := mainAxisSize - totalActualSize
|
remainingSpace := max(mainAxisSize-totalActualSize, 0)
|
||||||
if remainingSpace < 0 {
|
|
||||||
remainingSpace = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate spacing based on justification
|
// Calculate spacing based on justification
|
||||||
var spaceBefore, spaceBetween, spaceAfter int
|
var spaceBefore, spaceBetween, spaceAfter int
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/sst/opencode/internal/commands"
|
"github.com/sst/opencode/internal/commands"
|
||||||
"github.com/sst/opencode/internal/completions"
|
"github.com/sst/opencode/internal/completions"
|
||||||
"github.com/sst/opencode/internal/components/chat"
|
"github.com/sst/opencode/internal/components/chat"
|
||||||
|
cmdcomp "github.com/sst/opencode/internal/components/commands"
|
||||||
"github.com/sst/opencode/internal/components/dialog"
|
"github.com/sst/opencode/internal/components/dialog"
|
||||||
"github.com/sst/opencode/internal/components/modal"
|
"github.com/sst/opencode/internal/components/modal"
|
||||||
"github.com/sst/opencode/internal/components/status"
|
"github.com/sst/opencode/internal/components/status"
|
||||||
@@ -425,13 +426,13 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a appModel) View() string {
|
func (a appModel) View() string {
|
||||||
messagesView := a.messages.View()
|
|
||||||
editorView := a.editor.View()
|
editorView := a.editor.View()
|
||||||
|
lines := a.editor.Lines()
|
||||||
editorHeight := lipgloss.Height(editorView)
|
messagesView := a.messages.View()
|
||||||
if editorHeight < 5 {
|
if a.app.Session.ID == "" {
|
||||||
editorHeight = 5
|
messagesView = a.home()
|
||||||
}
|
}
|
||||||
|
editorHeight := max(lines, 5)
|
||||||
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
centeredEditorView := lipgloss.PlaceHorizontal(
|
centeredEditorView := lipgloss.PlaceHorizontal(
|
||||||
@@ -445,7 +446,7 @@ func (a appModel) View() string {
|
|||||||
layout.FlexOptions{
|
layout.FlexOptions{
|
||||||
Direction: layout.Column,
|
Direction: layout.Column,
|
||||||
Width: a.width,
|
Width: a.width,
|
||||||
Height: a.height - 1, // Leave room for status bar
|
Height: a.height,
|
||||||
},
|
},
|
||||||
layout.FlexItem{
|
layout.FlexItem{
|
||||||
View: messagesView,
|
View: messagesView,
|
||||||
@@ -453,15 +454,18 @@ func (a appModel) View() string {
|
|||||||
},
|
},
|
||||||
layout.FlexItem{
|
layout.FlexItem{
|
||||||
View: centeredEditorView,
|
View: centeredEditorView,
|
||||||
FixedSize: editorHeight,
|
FixedSize: 5,
|
||||||
},
|
},
|
||||||
|
// layout.FlexItem{
|
||||||
|
// View: a.status.View(),
|
||||||
|
// FixedSize: 1,
|
||||||
|
// },
|
||||||
)
|
)
|
||||||
|
|
||||||
if a.editor.Lines() > 1 {
|
if lines > 1 {
|
||||||
editorWidth := min(a.width, 80)
|
editorWidth := min(a.width, 80)
|
||||||
editorX := (a.width - editorWidth) / 2
|
editorX := (a.width - editorWidth) / 2
|
||||||
editorY := a.height - editorHeight - 1 // Position from bottom, accounting for status bar
|
editorY := a.height - editorHeight
|
||||||
|
|
||||||
mainLayout = layout.PlaceOverlay(
|
mainLayout = layout.PlaceOverlay(
|
||||||
editorX,
|
editorX,
|
||||||
editorY,
|
editorY,
|
||||||
@@ -476,7 +480,7 @@ func (a appModel) View() string {
|
|||||||
a.completions.SetWidth(editorWidth)
|
a.completions.SetWidth(editorWidth)
|
||||||
overlay := a.completions.View()
|
overlay := a.completions.View()
|
||||||
overlayHeight := lipgloss.Height(overlay)
|
overlayHeight := lipgloss.Height(overlay)
|
||||||
editorY := a.height - editorHeight - 1
|
editorY := a.height - editorHeight + 1
|
||||||
|
|
||||||
mainLayout = layout.PlaceOverlay(
|
mainLayout = layout.PlaceOverlay(
|
||||||
editorX,
|
editorX,
|
||||||
@@ -486,23 +490,82 @@ func (a appModel) View() string {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
components := []string{
|
|
||||||
mainLayout,
|
|
||||||
a.status.View(),
|
|
||||||
}
|
|
||||||
appView := strings.Join(components, "\n")
|
|
||||||
|
|
||||||
if a.modal != nil {
|
if a.modal != nil {
|
||||||
appView = a.modal.Render(appView)
|
mainLayout = a.modal.Render(mainLayout)
|
||||||
}
|
}
|
||||||
|
mainLayout = a.toastManager.RenderOverlay(mainLayout)
|
||||||
appView = a.toastManager.RenderOverlay(appView)
|
|
||||||
|
|
||||||
if theme.CurrentThemeUsesAnsiColors() {
|
if theme.CurrentThemeUsesAnsiColors() {
|
||||||
appView = util.ConvertRGBToAnsi16Colors(appView)
|
mainLayout = util.ConvertRGBToAnsi16Colors(mainLayout)
|
||||||
}
|
}
|
||||||
|
return mainLayout + "\n" + a.status.View()
|
||||||
|
}
|
||||||
|
|
||||||
return appView
|
func (a appModel) home() string {
|
||||||
|
t := theme.CurrentTheme()
|
||||||
|
baseStyle := styles.NewStyle().Background(t.Background())
|
||||||
|
base := baseStyle.Render
|
||||||
|
muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render
|
||||||
|
|
||||||
|
open := `
|
||||||
|
█▀▀█ █▀▀█ █▀▀ █▀▀▄
|
||||||
|
█░░█ █░░█ █▀▀ █░░█
|
||||||
|
▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ `
|
||||||
|
code := `
|
||||||
|
█▀▀ █▀▀█ █▀▀▄ █▀▀
|
||||||
|
█░░ █░░█ █░░█ █▀▀
|
||||||
|
▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀`
|
||||||
|
|
||||||
|
logo := lipgloss.JoinHorizontal(
|
||||||
|
lipgloss.Top,
|
||||||
|
muted(open),
|
||||||
|
base(code),
|
||||||
|
)
|
||||||
|
// cwd := app.Info.Path.Cwd
|
||||||
|
// config := app.Info.Path.Config
|
||||||
|
|
||||||
|
versionStyle := styles.NewStyle().
|
||||||
|
Foreground(t.TextMuted()).
|
||||||
|
Background(t.Background()).
|
||||||
|
Width(lipgloss.Width(logo)).
|
||||||
|
Align(lipgloss.Right)
|
||||||
|
version := versionStyle.Render(a.app.Version)
|
||||||
|
|
||||||
|
logoAndVersion := strings.Join([]string{logo, version}, "\n")
|
||||||
|
logoAndVersion = lipgloss.PlaceHorizontal(
|
||||||
|
a.width,
|
||||||
|
lipgloss.Center,
|
||||||
|
logoAndVersion,
|
||||||
|
styles.WhitespaceStyle(t.Background()),
|
||||||
|
)
|
||||||
|
commandsView := cmdcomp.New(
|
||||||
|
a.app,
|
||||||
|
cmdcomp.WithBackground(t.Background()),
|
||||||
|
cmdcomp.WithLimit(6),
|
||||||
|
)
|
||||||
|
cmds := lipgloss.PlaceHorizontal(
|
||||||
|
a.width,
|
||||||
|
lipgloss.Center,
|
||||||
|
commandsView.View(),
|
||||||
|
styles.WhitespaceStyle(t.Background()),
|
||||||
|
)
|
||||||
|
|
||||||
|
lines := []string{}
|
||||||
|
lines = append(lines, logoAndVersion)
|
||||||
|
lines = append(lines, "")
|
||||||
|
lines = append(lines, "")
|
||||||
|
// lines = append(lines, base("cwd ")+muted(cwd))
|
||||||
|
// lines = append(lines, base("config ")+muted(config))
|
||||||
|
// lines = append(lines, "")
|
||||||
|
lines = append(lines, cmds)
|
||||||
|
|
||||||
|
return lipgloss.Place(
|
||||||
|
a.width,
|
||||||
|
a.height-5,
|
||||||
|
lipgloss.Center,
|
||||||
|
lipgloss.Center,
|
||||||
|
baseStyle.Render(strings.Join(lines, "\n")),
|
||||||
|
styles.WhitespaceStyle(t.Background()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
|
func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
|
||||||
|
|||||||
Reference in New Issue
Block a user