Skip to content

Commit

Permalink
feat: csv bubble
Browse files Browse the repository at this point in the history
  • Loading branch information
mistakenelf committed Apr 14, 2024
1 parent cc61496 commit 9ab307a
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 0 deletions.
200 changes: 200 additions & 0 deletions csv/csv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Package csv implements a csv bubble which renders a table with
// the content of the csv.
package csv

import (
"encoding/csv"
"fmt"
"log"
"os"
"time"

"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"

"github.com/mistakenelf/fm/polish"
)

type statusMessageTimeoutMsg struct{}
type errorMsg string
type csvMsg struct {
headers []string
records [][]string
}

const (
purple = lipgloss.Color("99")
gray = lipgloss.Color("245")
lightGray = lipgloss.Color("241")
)

var (
HeaderStyle = lipgloss.NewStyle().Foreground(purple).Bold(true).Align(lipgloss.Center)
CellStyle = lipgloss.NewStyle().Padding(0, 1).Width(14)
OddRowStyle = CellStyle.Copy().Foreground(gray)
EvenRowStyle = CellStyle.Copy().Foreground(lightGray)
)

// Model represents the properties of a code bubble.
type Model struct {
Viewport viewport.Model
Filename string
Table *table.Table
StatusMessage string
StatusMessageLifetime time.Duration
statusMessageTimer *time.Timer
ViewportDisabled bool
Headers []string
Records [][]string
}

// NewStatusMessage sets a new status message, which will show for a limited
// amount of time.
func (m *Model) NewStatusMessageCmd(s string) tea.Cmd {
m.StatusMessage = s
if m.statusMessageTimer != nil {
m.statusMessageTimer.Stop()
}

m.statusMessageTimer = time.NewTimer(m.StatusMessageLifetime)

// Wait for timeout
return func() tea.Msg {
<-m.statusMessageTimer.C
return statusMessageTimeoutMsg{}
}
}

// SetFileName sets current file to highlight.
func (m *Model) SetFileNameCmd(filename string) tea.Cmd {
m.Filename = filename

return func() tea.Msg {
file, err := os.Open(m.Filename)
if err != nil {
log.Fatal("Error while reading the file", err)
}

defer file.Close()

reader := csv.NewReader(file)

headers, err := reader.Read()
if err != nil {
fmt.Println(err)
}

records, err := reader.ReadAll()
if err != nil {
fmt.Println("Error reading records")
}

return csvMsg{headers, records}
}
}

// New creates a new instance of code.
func New() Model {
viewPort := viewport.New(0, 0)
table := table.New()

return Model{
Viewport: viewPort,
StatusMessage: "",
StatusMessageLifetime: time.Second,
Table: table,
}
}

// Init initializes the code bubble.
func (m Model) Init() tea.Cmd {
return nil
}

// SetSize sets the size of the bubble.
func (m *Model) SetSizeCmd(w, h int) tea.Cmd {
m.Viewport.Width = w
m.Viewport.Height = h

if m.Filename != "" {
return m.SetFileNameCmd(m.Filename)
}

return nil
}

// GotoTop jumps to the top of the viewport.
func (m *Model) GotoTop() {
m.Viewport.GotoTop()
}

// GotoBottom jumps to the bottom of the viewport.
func (m *Model) GotoBottom() {
m.Viewport.GotoBottom()
}

// SetViewportDisabled toggles the state of the viewport.
func (m *Model) SetViewportDisabled(disabled bool) {
m.ViewportDisabled = disabled
}

// Update handles updating the UI of the code bubble.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)

switch msg := msg.(type) {
case statusMessageTimeoutMsg:
m.StatusMessage = ""

return m, nil
case csvMsg:
m.Headers = msg.headers
m.Records = msg.records

m.Table = table.New().
Border(lipgloss.NormalBorder()).
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))).
StyleFunc(func(row, col int) lipgloss.Style {
switch {
case row == 0:
return HeaderStyle
case row%2 == 0:
return EvenRowStyle
default:
return OddRowStyle
}
}).
Headers(m.Headers...).
Width(m.Viewport.Width).
Rows(m.Records...)

m.Viewport.SetContent(m.Table.String())

return m, nil
case errorMsg:
m.Filename = ""
return m, m.NewStatusMessageCmd(
lipgloss.NewStyle().
Foreground(polish.Colors.Red600).
Bold(true).
Render(string(msg)),
)
}

if !m.ViewportDisabled {
m.Viewport, cmd = m.Viewport.Update(msg)
cmds = append(cmds, cmd)
}

return m, tea.Batch(cmds...)
}

// View returns a string representation of the csv bubble.
func (m Model) View() string {
return m.Viewport.View()
}
4 changes: 4 additions & 0 deletions internal/tui/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func (m *model) openFileCmd() tea.Cmd {
m.resetViewports()

switch {
case selectedFile.Extension == ".csv":
m.state = showCsvState

return m.csv.SetFileNameCmd(selectedFile.Path)
case selectedFile.Extension == ".png" || selectedFile.Extension == ".jpg" || selectedFile.Extension == ".jpeg":
m.state = showImageState

Expand Down
2 changes: 2 additions & 0 deletions internal/tui/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (m *model) disableAllViewports() {
m.markdown.SetViewportDisabled(true)
m.help.SetViewportDisabled(true)
m.image.SetViewportDisabled(true)
m.csv.SetViewportDisabled(true)
}

func (m *model) resetViewports() {
Expand All @@ -31,6 +32,7 @@ func (m *model) resetViewports() {
m.markdown.GotoTop()
m.help.GotoTop()
m.image.GotoTop()
m.csv.GotoTop()
}

func (m *model) updateStatusBar() {
Expand Down
4 changes: 4 additions & 0 deletions internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/charmbracelet/bubbles/textinput"

"github.com/mistakenelf/fm/code"
"github.com/mistakenelf/fm/csv"
"github.com/mistakenelf/fm/filetree"
"github.com/mistakenelf/fm/help"
"github.com/mistakenelf/fm/icons"
Expand All @@ -27,6 +28,7 @@ const (
showPdfState
showHelpState
showMoveState
showCsvState
)

type Config struct {
Expand All @@ -42,6 +44,7 @@ type Config struct {
type model struct {
filetree filetree.Model
secondaryFiletree filetree.Model
csv csv.Model
help help.Model
code code.Model
image image.Model
Expand Down Expand Up @@ -167,5 +170,6 @@ func New(cfg Config) model {
showTextInput: false,
textinput: textinput.New(),
statusMessageLifetime: time.Second,
csv: csv.New(),
}
}
4 changes: 4 additions & 0 deletions internal/tui/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

cmds = append(cmds, m.image.SetSizeCmd(halfSize, height))
cmds = append(cmds, m.markdown.SetSizeCmd(halfSize, height))
cmds = append(cmds, m.csv.SetSizeCmd(halfSize, height))

m.filetree.SetSize(halfSize, height-3)
m.secondaryFiletree.SetSize(halfSize, height-3)
Expand Down Expand Up @@ -194,6 +195,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.help, cmd = m.help.Update(msg)
cmds = append(cmds, cmd)

m.csv, cmd = m.csv.Update(msg)
cmds = append(cmds, cmd)

m.updateStatusBar()

return m, tea.Batch(cmds...)
Expand Down
2 changes: 2 additions & 0 deletions internal/tui/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ func (m model) View() string {
rightBox = m.markdown.View()
case showMoveState:
rightBox = m.secondaryFiletree.View()
case showCsvState:
rightBox = m.csv.View()
}

return lipgloss.JoinVertical(lipgloss.Top,
Expand Down

0 comments on commit 9ab307a

Please sign in to comment.