Detailed changes
@@ -1 +1 @@
-{"words":["opencode","charmbracelet","lipgloss","bubbletea","textinput","Focusable","lsps","Sourcegraph"],"version":"0.2","language":"en","flagWords":[]}
+{"flagWords":[],"language":"en","words":["opencode","charmbracelet","lipgloss","bubbletea","textinput","Focusable","lsps","Sourcegraph","filepicker","imageorient","rasterx","oksvg","termenv","trashhalo","lucasb","nfnt","srwiley","Lanczos"],"version":"0.2"}
@@ -1,468 +0,0 @@
-package dialog
-
-// import (
-// "fmt"
-// "net/http"
-// "os"
-// "path/filepath"
-// "sort"
-// "strings"
-// "time"
-//
-// "github.com/charmbracelet/bubbles/v2/key"
-// "github.com/charmbracelet/bubbles/v2/textinput"
-// "github.com/charmbracelet/bubbles/v2/viewport"
-// tea "github.com/charmbracelet/bubbletea/v2"
-// "github.com/charmbracelet/lipgloss/v2"
-// "github.com/opencode-ai/opencode/internal/app"
-// "github.com/opencode-ai/opencode/internal/logging"
-// "github.com/opencode-ai/opencode/internal/message"
-// "github.com/opencode-ai/opencode/internal/tui/image"
-// "github.com/opencode-ai/opencode/internal/tui/styles"
-// "github.com/opencode-ai/opencode/internal/tui/util"
-// )
-//
-// const (
-// maxAttachmentSize = int64(5 * 1024 * 1024) // 5MB
-// downArrow = "down"
-// upArrow = "up"
-// )
-//
-// type FilePrickerKeyMap struct {
-// Enter key.Binding
-// Down key.Binding
-// Up key.Binding
-// Forward key.Binding
-// Backward key.Binding
-// OpenFilePicker key.Binding
-// Esc key.Binding
-// InsertCWD key.Binding
-// }
-//
-// var filePickerKeyMap = FilePrickerKeyMap{
-// Enter: key.NewBinding(
-// key.WithKeys("enter"),
-// key.WithHelp("enter", "select file/enter directory"),
-// ),
-// Down: key.NewBinding(
-// key.WithKeys("j", downArrow),
-// key.WithHelp("↓/j", "down"),
-// ),
-// Up: key.NewBinding(
-// key.WithKeys("k", upArrow),
-// key.WithHelp("↑/k", "up"),
-// ),
-// Forward: key.NewBinding(
-// key.WithKeys("l"),
-// key.WithHelp("l", "enter directory"),
-// ),
-// Backward: key.NewBinding(
-// key.WithKeys("h", "backspace"),
-// key.WithHelp("h/backspace", "go back"),
-// ),
-// OpenFilePicker: key.NewBinding(
-// key.WithKeys("ctrl+f"),
-// key.WithHelp("ctrl+f", "open file picker"),
-// ),
-// Esc: key.NewBinding(
-// key.WithKeys("esc"),
-// key.WithHelp("esc", "close/exit"),
-// ),
-// InsertCWD: key.NewBinding(
-// key.WithKeys("i"),
-// key.WithHelp("i", "manual path input"),
-// ),
-// }
-//
-// type filepickerCmp struct {
-// basePath string
-// width int
-// height int
-// cursor int
-// err error
-// cursorChain stack
-// viewport viewport.Model
-// dirs []os.DirEntry
-// cwdDetails *DirNode
-// selectedFile string
-// cwd textinput.Model
-// ShowFilePicker bool
-// app *app.App
-// }
-//
-// type DirNode struct {
-// parent *DirNode
-// child *DirNode
-// directory string
-// }
-// type stack []int
-//
-// func (s stack) Push(v int) stack {
-// return append(s, v)
-// }
-//
-// func (s stack) Pop() (stack, int) {
-// l := len(s)
-// return s[:l-1], s[l-1]
-// }
-//
-// type AttachmentAddedMsg struct {
-// Attachment message.Attachment
-// }
-//
-// func (f *filepickerCmp) Init() tea.Cmd {
-// return nil
-// }
-//
-// func (f *filepickerCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-// var cmd tea.Cmd
-// switch msg := msg.(type) {
-// case tea.WindowSizeMsg:
-// f.width = 60
-// f.height = 20
-// f.viewport.SetWidth(80)
-// f.viewport.SetHeight(22)
-// f.cursor = 0
-// f.getCurrentFileBelowCursor()
-// case tea.KeyPressMsg:
-// if f.cwd.Focused() {
-// f.cwd, cmd = f.cwd.Update(msg)
-// }
-// switch {
-// case key.Matches(msg, filePickerKeyMap.InsertCWD):
-// f.cwd.Focus()
-// return f, cmd
-// case key.Matches(msg, filePickerKeyMap.Esc):
-// if f.cwd.Focused() {
-// f.cwd.Blur()
-// }
-// case key.Matches(msg, filePickerKeyMap.Down):
-// if !f.cwd.Focused() || msg.String() == downArrow {
-// if f.cursor < len(f.dirs)-1 {
-// f.cursor++
-// f.getCurrentFileBelowCursor()
-// }
-// }
-// case key.Matches(msg, filePickerKeyMap.Up):
-// if !f.cwd.Focused() || msg.String() == upArrow {
-// if f.cursor > 0 {
-// f.cursor--
-// f.getCurrentFileBelowCursor()
-// }
-// }
-// case key.Matches(msg, filePickerKeyMap.Enter):
-// var path string
-// var isPathDir bool
-// if f.cwd.Focused() {
-// path = f.cwd.Value()
-// fileInfo, err := os.Stat(path)
-// if err != nil {
-// logging.ErrorPersist("Invalid path")
-// return f, cmd
-// }
-// isPathDir = fileInfo.IsDir()
-// } else {
-// path = filepath.Join(f.cwdDetails.directory, "/", f.dirs[f.cursor].Name())
-// isPathDir = f.dirs[f.cursor].IsDir()
-// }
-// if isPathDir {
-// newWorkingDir := DirNode{parent: f.cwdDetails, directory: path}
-// f.cwdDetails.child = &newWorkingDir
-// f.cwdDetails = f.cwdDetails.child
-// f.cursorChain = f.cursorChain.Push(f.cursor)
-// f.dirs = readDir(f.cwdDetails.directory, false)
-// f.cursor = 0
-// f.cwd.SetValue(f.cwdDetails.directory)
-// f.getCurrentFileBelowCursor()
-// } else {
-// f.selectedFile = path
-// return f.addAttachmentToMessage()
-// }
-// case key.Matches(msg, filePickerKeyMap.Esc):
-// if !f.cwd.Focused() {
-// f.cursorChain = make(stack, 0)
-// f.cursor = 0
-// } else {
-// f.cwd.Blur()
-// }
-// case key.Matches(msg, filePickerKeyMap.Forward):
-// if !f.cwd.Focused() {
-// if f.dirs[f.cursor].IsDir() {
-// path := filepath.Join(f.cwdDetails.directory, "/", f.dirs[f.cursor].Name())
-// newWorkingDir := DirNode{parent: f.cwdDetails, directory: path}
-// f.cwdDetails.child = &newWorkingDir
-// f.cwdDetails = f.cwdDetails.child
-// f.cursorChain = f.cursorChain.Push(f.cursor)
-// f.dirs = readDir(f.cwdDetails.directory, false)
-// f.cursor = 0
-// f.cwd.SetValue(f.cwdDetails.directory)
-// f.getCurrentFileBelowCursor()
-// }
-// }
-// case key.Matches(msg, filePickerKeyMap.Backward):
-// if !f.cwd.Focused() {
-// if len(f.cursorChain) != 0 && f.cwdDetails.parent != nil {
-// f.cursorChain, f.cursor = f.cursorChain.Pop()
-// f.cwdDetails = f.cwdDetails.parent
-// f.cwdDetails.child = nil
-// f.dirs = readDir(f.cwdDetails.directory, false)
-// f.cwd.SetValue(f.cwdDetails.directory)
-// f.getCurrentFileBelowCursor()
-// }
-// }
-// case key.Matches(msg, filePickerKeyMap.OpenFilePicker):
-// f.dirs = readDir(f.cwdDetails.directory, false)
-// f.cursor = 0
-// f.getCurrentFileBelowCursor()
-// }
-// }
-// return f, cmd
-// }
-//
-// func (f *filepickerCmp) addAttachmentToMessage() (tea.Model, tea.Cmd) {
-// // modeInfo := GetSelectedModel(config.Get())
-// // if !modeInfo.SupportsAttachments {
-// // logging.ErrorPersist(fmt.Sprintf("Model %s doesn't support attachments", modeInfo.Name))
-// // return f, nil
-// // }
-//
-// selectedFilePath := f.selectedFile
-// if !isExtSupported(selectedFilePath) {
-// logging.ErrorPersist("Unsupported file")
-// return f, nil
-// }
-//
-// isFileLarge, err := image.ValidateFileSize(selectedFilePath, maxAttachmentSize)
-// if err != nil {
-// logging.ErrorPersist("unable to read the image")
-// return f, nil
-// }
-// if isFileLarge {
-// logging.ErrorPersist("file too large, max 5MB")
-// return f, nil
-// }
-//
-// content, err := os.ReadFile(selectedFilePath)
-// if err != nil {
-// logging.ErrorPersist("Unable read selected file")
-// return f, nil
-// }
-//
-// mimeBufferSize := min(512, len(content))
-// mimeType := http.DetectContentType(content[:mimeBufferSize])
-// fileName := filepath.Base(selectedFilePath)
-// attachment := message.Attachment{FilePath: selectedFilePath, FileName: fileName, MimeType: mimeType, Content: content}
-// f.selectedFile = ""
-// return f, util.CmdHandler(AttachmentAddedMsg{attachment})
-// }
-//
-// func (f *filepickerCmp) View() tea.View {
-// t := styles.CurrentTheme()
-// baseStyle := t.S().Base
-// const maxVisibleDirs = 20
-// const maxWidth = 80
-//
-// adjustedWidth := maxWidth
-// for _, file := range f.dirs {
-// if len(file.Name()) > adjustedWidth-4 { // Account for padding
-// adjustedWidth = len(file.Name()) + 4
-// }
-// }
-// adjustedWidth = max(30, min(adjustedWidth, f.width-15)) + 1
-//
-// files := make([]string, 0, maxVisibleDirs)
-// startIdx := 0
-//
-// if len(f.dirs) > maxVisibleDirs {
-// halfVisible := maxVisibleDirs / 2
-// if f.cursor >= halfVisible && f.cursor < len(f.dirs)-halfVisible {
-// startIdx = f.cursor - halfVisible
-// } else if f.cursor >= len(f.dirs)-halfVisible {
-// startIdx = len(f.dirs) - maxVisibleDirs
-// }
-// }
-//
-// endIdx := min(startIdx+maxVisibleDirs, len(f.dirs))
-//
-// for i := startIdx; i < endIdx; i++ {
-// file := f.dirs[i]
-// itemStyle := t.S().Text.Width(adjustedWidth)
-//
-// if i == f.cursor {
-// itemStyle = itemStyle.
-// Background(t.Primary).
-// Bold(true)
-// }
-// filename := file.Name()
-//
-// if len(filename) > adjustedWidth-4 {
-// filename = filename[:adjustedWidth-7] + "..."
-// }
-// if file.IsDir() {
-// filename = filename + "/"
-// }
-// // No need to reassign filename if it's not changing
-//
-// files = append(files, itemStyle.Padding(0, 1).Render(filename))
-// }
-//
-// // Pad to always show exactly 21 lines
-// for len(files) < maxVisibleDirs {
-// files = append(files, baseStyle.Width(adjustedWidth).Render(""))
-// }
-//
-// currentPath := baseStyle.
-// Height(1).
-// Width(adjustedWidth).
-// Render(f.cwd.View())
-//
-// viewportstyle := baseStyle.
-// Width(f.viewport.Width()).
-// Border(lipgloss.RoundedBorder()).
-// BorderForeground(t.BorderFocus).
-// Padding(2).
-// Render(f.viewport.View())
-// var insertExitText string
-// if f.IsCWDFocused() {
-// insertExitText = "Press esc to exit typing path"
-// } else {
-// insertExitText = "Press i to start typing path"
-// }
-//
-// content := lipgloss.JoinVertical(
-// lipgloss.Left,
-// currentPath,
-// baseStyle.Width(adjustedWidth).Render(""),
-// baseStyle.Width(adjustedWidth).Render(lipgloss.JoinVertical(lipgloss.Left, files...)),
-// baseStyle.Width(adjustedWidth).Render(""),
-// t.S().Muted.Width(adjustedWidth).Render(insertExitText),
-// )
-//
-// f.cwd.SetValue(f.cwd.Value())
-// contentStyle := baseStyle.Padding(1, 2).
-// Border(lipgloss.RoundedBorder()).
-// BorderForeground(t.BorderFocus).
-// Width(lipgloss.Width(content) + 4)
-//
-// return tea.NewView(
-// lipgloss.JoinHorizontal(lipgloss.Center, contentStyle.Render(content), viewportstyle),
-// )
-// }
-//
-// type FilepickerCmp interface {
-// util.Model
-// ToggleFilepicker(showFilepicker bool)
-// IsCWDFocused() bool
-// }
-//
-// func (f *filepickerCmp) ToggleFilepicker(showFilepicker bool) {
-// f.ShowFilePicker = showFilepicker
-// }
-//
-// func (f *filepickerCmp) IsCWDFocused() bool {
-// return f.cwd.Focused()
-// }
-//
-// func NewFilepickerCmp(app *app.App) FilepickerCmp {
-// homepath, err := os.UserHomeDir()
-// if err != nil {
-// logging.Error("error loading user files")
-// return nil
-// }
-// baseDir := DirNode{parent: nil, directory: homepath}
-// dirs := readDir(homepath, false)
-// viewport := viewport.New()
-// currentDirectory := textinput.New()
-// currentDirectory.CharLimit = 200
-// currentDirectory.SetWidth(44)
-// currentDirectory.Cursor().Blink = true
-// currentDirectory.SetValue(baseDir.directory)
-// return &filepickerCmp{cwdDetails: &baseDir, dirs: dirs, cursorChain: make(stack, 0), viewport: viewport, cwd: currentDirectory, app: app}
-// }
-//
-// func (f *filepickerCmp) getCurrentFileBelowCursor() {
-// if len(f.dirs) == 0 || f.cursor < 0 || f.cursor >= len(f.dirs) {
-// logging.Error(fmt.Sprintf("Invalid cursor position. Dirs length: %d, Cursor: %d", len(f.dirs), f.cursor))
-// f.viewport.SetContent("Preview unavailable")
-// return
-// }
-//
-// dir := f.dirs[f.cursor]
-// filename := dir.Name()
-// if !dir.IsDir() && isExtSupported(filename) {
-// fullPath := f.cwdDetails.directory + "/" + dir.Name()
-//
-// go func() {
-// imageString, err := image.ImagePreview(f.viewport.Width()-4, fullPath)
-// if err != nil {
-// logging.Error(err.Error())
-// f.viewport.SetContent("Preview unavailable")
-// return
-// }
-//
-// f.viewport.SetContent(imageString)
-// }()
-// } else {
-// f.viewport.SetContent("Preview unavailable")
-// }
-// }
-//
-// func readDir(path string, showHidden bool) []os.DirEntry {
-// logging.Info(fmt.Sprintf("Reading directory: %s", path))
-//
-// entriesChan := make(chan []os.DirEntry, 1)
-// errChan := make(chan error, 1)
-//
-// go func() {
-// dirEntries, err := os.ReadDir(path)
-// if err != nil {
-// logging.ErrorPersist(err.Error())
-// errChan <- err
-// return
-// }
-// entriesChan <- dirEntries
-// }()
-//
-// select {
-// case dirEntries := <-entriesChan:
-// sort.Slice(dirEntries, func(i, j int) bool {
-// if dirEntries[i].IsDir() == dirEntries[j].IsDir() {
-// return dirEntries[i].Name() < dirEntries[j].Name()
-// }
-// return dirEntries[i].IsDir()
-// })
-//
-// if showHidden {
-// return dirEntries
-// }
-//
-// var sanitizedDirEntries []os.DirEntry
-// for _, dirEntry := range dirEntries {
-// isHidden, _ := IsHidden(dirEntry.Name())
-// if !isHidden {
-// if dirEntry.IsDir() || isExtSupported(dirEntry.Name()) {
-// sanitizedDirEntries = append(sanitizedDirEntries, dirEntry)
-// }
-// }
-// }
-//
-// return sanitizedDirEntries
-//
-// case err := <-errChan:
-// logging.ErrorPersist(fmt.Sprintf("Error reading directory %s", path), err)
-// return []os.DirEntry{}
-//
-// case <-time.After(5 * time.Second):
-// logging.ErrorPersist(fmt.Sprintf("Timeout reading directory %s", path), nil)
-// return []os.DirEntry{}
-// }
-// }
-//
-// func IsHidden(file string) (bool, error) {
-// return strings.HasPrefix(file, "."), nil
-// }
-//
-// func isExtSupported(path string) bool {
-// ext := strings.ToLower(filepath.Ext(path))
-// return (ext == ".jpg" || ext == ".jpeg" || ext == ".webp" || ext == ".png")
-// }
@@ -5,12 +5,14 @@ import (
"strings"
"github.com/charmbracelet/bubbles/v2/filepicker"
+ "github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
"github.com/opencode-ai/opencode/internal/tui/components/core"
"github.com/opencode-ai/opencode/internal/tui/components/dialogs"
"github.com/opencode-ai/opencode/internal/tui/components/image"
"github.com/opencode-ai/opencode/internal/tui/styles"
+ "github.com/opencode-ai/opencode/internal/tui/util"
)
const (
@@ -19,16 +21,19 @@ const (
fileSelectionHight = 10
)
+type FilePickedMsg struct {
+ FilePath string
+}
+
type FilePicker interface {
dialogs.DialogModel
}
-type filePicker struct {
+type model struct {
wWidth int
wHeight int
width int
- filepicker filepicker.Model
- selectedFile string
+ filePicker filepicker.Model
highlightedFile string
image image.Model
}
@@ -46,31 +51,40 @@ func NewFilePickerCmp() FilePicker {
fp.SetHeight(fileSelectionHight)
image := image.New(1, 1, "")
- return &filePicker{filepicker: fp, image: image}
+ return &model{filePicker: fp, image: image}
}
-func (m *filePicker) Init() tea.Cmd {
- return m.filepicker.Init()
+func (m *model) Init() tea.Cmd {
+ return m.filePicker.Init()
}
-func (m *filePicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.wWidth = msg.Width
m.wHeight = msg.Height
m.width = min(70, m.wWidth)
- styles := m.filepicker.Styles
+ styles := m.filePicker.Styles
styles.Directory = styles.Directory.Width(m.width - 4)
styles.Selected = styles.Selected.PaddingLeft(1).Width(m.width - 4)
styles.DisabledSelected = styles.DisabledSelected.PaddingLeft(1).Width(m.width - 4)
styles.File = styles.File.Width(m.width)
- m.filepicker.Styles = styles
+ m.filePicker.Styles = styles
return m, nil
+
+ case tea.KeyPressMsg:
+ if key.Matches(msg, m.filePicker.KeyMap.Back) {
+ // make sure we don't go back if we are at the home directory
+ homeDir, _ := os.UserHomeDir()
+ if m.filePicker.CurrentDirectory == homeDir {
+ return m, nil
+ }
+ }
}
var cmd tea.Cmd
var cmds []tea.Cmd
- m.filepicker, cmd = m.filepicker.Update(msg)
+ m.filePicker, cmd = m.filePicker.Update(msg)
cmds = append(cmds, cmd)
if m.highlightedFile != m.currentImage() && m.currentImage() != "" {
w, h := m.imagePreviewSize()
@@ -80,37 +94,40 @@ func (m *filePicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.highlightedFile = m.currentImage()
// Did the user select a file?
- if didSelect, path := m.filepicker.DidSelectFile(msg); didSelect {
+ if didSelect, path := m.filePicker.DidSelectFile(msg); didSelect {
// Get the path of the selected file.
- m.selectedFile = path
+ return m, tea.Sequence(
+ util.CmdHandler(dialogs.CloseDialogMsg{}),
+ util.CmdHandler(FilePickedMsg{FilePath: path}),
+ )
}
m.image, cmd = m.image.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
-func (m *filePicker) View() tea.View {
+func (m *model) View() tea.View {
t := styles.CurrentTheme()
content := lipgloss.JoinVertical(
lipgloss.Left,
t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Add Image", m.width-4)),
m.imagePreview(),
- m.filepicker.View(),
+ m.filePicker.View(),
)
return tea.NewView(m.style().Render(content))
}
-func (m *filePicker) currentImage() string {
- for _, ext := range m.filepicker.AllowedTypes {
- if strings.HasSuffix(m.filepicker.HighlightedPath(), ext) {
- return m.filepicker.HighlightedPath()
+func (m *model) currentImage() string {
+ for _, ext := range m.filePicker.AllowedTypes {
+ if strings.HasSuffix(m.filePicker.HighlightedPath(), ext) {
+ return m.filePicker.HighlightedPath()
}
}
return ""
}
-func (m *filePicker) imagePreview() string {
+func (m *model) imagePreview() string {
t := styles.CurrentTheme()
w, h := m.imagePreviewSize()
if m.currentImage() == "" {
@@ -125,16 +142,16 @@ func (m *filePicker) imagePreview() string {
return m.imagePreviewStyle().Width(w).Height(h).Render(m.image.View())
}
-func (m *filePicker) imagePreviewStyle() lipgloss.Style {
+func (m *model) imagePreviewStyle() lipgloss.Style {
t := styles.CurrentTheme()
return t.S().Base.Padding(1, 1, 1, 1)
}
-func (m *filePicker) imagePreviewSize() (int, int) {
+func (m *model) imagePreviewSize() (int, int) {
return m.width - 4, min(20, m.wHeight/2)
}
-func (m *filePicker) style() lipgloss.Style {
+func (m *model) style() lipgloss.Style {
t := styles.CurrentTheme()
return t.S().Base.
Width(m.width).
@@ -143,12 +160,12 @@ func (m *filePicker) style() lipgloss.Style {
}
// ID implements FilePicker.
-func (m *filePicker) ID() dialogs.DialogID {
+func (m *model) ID() dialogs.DialogID {
return FilePickerID
}
// Position implements FilePicker.
-func (m *filePicker) Position() (int, int) {
+func (m *model) Position() (int, int) {
row := m.wHeight/4 - 2 // just a bit above the center
col := m.wWidth / 2
col -= m.width / 2
@@ -3,7 +3,6 @@
package image
import (
- "context"
"fmt"
_ "image/jpeg"
_ "image/png"
@@ -17,8 +16,6 @@ type Model struct {
width uint
height uint
err error
-
- cancelAnimation context.CancelFunc
}
func New(width, height uint, url string) Model {
@@ -38,11 +35,11 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
case errMsg:
m.err = msg
return m, nil
- case rewdrawMsg:
+ case redrawMsg:
m.width = msg.width
m.height = msg.height
m.url = msg.url
- return m, loadUrl(m.url)
+ return m, loadURL(m.url)
case loadMsg:
return handleLoadMsg(m, msg)
}
@@ -60,7 +57,7 @@ type errMsg struct{ error }
func (m Model) Redraw(width uint, height uint, url string) tea.Cmd {
return func() tea.Msg {
- return rewdrawMsg{
+ return redrawMsg{
width: width,
height: height,
url: url,
@@ -68,9 +65,9 @@ func (m Model) Redraw(width uint, height uint, url string) tea.Cmd {
}
}
-func (m Model) UpdateUrl(url string) tea.Cmd {
+func (m Model) UpdateURL(url string) tea.Cmd {
return func() tea.Msg {
- return rewdrawMsg{
+ return redrawMsg{
width: m.width,
height: m.height,
url: url,
@@ -78,7 +75,7 @@ func (m Model) UpdateUrl(url string) tea.Cmd {
}
}
-type rewdrawMsg struct {
+type redrawMsg struct {
width uint
height uint
url string
@@ -6,7 +6,6 @@ import (
"image"
"image/png"
"io"
- "io/ioutil"
"net/http"
"os"
"strings"
@@ -24,7 +23,7 @@ type loadMsg struct {
io.ReadCloser
}
-func loadUrl(url string) tea.Cmd {
+func loadURL(url string) tea.Cmd {
var r io.ReadCloser
var err error
@@ -52,20 +51,9 @@ func load(r io.ReadCloser) tea.Cmd {
}
func handleLoadMsg(m Model, msg loadMsg) (Model, tea.Cmd) {
- if m.cancelAnimation != nil {
- m.cancelAnimation()
- }
-
- // blank out image so it says "loading..."
- m.image = ""
-
- return handleLoadMsgStatic(m, msg)
-}
-
-func handleLoadMsgStatic(m Model, msg loadMsg) (Model, tea.Cmd) {
defer msg.Close()
- img, err := readerToimage(m.width, m.height, m.url, msg)
+ img, err := readerToImage(m.width, m.height, m.url, msg)
if err != nil {
return m, func() tea.Msg { return errMsg{err} }
}
@@ -73,7 +61,7 @@ func handleLoadMsgStatic(m Model, msg loadMsg) (Model, tea.Cmd) {
return m, nil
}
-func imageToString(width, height uint, url string, img image.Image) (string, error) {
+func imageToString(width, height uint, img image.Image) (string, error) {
img = resize.Thumbnail(width, height*2-4, img, resize.Lanczos3)
b := img.Bounds()
w := b.Max.X
@@ -84,7 +72,7 @@ func imageToString(width, height uint, url string, img image.Image) (string, err
for x := w; x < int(width); x = x + 2 {
str.WriteString(" ")
}
- for x := 0; x < w; x++ {
+ for x := range w {
c1, _ := colorful.MakeColor(img.At(x, y))
color1 := p.Color(c1.Hex())
c2, _ := colorful.MakeColor(img.At(x, y+1))
@@ -99,9 +87,9 @@ func imageToString(width, height uint, url string, img image.Image) (string, err
return str.String(), nil
}
-func readerToimage(width uint, height uint, url string, r io.Reader) (string, error) {
+func readerToImage(width uint, height uint, url string, r io.Reader) (string, error) {
if strings.HasSuffix(strings.ToLower(url), ".svg") {
- return svgToimage(width, height, url, r)
+ return svgToImage(width, height, r)
}
img, _, err := imageorient.Decode(r)
@@ -109,15 +97,15 @@ func readerToimage(width uint, height uint, url string, r io.Reader) (string, er
return "", err
}
- return imageToString(width, height, url, img)
+ return imageToString(width, height, img)
}
-func svgToimage(width uint, height uint, url string, r io.Reader) (string, error) {
+func svgToImage(width uint, height uint, r io.Reader) (string, error) {
// Original author: https://stackoverflow.com/users/10826783/usual-human
// https://stackoverflow.com/questions/42993407/how-to-create-and-export-svg-to-png-jpeg-in-golang
// Adapted to use size from SVG, and to use temp file.
- tmpPngFile, err := ioutil.TempFile("", "imgcat.*.png")
+ tmpPngFile, err := os.CreateTemp("", "img.*.png")
if err != nil {
return "", err
}
@@ -153,5 +141,5 @@ func svgToimage(width uint, height uint, url string, r io.Reader) (string, error
if err != nil {
return "", err
}
- return imageToString(width, height, url, img)
+ return imageToString(width, height, img)
}