138 lines
3.2 KiB
Go
138 lines
3.2 KiB
Go
package util
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/bubbles/viewport"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/muesli/reflow/ansi"
|
|
)
|
|
|
|
type MsgTempfileEditorClosed string
|
|
|
|
// OpenTempfileEditor opens $EDITOR on a temporary file with the given content.
|
|
// Upon closing, the contents of the file are read and returned wrapped in a
|
|
// MsgTempfileEditorClosed
|
|
func OpenTempfileEditor(pattern string, content string, placeholder string) tea.Cmd {
|
|
msgFile, _ := os.CreateTemp("/tmp", pattern)
|
|
|
|
err := os.WriteFile(msgFile.Name(), []byte(placeholder+content), os.ModeAppend)
|
|
if err != nil {
|
|
return func() tea.Msg { return err }
|
|
}
|
|
|
|
editor := os.Getenv("EDITOR")
|
|
if editor == "" {
|
|
editor = "vim"
|
|
}
|
|
|
|
c := exec.Command(editor, msgFile.Name())
|
|
return tea.ExecProcess(c, func(err error) tea.Msg {
|
|
bytes, err := os.ReadFile(msgFile.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
os.Remove(msgFile.Name())
|
|
fileContents := string(bytes)
|
|
if strings.HasPrefix(fileContents, placeholder) {
|
|
fileContents = fileContents[len(placeholder):]
|
|
}
|
|
stripped := strings.Trim(fileContents, "\n \t")
|
|
return MsgTempfileEditorClosed(stripped)
|
|
})
|
|
}
|
|
|
|
// similar to lipgloss.Height, except returns 0 instead of 1 on empty strings
|
|
func Height(str string) int {
|
|
if str == "" {
|
|
return 0
|
|
}
|
|
return strings.Count(str, "\n") + 1
|
|
}
|
|
|
|
func Width(str string) int {
|
|
if str == "" {
|
|
return 0
|
|
}
|
|
return ansi.PrintableRuneWidth(str)
|
|
}
|
|
|
|
func TruncateRightToCellWidth(str string, width int, tail string) string {
|
|
cellWidth := ansi.PrintableRuneWidth(str)
|
|
if cellWidth <= width {
|
|
return str
|
|
}
|
|
|
|
tailWidth := ansi.PrintableRuneWidth(tail)
|
|
if width <= tailWidth {
|
|
return tail[:width]
|
|
}
|
|
|
|
targetWidth := width - tailWidth
|
|
runes := []rune(str)
|
|
|
|
for i := len(runes) - 1; i >= 0; i-- {
|
|
str = string(runes[:i])
|
|
if ansi.PrintableRuneWidth(str) <= targetWidth {
|
|
return str + tail
|
|
}
|
|
}
|
|
|
|
return tail
|
|
}
|
|
|
|
func TruncateLeftToCellWidth(str string, width int, tail string) string {
|
|
cellWidth := ansi.PrintableRuneWidth(str)
|
|
if cellWidth <= width {
|
|
return str
|
|
}
|
|
|
|
tailWidth := ansi.PrintableRuneWidth(tail)
|
|
if width <= tailWidth {
|
|
return tail[:width]
|
|
}
|
|
|
|
targetWidth := width - tailWidth
|
|
runes := []rune(str)
|
|
|
|
for i := 0; i < len(runes); i++ {
|
|
str = string(runes[i:])
|
|
if ansi.PrintableRuneWidth(str) <= targetWidth {
|
|
return tail + str
|
|
}
|
|
}
|
|
|
|
return tail
|
|
}
|
|
|
|
func ScrollIntoView(vp *viewport.Model, offset int, edge int) {
|
|
currentOffset := vp.YOffset
|
|
if offset >= currentOffset && offset < currentOffset+vp.Height {
|
|
return
|
|
}
|
|
distance := currentOffset - offset
|
|
if distance < 0 {
|
|
// we should scroll down until it just comes into view
|
|
vp.SetYOffset(currentOffset - (distance + (vp.Height - edge)) + 1)
|
|
} else {
|
|
// we should scroll up
|
|
vp.SetYOffset(currentOffset - distance - edge)
|
|
}
|
|
}
|
|
|
|
func ErrorBanner(err error, width int) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
return lipgloss.NewStyle().
|
|
Width(width).
|
|
AlignHorizontal(lipgloss.Center).
|
|
Bold(true).
|
|
Foreground(lipgloss.Color("1")).
|
|
Render(fmt.Sprintf("%s", err))
|
|
}
|