|
|
|
@ -6,10 +6,11 @@ import (
@@ -6,10 +6,11 @@ import (
|
|
|
|
|
"io/ioutil" |
|
|
|
|
"log" |
|
|
|
|
"os" |
|
|
|
|
"strings" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tea "github.com/charmbracelet/bubbletea" |
|
|
|
|
"github.com/charmbracelet/bubbles/spinner" |
|
|
|
|
"github.com/charmbracelet/bubbles/viewport" |
|
|
|
|
tea "github.com/charmbracelet/bubbletea" |
|
|
|
|
"github.com/charmbracelet/lipgloss" |
|
|
|
|
"github.com/mattn/go-isatty" |
|
|
|
|
"github.com/muesli/reflow/indent" |
|
|
|
@ -20,10 +21,38 @@ import (
@@ -20,10 +21,38 @@ import (
|
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render |
|
|
|
|
titleStyle = func() lipgloss.Style { |
|
|
|
|
b := lipgloss.RoundedBorder() |
|
|
|
|
b.Right = "├" |
|
|
|
|
return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) |
|
|
|
|
}() |
|
|
|
|
infoStyle = func() lipgloss.Style { |
|
|
|
|
b := lipgloss.RoundedBorder() |
|
|
|
|
b.Left = "┤" |
|
|
|
|
return titleStyle.Copy().BorderStyle(b) |
|
|
|
|
}() |
|
|
|
|
k = keybase.NewKeybase() |
|
|
|
|
mainModel *model |
|
|
|
|
useHighPerformanceRenderer = false |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
func (m model) headerView() string { |
|
|
|
|
title := titleStyle.Render("convo-name") |
|
|
|
|
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title))) |
|
|
|
|
return lipgloss.JoinHorizontal(lipgloss.Center, title, line) |
|
|
|
|
} |
|
|
|
|
func (m model) footerView() string { |
|
|
|
|
info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)) |
|
|
|
|
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info))) |
|
|
|
|
return lipgloss.JoinHorizontal(lipgloss.Center, line, info) |
|
|
|
|
} |
|
|
|
|
func max(a, b int) int { |
|
|
|
|
if a > b { |
|
|
|
|
return a |
|
|
|
|
} |
|
|
|
|
return b |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func main() { |
|
|
|
|
var ( |
|
|
|
|
daemonMode bool |
|
|
|
@ -78,6 +107,11 @@ func (m model) Init() tea.Cmd {
@@ -78,6 +107,11 @@ func (m model) Init() tea.Cmd {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { |
|
|
|
|
var ( |
|
|
|
|
cmd tea.Cmd |
|
|
|
|
cmds []tea.Cmd |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
switch msg := msg.(type) { |
|
|
|
|
case tea.KeyMsg: |
|
|
|
|
if msg.String() == "ctrl+c" { |
|
|
|
@ -87,28 +121,72 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -87,28 +121,72 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
|
|
return m, nil |
|
|
|
|
} |
|
|
|
|
case spinner.TickMsg: |
|
|
|
|
var cmd tea.Cmd |
|
|
|
|
m.spinner, cmd = m.spinner.Update(msg) |
|
|
|
|
return m, cmd |
|
|
|
|
case chat1.MsgSummary: |
|
|
|
|
log.Println("chat1.MsgSummary passed to m.Update()") |
|
|
|
|
var cmd tea.Cmd |
|
|
|
|
return m, cmd |
|
|
|
|
case tea.WindowSizeMsg: |
|
|
|
|
headerHeight := lipgloss.Height(m.headerView()) |
|
|
|
|
footerHeight := lipgloss.Height(m.footerView()) |
|
|
|
|
verticalMarginHeight := headerHeight + footerHeight |
|
|
|
|
if !m.ready { |
|
|
|
|
// Since this program is using the full size of the viewport we
|
|
|
|
|
// need to wait until we've received the window dimensions before
|
|
|
|
|
// we can initialize the viewport. The initial dimensions come in
|
|
|
|
|
// quickly, though asynchronously, which is why we wait for them
|
|
|
|
|
// here.
|
|
|
|
|
m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight) |
|
|
|
|
m.viewport.YPosition = headerHeight |
|
|
|
|
m.viewport.HighPerformanceRendering = useHighPerformanceRenderer |
|
|
|
|
m.viewport.SetContent(m.PopulateChat()) |
|
|
|
|
m.ready = true |
|
|
|
|
// This is only necessary for high performance rendering, which in
|
|
|
|
|
// most cases you won't need.
|
|
|
|
|
// Render the viewport one line below the header.
|
|
|
|
|
m.viewport.YPosition = headerHeight + 1 |
|
|
|
|
} else { |
|
|
|
|
m.viewport.Width = msg.Width |
|
|
|
|
m.viewport.Height = msg.Height - verticalMarginHeight |
|
|
|
|
} |
|
|
|
|
if useHighPerformanceRenderer { |
|
|
|
|
// Render (or re-render) the whole viewport. Necessary both to
|
|
|
|
|
// initialize the viewport and when the window is resized.
|
|
|
|
|
// This is needed for high-performance rendering only.
|
|
|
|
|
cmds = append(cmds, viewport.Sync(m.viewport)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Handle keyboard and mouse events in the viewport
|
|
|
|
|
m.viewport, cmd = m.viewport.Update(msg) |
|
|
|
|
cmds = append(cmds, cmd) |
|
|
|
|
return m, tea.Batch(cmds...) |
|
|
|
|
default: |
|
|
|
|
return m, nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (m model) View() string { |
|
|
|
|
s := "\n" |
|
|
|
|
|
|
|
|
|
for _, res := range mainModel.chat { |
|
|
|
|
log.Println(res) |
|
|
|
|
if res.Content.TypeName == "text" { |
|
|
|
|
s += fmt.Sprintf("%+v: %+v\n", res.Sender.Username, res.Content.Text.Body) |
|
|
|
|
func (m model) PopulateChat() string { |
|
|
|
|
if m.currentConversation.Name == "" { |
|
|
|
|
return "" |
|
|
|
|
} else { |
|
|
|
|
ret := "" |
|
|
|
|
for _, chatmsg := range m.chat { |
|
|
|
|
var content string |
|
|
|
|
if chatmsg.Content.TypeName == "text" { |
|
|
|
|
content = chatmsg.Content.Text.Body |
|
|
|
|
} else { |
|
|
|
|
content = "Unrendered." |
|
|
|
|
} |
|
|
|
|
ret += fmt.Sprintf("%+v: %+v", chatmsg.Sender.Username, content) |
|
|
|
|
} |
|
|
|
|
return ret |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (m model) View() string { |
|
|
|
|
s := "\n" |
|
|
|
|
|
|
|
|
|
s += m.viewport.View() |
|
|
|
|
s += helpStyle("\nCtrl+C to exit\n") |
|
|
|
|
|
|
|
|
|
if m.quitting { |
|
|
|
|