105 lines
2.7 KiB
Go
105 lines
2.7 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 an $EDITOR on a new temporary file with the given
|
|
// content. Upon closing, the contents of the file are read back returned
|
|
// wrapped in a msgTempfileEditorClosed returned by the tea.Cmd
|
|
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 on empty strings
|
|
func Height(str string) int {
|
|
if str == "" {
|
|
return 0
|
|
}
|
|
return strings.Count(str, "\n") + 1
|
|
}
|
|
|
|
// truncate a string until its rendered cell width + the provided tail fits
|
|
// within the given width
|
|
func TruncateToCellWidth(str string, width int, tail string) string {
|
|
cellWidth := ansi.PrintableRuneWidth(str)
|
|
if cellWidth <= width {
|
|
return str
|
|
}
|
|
tailWidth := ansi.PrintableRuneWidth(tail)
|
|
for {
|
|
str = str[:len(str)-((cellWidth+tailWidth)-width)]
|
|
cellWidth = ansi.PrintableRuneWidth(str)
|
|
if cellWidth+tailWidth <= max(width, 0) {
|
|
break
|
|
}
|
|
}
|
|
return str + tail
|
|
}
|
|
|
|
// fraction is the fraction of the total screen height into view the offset
|
|
// should be scrolled into view. 0.5 = items will be snapped to middle of
|
|
// view
|
|
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))
|
|
}
|
|
|