Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 48 additions & 44 deletions examples/chat/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,29 @@ package main

import (
"fmt"
"os"
"log"
"strings"

"charm.land/bubbles/v2/cursor"
"charm.land/bubbles/v2/textarea"
"charm.land/bubbles/v2/viewport"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

const gap = "\n\n"

func main() {
p := tea.NewProgram(initialModel())

if _, err := p.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Oof: %v\n", err)
log.Fatal(err)
}
}

type (
errMsg error
)

type model struct {
viewport viewport.Model
messages []string
Expand All @@ -33,7 +39,6 @@ type model struct {
func initialModel() model {
ta := textarea.New()
ta.Placeholder = "Send a message..."
ta.SetVirtualCursor(false)
ta.Focus()

ta.Prompt = "┃ "
Expand All @@ -43,17 +48,13 @@ func initialModel() model {
ta.SetHeight(3)

// Remove cursor line styling
s := ta.Styles()
s.Focused.CursorLine = lipgloss.NewStyle()
ta.SetStyles(s)
ta.FocusedStyle.CursorLine = lipgloss.NewStyle()

ta.ShowLineNumbers = false

vp := viewport.New(viewport.WithWidth(30), viewport.WithHeight(5))
vp := viewport.New(30, 5)
vp.SetContent(`Welcome to the chat room!
Type a message and press Enter to send.`)
vp.KeyMap.Left.SetEnabled(false)
vp.KeyMap.Right.SetEnabled(false)

ta.KeyMap.InsertNewline.SetEnabled(false)

Expand All @@ -71,53 +72,56 @@ func (m model) Init() tea.Cmd {
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
tiCmd tea.Cmd
vpCmd tea.Cmd
)

switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.viewport.SetWidth(msg.Width)
m.viewport.Width = msg.Width
m.textarea.SetWidth(msg.Width)
m.viewport.SetHeight(msg.Height - m.textarea.Height())
m.viewport.Height = msg.Height - m.textarea.Height() - lipgloss.Height(gap)

if len(m.messages) > 0 {
// Wrap content before setting it.
m.viewport.SetContent(lipgloss.NewStyle().Width(m.viewport.Width()).Render(strings.Join(m.messages, "\n")))
m.viewport.SetContent(lipgloss.NewStyle().Width(m.viewport.Width).Render(strings.Join(m.messages, "\n")))
}
m.viewport.GotoBottom()
case tea.KeyPressMsg:
switch msg.String() {
case "ctrl+c", "esc":
m.textarea, tiCmd = m.textarea.Update(msg)
m.viewport, vpCmd = m.viewport.Update(msg)
case tea.KeyMsg:
m.textarea, tiCmd = m.textarea.Update(msg)
m.viewport, vpCmd = m.viewport.Update(msg)
switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc:
fmt.Println(m.textarea.Value())
return m, tea.Quit
case "enter":
case tea.KeyEnter:
m.messages = append(m.messages, m.senderStyle.Render("You: ")+m.textarea.Value())
m.viewport.SetContent(lipgloss.NewStyle().Width(m.viewport.Width()).Render(strings.Join(m.messages, "\n")))
m.viewport.SetContent(lipgloss.NewStyle().Width(m.viewport.Width).Render(strings.Join(m.messages, "\n")))
m.textarea.Reset()
m.viewport.GotoBottom()
return m, nil
default:
// Send all other keypresses to the textarea.
var cmd tea.Cmd
m.textarea, cmd = m.textarea.Update(msg)
return m, cmd
}

case cursor.BlinkMsg:
// Textarea should also process cursor blinks.
var cmd tea.Cmd
m.textarea, cmd = m.textarea.Update(msg)
return m, cmd
// We handle errors just like any other message
case errMsg:
m.err = msg
return m, nil

default:
m.textarea, tiCmd = m.textarea.Update(msg)
m.viewport, vpCmd = m.viewport.Update(msg)
}

return m, nil
return m, tea.Batch(tiCmd, vpCmd)
}

func (m model) View() tea.View {
viewportView := m.viewport.View()
v := tea.NewView(viewportView + "\n" + m.textarea.View())
c := m.textarea.Cursor()
if c != nil {
c.Y += lipgloss.Height(viewportView)
}
v.Cursor = c
v.AltScreen = true
return v
func (m model) View() string {
return fmt.Sprintf(
"%s%s%s",
m.viewport.View(),
gap,
m.textarea.View(),
)
}