4 "github.com/charmbracelet/bubbles/key"
5 tea "github.com/charmbracelet/bubbletea"
6 "github.com/charmbracelet/lipgloss"
7 "github.com/opencode-ai/opencode/internal/tui/theme"
10type SplitPaneLayout interface {
14 SetLeftPanel(panel Container) tea.Cmd
15 SetRightPanel(panel Container) tea.Cmd
16 SetBottomPanel(panel Container) tea.Cmd
18 ClearLeftPanel() tea.Cmd
19 ClearRightPanel() tea.Cmd
20 ClearBottomPanel() tea.Cmd
23type splitPaneLayout struct {
34type SplitPaneOption func(*splitPaneLayout)
36func (s *splitPaneLayout) Init() tea.Cmd {
39 if s.leftPanel != nil {
40 cmds = append(cmds, s.leftPanel.Init())
43 if s.rightPanel != nil {
44 cmds = append(cmds, s.rightPanel.Init())
47 if s.bottomPanel != nil {
48 cmds = append(cmds, s.bottomPanel.Init())
51 return tea.Batch(cmds...)
54func (s *splitPaneLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
56 switch msg := msg.(type) {
57 case tea.WindowSizeMsg:
58 return s, s.SetSize(msg.Width, msg.Height)
61 if s.rightPanel != nil {
62 u, cmd := s.rightPanel.Update(msg)
63 s.rightPanel = u.(Container)
65 cmds = append(cmds, cmd)
69 if s.leftPanel != nil {
70 u, cmd := s.leftPanel.Update(msg)
71 s.leftPanel = u.(Container)
73 cmds = append(cmds, cmd)
77 if s.bottomPanel != nil {
78 u, cmd := s.bottomPanel.Update(msg)
79 s.bottomPanel = u.(Container)
81 cmds = append(cmds, cmd)
85 return s, tea.Batch(cmds...)
88func (s *splitPaneLayout) View() string {
91 if s.leftPanel != nil && s.rightPanel != nil {
92 leftView := s.leftPanel.View()
93 rightView := s.rightPanel.View()
94 topSection = lipgloss.JoinHorizontal(lipgloss.Top, leftView, rightView)
95 } else if s.leftPanel != nil {
96 topSection = s.leftPanel.View()
97 } else if s.rightPanel != nil {
98 topSection = s.rightPanel.View()
105 if s.bottomPanel != nil && topSection != "" {
106 bottomView := s.bottomPanel.View()
107 finalView = lipgloss.JoinVertical(lipgloss.Left, topSection, bottomView)
108 } else if s.bottomPanel != nil {
109 finalView = s.bottomPanel.View()
111 finalView = topSection
115 t := theme.CurrentTheme()
117 style := lipgloss.NewStyle().
120 Background(t.Background())
122 return style.Render(finalView)
128func (s *splitPaneLayout) SetSize(width, height int) tea.Cmd {
132 var topHeight, bottomHeight int
133 if s.bottomPanel != nil {
134 topHeight = int(float64(height) * s.verticalRatio)
135 bottomHeight = height - topHeight
141 var leftWidth, rightWidth int
142 if s.leftPanel != nil && s.rightPanel != nil {
143 leftWidth = int(float64(width) * s.ratio)
144 rightWidth = width - leftWidth
145 } else if s.leftPanel != nil {
148 } else if s.rightPanel != nil {
154 if s.leftPanel != nil {
155 cmd := s.leftPanel.SetSize(leftWidth, topHeight)
156 cmds = append(cmds, cmd)
159 if s.rightPanel != nil {
160 cmd := s.rightPanel.SetSize(rightWidth, topHeight)
161 cmds = append(cmds, cmd)
164 if s.bottomPanel != nil {
165 cmd := s.bottomPanel.SetSize(width, bottomHeight)
166 cmds = append(cmds, cmd)
168 return tea.Batch(cmds...)
171func (s *splitPaneLayout) GetSize() (int, int) {
172 return s.width, s.height
175func (s *splitPaneLayout) SetLeftPanel(panel Container) tea.Cmd {
177 if s.width > 0 && s.height > 0 {
178 return s.SetSize(s.width, s.height)
183func (s *splitPaneLayout) SetRightPanel(panel Container) tea.Cmd {
185 if s.width > 0 && s.height > 0 {
186 return s.SetSize(s.width, s.height)
191func (s *splitPaneLayout) SetBottomPanel(panel Container) tea.Cmd {
192 s.bottomPanel = panel
193 if s.width > 0 && s.height > 0 {
194 return s.SetSize(s.width, s.height)
199func (s *splitPaneLayout) ClearLeftPanel() tea.Cmd {
201 if s.width > 0 && s.height > 0 {
202 return s.SetSize(s.width, s.height)
207func (s *splitPaneLayout) ClearRightPanel() tea.Cmd {
209 if s.width > 0 && s.height > 0 {
210 return s.SetSize(s.width, s.height)
215func (s *splitPaneLayout) ClearBottomPanel() tea.Cmd {
217 if s.width > 0 && s.height > 0 {
218 return s.SetSize(s.width, s.height)
223func (s *splitPaneLayout) BindingKeys() []key.Binding {
224 keys := []key.Binding{}
225 if s.leftPanel != nil {
226 if b, ok := s.leftPanel.(Bindings); ok {
227 keys = append(keys, b.BindingKeys()...)
230 if s.rightPanel != nil {
231 if b, ok := s.rightPanel.(Bindings); ok {
232 keys = append(keys, b.BindingKeys()...)
235 if s.bottomPanel != nil {
236 if b, ok := s.bottomPanel.(Bindings); ok {
237 keys = append(keys, b.BindingKeys()...)
243func NewSplitPane(options ...SplitPaneOption) SplitPaneLayout {
245 layout := &splitPaneLayout{
247 verticalRatio: 0.9, // Default 90% for top section, 10% for bottom
249 for _, option := range options {
255func WithLeftPanel(panel Container) SplitPaneOption {
256 return func(s *splitPaneLayout) {
261func WithRightPanel(panel Container) SplitPaneOption {
262 return func(s *splitPaneLayout) {
267func WithRatio(ratio float64) SplitPaneOption {
268 return func(s *splitPaneLayout) {
273func WithBottomPanel(panel Container) SplitPaneOption {
274 return func(s *splitPaneLayout) {
275 s.bottomPanel = panel
279func WithVerticalRatio(ratio float64) SplitPaneOption {
280 return func(s *splitPaneLayout) {
281 s.verticalRatio = ratio
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list