From f37d59f9c1c6980cf20afcc8b52e61bb346641f4 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Thu, 7 Mar 2024 20:38:13 -0500 Subject: [PATCH 01/31] chore: move teacup bubbles here --- code/code.go | 141 ++++++++++ filesystem/filesystem.go | 534 ++++++++++++++++++++++++++++++++++++++ filetree/commands.go | 75 ++++++ filetree/init.go | 10 + filetree/keys.go | 15 ++ filetree/methods.go | 44 ++++ filetree/model.go | 31 +++ filetree/styles.go | 7 + filetree/update.go | 49 ++++ filetree/view.go | 31 +++ go.mod | 12 +- help/help.go | 135 ++++++++++ icons/directories.go | 62 +++++ icons/extensions.go | 537 +++++++++++++++++++++++++++++++++++++++ icons/filenames.go | 415 ++++++++++++++++++++++++++++++ icons/glyphs.go | 362 ++++++++++++++++++++++++++ icons/icons.go | 87 +++++++ icons/sub_extensions.go | 48 ++++ image/image.go | 167 ++++++++++++ internal/tui/model.go | 6 +- internal/tui/update.go | 10 +- markdown/markdown.go | 143 +++++++++++ pdf/pdf.go | 139 ++++++++++ statusbar/statusbar.go | 119 +++++++++ 24 files changed, 3162 insertions(+), 17 deletions(-) create mode 100644 code/code.go create mode 100644 filesystem/filesystem.go create mode 100644 filetree/commands.go create mode 100644 filetree/init.go create mode 100644 filetree/keys.go create mode 100644 filetree/methods.go create mode 100644 filetree/model.go create mode 100644 filetree/styles.go create mode 100644 filetree/update.go create mode 100644 filetree/view.go create mode 100644 help/help.go create mode 100644 icons/directories.go create mode 100644 icons/extensions.go create mode 100644 icons/filenames.go create mode 100644 icons/glyphs.go create mode 100644 icons/icons.go create mode 100644 icons/sub_extensions.go create mode 100644 image/image.go create mode 100644 markdown/markdown.go create mode 100644 pdf/pdf.go create mode 100644 statusbar/statusbar.go diff --git a/code/code.go b/code/code.go new file mode 100644 index 0000000..9e65b3a --- /dev/null +++ b/code/code.go @@ -0,0 +1,141 @@ +// Package code implements a code bubble which renders syntax highlighted +// source code based on a filename. +package code + +import ( + "bytes" + "fmt" + "path/filepath" + + "github.com/alecthomas/chroma/quick" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/mistakenelf/fm/filesystem" +) + +type syntaxMsg string +type errorMsg error + +// Highlight returns a syntax highlighted string of text. +func Highlight(content, extension, syntaxTheme string) (string, error) { + buf := new(bytes.Buffer) + if err := quick.Highlight(buf, content, extension, "terminal256", syntaxTheme); err != nil { + return "", fmt.Errorf("%w", err) + } + + return buf.String(), nil +} + +// readFileContentCmd reads the content of the file. +func readFileContentCmd(fileName, syntaxTheme string) tea.Cmd { + return func() tea.Msg { + content, err := filesystem.ReadFileContent(fileName) + if err != nil { + return errorMsg(err) + } + + highlightedContent, err := Highlight(content, filepath.Ext(fileName), syntaxTheme) + if err != nil { + return errorMsg(err) + } + + return syntaxMsg(highlightedContent) + } +} + +// Model represents the properties of a code bubble. +type Model struct { + Viewport viewport.Model + Active bool + Filename string + HighlightedContent string + SyntaxTheme string +} + +// New creates a new instance of code. +func New(active bool) Model { + viewPort := viewport.New(0, 0) + + return Model{ + Viewport: viewPort, + Active: active, + SyntaxTheme: "dracula", + } +} + +// Init initializes the code bubble. +func (m Model) Init() tea.Cmd { + return nil +} + +// SetFileName sets current file to highlight. +func (m *Model) SetFileName(filename string) tea.Cmd { + m.Filename = filename + + return readFileContentCmd(filename, m.SyntaxTheme) +} + +// SetIsActive sets if the bubble is currently active. +func (m *Model) SetIsActive(active bool) { + m.Active = active +} + +// SetSyntaxTheme sets the syntax theme of the rendered code. +func (m *Model) SetSyntaxTheme(theme string) { + m.SyntaxTheme = theme +} + +// SetSize sets the size of the bubble. +func (m *Model) SetSize(w, h int) { + m.Viewport.Width = w + m.Viewport.Height = h +} + +// GotoTop jumps to the top of the viewport. +func (m *Model) GotoTop() { + m.Viewport.GotoTop() +} + +// Update handles updating the UI of a code bubble. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + + switch msg := msg.(type) { + case syntaxMsg: + m.Filename = "" + m.HighlightedContent = lipgloss.NewStyle(). + Width(m.Viewport.Width). + Height(m.Viewport.Height). + Render(string(msg)) + + m.Viewport.SetContent(m.HighlightedContent) + + return m, nil + case errorMsg: + m.Filename = "" + m.HighlightedContent = lipgloss.NewStyle(). + Width(m.Viewport.Width). + Height(m.Viewport.Height). + Render("Error: " + msg.Error()) + + m.Viewport.SetContent(m.HighlightedContent) + + return m, nil + } + + if m.Active { + m.Viewport, cmd = m.Viewport.Update(msg) + cmds = append(cmds, cmd) + } + + return m, tea.Batch(cmds...) +} + +// View returns a string representation of the code bubble. +func (m Model) View() string { + return m.Viewport.View() +} diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go new file mode 100644 index 0000000..03a309c --- /dev/null +++ b/filesystem/filesystem.go @@ -0,0 +1,534 @@ +// Package filesystem is a collection of various different filesystem +// helper functions. +package filesystem + +import ( + "archive/zip" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + "time" +) + +// Directory shortcuts. +const ( + CurrentDirectory = "." + PreviousDirectory = ".." + HomeDirectory = "~" + RootDirectory = "/" +) + +// Different types of listings. +const ( + DirectoriesListingType = "directories" + FilesListingType = "files" +) + +// RenameDirectoryItem renames a directory or files given a source and destination. +func RenameDirectoryItem(src, dst string) error { + err := os.Rename(src, dst) + + return errors.Unwrap(err) +} + +// CreateDirectory creates a new directory given a name. +func CreateDirectory(name string) error { + if _, err := os.Stat(name); errors.Is(err, os.ErrNotExist) { + err := os.Mkdir(name, os.ModePerm) + if err != nil { + return errors.Unwrap(err) + } + } + + return nil +} + +// GetDirectoryListing returns a list of files and directories within a given directory. +func GetDirectoryListing(dir string, showHidden bool) ([]fs.DirEntry, error) { + index := 0 + + files, err := os.ReadDir(dir) + if err != nil { + return nil, errors.Unwrap(err) + } + + if !showHidden { + for _, file := range files { + // If the file or directory starts with a dot, + // we know its hidden so dont add it to the array + // of files to return. + if !strings.HasPrefix(file.Name(), ".") { + files[index] = file + index++ + } + } + + // Set files to the list that does not include hidden files. + files = files[:index] + } + + return files, nil +} + +// GetDirectoryListingByType returns a directory listing based on type (directories | files). +func GetDirectoryListingByType(dir, listingType string, showHidden bool) ([]fs.DirEntry, error) { + index := 0 + + files, err := os.ReadDir(dir) + if err != nil { + return nil, errors.Unwrap(err) + } + + for _, file := range files { + switch { + case file.IsDir() && listingType == DirectoriesListingType && !showHidden: + if !strings.HasPrefix(file.Name(), ".") { + files[index] = file + index++ + } + case file.IsDir() && listingType == DirectoriesListingType && showHidden: + files[index] = file + index++ + case !file.IsDir() && listingType == FilesListingType && !showHidden: + if !strings.HasPrefix(file.Name(), ".") { + files[index] = file + index++ + } + case !file.IsDir() && listingType == FilesListingType && showHidden: + files[index] = file + index++ + } + } + + return files[:index], nil +} + +// DeleteDirectory deletes a directory given a name. +func DeleteDirectory(name string) error { + err := os.RemoveAll(name) + + return errors.Unwrap(err) +} + +// GetHomeDirectory returns the users home directory. +func GetHomeDirectory() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", errors.Unwrap(err) + } + + return home, nil +} + +// GetWorkingDirectory returns the current working directory. +func GetWorkingDirectory() (string, error) { + workingDir, err := os.Getwd() + if err != nil { + return "", errors.Unwrap(err) + } + + return workingDir, nil +} + +// DeleteFile deletes a file given a name. +func DeleteFile(name string) error { + err := os.Remove(name) + + return errors.Unwrap(err) +} + +// MoveDirectoryItem moves a file from one place to another. +func MoveDirectoryItem(src, dst string) error { + err := os.Rename(src, dst) + + return errors.Unwrap(err) +} + +// ReadFileContent returns the contents of a file given a name. +func ReadFileContent(name string) (string, error) { + fileContent, err := os.ReadFile(filepath.Clean(name)) + if err != nil { + return "", errors.Unwrap(err) + } + + return string(fileContent), nil +} + +// CreateFile creates a file given a name. +func CreateFile(name string) error { + f, err := os.Create(filepath.Clean(name)) + if err != nil { + return errors.Unwrap(err) + } + + if err = f.Close(); err != nil { + return errors.Unwrap(err) + } + + return errors.Unwrap(err) +} + +// Zip zips a directory given a name. +func Zip(name string) error { + var splitName []string + var output string + + srcFile, err := os.Open(filepath.Clean(name)) + if err != nil { + return errors.Unwrap(err) + } + + defer func() { + err = srcFile.Close() + }() + + fileExtension := filepath.Ext(name) + splitFileName := strings.Split(name, "/") + fileName := splitFileName[len(splitFileName)-1] + switch { + case strings.HasPrefix(fileName, ".") && fileExtension != "" && fileExtension == fileName: + output = fmt.Sprintf("%s_%d.zip", fileName, time.Now().Unix()) + case strings.HasPrefix(fileName, ".") && fileExtension != "" && fileExtension != fileName: + splitName = strings.Split(fileName, ".") + output = fmt.Sprintf(".%s_%d.zip", splitName[1], time.Now().Unix()) + case fileExtension != "": + splitName = strings.Split(fileName, ".") + output = fmt.Sprintf("%s_%d.zip", splitName[0], time.Now().Unix()) + default: + output = fmt.Sprintf("%s_%d.zip", fileName, time.Now().Unix()) + } + + newfile, err := os.Create(filepath.Clean(output)) + if err != nil { + return errors.Unwrap(err) + } + + defer func() { + err = newfile.Close() + }() + + zipWriter := zip.NewWriter(newfile) + defer func() { + err = zipWriter.Close() + }() + + info, err := os.Stat(name) + if err != nil { + return errors.Unwrap(err) + } + + if info.IsDir() { + err = filepath.Walk(name, func(filePath string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + + if err != nil { + return errors.Unwrap(err) + } + + relPath := strings.TrimPrefix(filePath, name) + zipFile, err := zipWriter.Create(relPath) + if err != nil { + return errors.Unwrap(err) + } + + fsFile, err := os.Open(filepath.Clean(filePath)) + if err != nil { + return errors.Unwrap(err) + } + + _, err = io.Copy(zipFile, fsFile) + if err != nil { + return errors.Unwrap(err) + } + + return nil + }) + } else { + var files []string + + // Walk the directory to get a list of files within it and append it to + // the array of files. + err := filepath.Walk(name, func(path string, f fs.FileInfo, err error) error { + if f.Name() != "." && !f.IsDir() { + files = append(files, path) + } + + return nil + }) + + if err != nil { + return errors.Unwrap(err) + } + + for _, file := range files { + zipfile, err := os.Open(filepath.Clean(file)) + if err != nil { + return errors.Unwrap(err) + } + + defer func() { + err = zipfile.Close() + }() + + info, err := zipfile.Stat() + if err != nil { + return errors.Unwrap(err) + } + + header, err := zip.FileInfoHeader(info) + if err != nil { + return errors.Unwrap(err) + } + + header.Method = zip.Deflate + writer, err := zipWriter.CreateHeader(header) + if err != nil { + return errors.Unwrap(err) + } + + _, err = io.Copy(writer, zipfile) + if err != nil { + return errors.Unwrap(err) + } + } + } + + err = zipWriter.Close() + if err != nil { + return errors.Unwrap(err) + } + + return errors.Unwrap(err) +} + +// Unzip unzips a directory given a name. +func Unzip(name string) error { + var output string + + reader, err := zip.OpenReader(name) + if err != nil { + return errors.Unwrap(err) + } + + defer func() { + err = reader.Close() + }() + + if strings.HasPrefix(name, ".") { + output = strings.Split(name, ".")[1] + } else { + output = strings.Split(name, ".")[0] + } + + for _, file := range reader.File { + archiveFile := file.Name + fpath := filepath.Join(output, archiveFile) + + if !strings.HasPrefix(fpath, filepath.Clean(output)+string(os.PathSeparator)) { + return errors.Unwrap(err) + } + + if file.FileInfo().IsDir() { + err = os.MkdirAll(fpath, os.ModePerm) + if err != nil { + return errors.Unwrap(err) + } + + continue + } + + if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { + return errors.Unwrap(err) + } + + outFile, err := os.OpenFile(filepath.Clean(fpath), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) + if err != nil { + return errors.Unwrap(err) + } + + outputFile, err := file.Open() + if err != nil { + return errors.Unwrap(err) + } + + _, err = io.Copy(outFile, outputFile) + if err != nil { + return errors.Unwrap(err) + } + + err = outFile.Close() + if err != nil { + return errors.Unwrap(err) + } + + err = outputFile.Close() + if err != nil { + return errors.Unwrap(err) + } + } + + return errors.Unwrap(err) +} + +// CopyFile copies a file given a name. +func CopyFile(name string) error { + var splitName []string + var output string + + srcFile, err := os.Open(filepath.Clean(name)) + if err != nil { + return errors.Unwrap(err) + } + + defer func() { + err = srcFile.Close() + }() + + fileExtension := filepath.Ext(name) + splitFileName := strings.Split(name, "/") + fileName := splitFileName[len(splitFileName)-1] + switch { + case strings.HasPrefix(fileName, ".") && fileExtension != "" && fileExtension == fileName: + output = fmt.Sprintf("%s_%d", fileName, time.Now().Unix()) + case strings.HasPrefix(fileName, ".") && fileExtension != "" && fileExtension != fileName: + splitName = strings.Split(fileName, ".") + output = fmt.Sprintf(".%s_%d.%s", splitName[1], time.Now().Unix(), splitName[2]) + case fileExtension != "": + splitName = strings.Split(fileName, ".") + output = fmt.Sprintf("%s_%d.%s", splitName[0], time.Now().Unix(), splitName[1]) + default: + output = fmt.Sprintf("%s_%d", fileName, time.Now().Unix()) + } + + destFile, err := os.Create(filepath.Clean(output)) + if err != nil { + return errors.Unwrap(err) + } + + defer func() { + err = destFile.Close() + }() + + _, err = io.Copy(destFile, srcFile) + if err != nil { + return errors.Unwrap(err) + } + + err = destFile.Sync() + if err != nil { + return errors.Unwrap(err) + } + + return errors.Unwrap(err) +} + +// CopyDirectory copies a directory given a name. +func CopyDirectory(name string) error { + output := fmt.Sprintf("%s_%d", name, time.Now().Unix()) + + err := filepath.Walk(name, func(path string, info os.FileInfo, err error) error { + relPath := strings.Replace(path, name, "", 1) + + if info.IsDir() { + return fmt.Errorf("%w", os.Mkdir(filepath.Join(output, relPath), os.ModePerm)) + } + + var data, err1 = os.ReadFile(filepath.Join(filepath.Clean(name), filepath.Clean(relPath))) + if err1 != nil { + return errors.Unwrap(err) + } + + return fmt.Errorf("%w", os.WriteFile(filepath.Join(output, relPath), data, os.ModePerm)) + }) + + return errors.Unwrap(err) +} + +// GetDirectoryItemSize calculates the size of a directory or file. +func GetDirectoryItemSize(path string) (int64, error) { + curFile, err := os.Stat(path) + if err != nil { + return 0, errors.Unwrap(err) + } + + if !curFile.IsDir() { + return curFile.Size(), nil + } + + var size int64 + err = filepath.WalkDir(path, func(path string, entry os.DirEntry, err error) error { + if err != nil { + return errors.Unwrap(err) + } + + fileInfo, err := entry.Info() + if err != nil { + return errors.Unwrap(err) + } + + if !entry.IsDir() { + size += fileInfo.Size() + } + + return errors.Unwrap(err) + }) + + return size, errors.Unwrap(err) +} + +// FindFilesByName returns files found based on a name. +func FindFilesByName(name, dir string) ([]string, []fs.DirEntry, error) { + var paths []string + var entries []fs.DirEntry + + err := filepath.WalkDir(dir, func(path string, entry os.DirEntry, err error) error { + if err != nil { + return filepath.SkipDir + } + + if strings.Contains(entry.Name(), name) { + paths = append(paths, path) + entries = append(entries, entry) + } + + return errors.Unwrap(err) + }) + + return paths, entries, errors.Unwrap(err) +} + +// WriteToFile writes content to a file, overwriting content if it exists. +func WriteToFile(path, content string) error { + file, err := os.OpenFile(filepath.Clean(path), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) + if err != nil { + return errors.Unwrap(err) + } + + workingDir, err := os.Getwd() + if err != nil { + return errors.Unwrap(err) + } + + _, err = file.WriteString(fmt.Sprintf("%s\n", filepath.Join(workingDir, content))) + if err != nil { + err = file.Close() + if err != nil { + return errors.Unwrap(err) + } + + return errors.Unwrap(err) + } + + err = file.Close() + if err != nil { + return errors.Unwrap(err) + } + + return errors.Unwrap(err) +} diff --git a/filetree/commands.go b/filetree/commands.go new file mode 100644 index 0000000..0ff9c64 --- /dev/null +++ b/filetree/commands.go @@ -0,0 +1,75 @@ +package filetree + +import ( + "fmt" + "os" + "path/filepath" + + tea "github.com/charmbracelet/bubbletea" + "github.com/mistakenelf/fm/filesystem" +) + +type getDirectoryListingMsg []DirectoryItem +type errorMsg error + +// getDirectoryListingCmd updates the directory listing based on the name of the directory provided. +func getDirectoryListingCmd(directoryName string, showHidden bool) tea.Cmd { + return func() tea.Msg { + var err error + var directoryItems []DirectoryItem + + if directoryName == filesystem.HomeDirectory { + directoryName, err = filesystem.GetHomeDirectory() + if err != nil { + return errorMsg(err) + } + } + + directoryInfo, err := os.Stat(directoryName) + if err != nil { + return errorMsg(err) + } + + if !directoryInfo.IsDir() { + return nil + } + + files, err := filesystem.GetDirectoryListing(directoryName, showHidden) + if err != nil { + return errorMsg(err) + } + + err = os.Chdir(directoryName) + if err != nil { + return errorMsg(err) + } + + workingDirectory, err := filesystem.GetWorkingDirectory() + if err != nil { + return errorMsg(err) + } + + for _, file := range files { + fileInfo, err := file.Info() + if err != nil { + continue + } + + status := fmt.Sprintf("%s %s %s", + fileInfo.ModTime().Format("2006-01-02 15:04:05"), + fileInfo.Mode().String(), + ConvertBytesToSizeString(fileInfo.Size())) + + directoryItems = append(directoryItems, DirectoryItem{ + name: file.Name(), + details: status, + path: filepath.Join(workingDirectory, file.Name()), + extension: filepath.Ext(fileInfo.Name()), + isDirectory: fileInfo.IsDir(), + currentDirectory: workingDirectory, + }) + } + + return getDirectoryListingMsg(directoryItems) + } +} diff --git a/filetree/init.go b/filetree/init.go new file mode 100644 index 0000000..2a34147 --- /dev/null +++ b/filetree/init.go @@ -0,0 +1,10 @@ +package filetree + +import ( + tea "github.com/charmbracelet/bubbletea" + "github.com/mistakenelf/fm/filesystem" +) + +func (m Model) Init() tea.Cmd { + return getDirectoryListingCmd(filesystem.CurrentDirectory, true) +} diff --git a/filetree/keys.go b/filetree/keys.go new file mode 100644 index 0000000..beb0d23 --- /dev/null +++ b/filetree/keys.go @@ -0,0 +1,15 @@ +package filetree + +import "github.com/charmbracelet/bubbles/key" + +type KeyMap struct { + Down key.Binding + Up key.Binding +} + +func DefaultKeyMap() KeyMap { + return KeyMap{ + Down: key.NewBinding(key.WithKeys("j", "down", "ctrl+n"), key.WithHelp("j", "down")), + Up: key.NewBinding(key.WithKeys("k", "up", "ctrl+p"), key.WithHelp("k", "up")), + } +} diff --git a/filetree/methods.go b/filetree/methods.go new file mode 100644 index 0000000..17fb8fc --- /dev/null +++ b/filetree/methods.go @@ -0,0 +1,44 @@ +package filetree + +import "fmt" + +const ( + thousand = 1000 + ten = 10 + fivePercent = 0.0499 +) + +// ConvertBytesToSizeString converts a byte count to a human readable string. +func ConvertBytesToSizeString(size int64) string { + if size < thousand { + return fmt.Sprintf("%dB", size) + } + + suffix := []string{ + "K", // kilo + "M", // mega + "G", // giga + "T", // tera + "P", // peta + "E", // exa + "Z", // zeta + "Y", // yotta + } + + curr := float64(size) / thousand + for _, s := range suffix { + if curr < ten { + return fmt.Sprintf("%.1f%s", curr-fivePercent, s) + } else if curr < thousand { + return fmt.Sprintf("%d%s", int(curr), s) + } + curr /= thousand + } + + return "" +} + +// SetIsActive sets if the bubble is currently active. +func (m *Model) SetIsActive(active bool) { + m.active = active +} diff --git a/filetree/model.go b/filetree/model.go new file mode 100644 index 0000000..fd73fc9 --- /dev/null +++ b/filetree/model.go @@ -0,0 +1,31 @@ +package filetree + +type DirectoryItem struct { + name string + details string + path string + extension string + isDirectory bool + currentDirectory string +} + +type Model struct { + cursor int + files []DirectoryItem + active bool + keyMap KeyMap + min int + max int + height int + width int +} + +func New() Model { + return Model{ + cursor: 0, + active: true, + keyMap: DefaultKeyMap(), + min: 0, + max: 0, + } +} diff --git a/filetree/styles.go b/filetree/styles.go new file mode 100644 index 0000000..a723e69 --- /dev/null +++ b/filetree/styles.go @@ -0,0 +1,7 @@ +package filetree + +import "github.com/charmbracelet/lipgloss" + +var ( + selectedItemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true) +) diff --git a/filetree/update.go b/filetree/update.go new file mode 100644 index 0000000..ef18c4e --- /dev/null +++ b/filetree/update.go @@ -0,0 +1,49 @@ +package filetree + +import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" +) + +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + var ( + cmds []tea.Cmd + ) + + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.height = msg.Height + m.width = msg.Width + m.max = m.height - 1 + case getDirectoryListingMsg: + if msg != nil { + m.files = msg + m.max = max(m.max, m.height-1) + } + case tea.KeyMsg: + switch { + case key.Matches(msg, m.keyMap.Down): + m.cursor++ + if m.cursor >= len(m.files) { + m.cursor = len(m.files) - 1 + } + + if m.cursor > m.max { + m.min++ + m.max++ + } + case key.Matches(msg, m.keyMap.Up): + m.cursor-- + if m.cursor < 0 { + m.cursor = 0 + } + + if m.cursor < m.min { + m.min-- + m.max-- + } + } + } + + return m, tea.Batch(cmds...) +} diff --git a/filetree/view.go b/filetree/view.go new file mode 100644 index 0000000..50775f7 --- /dev/null +++ b/filetree/view.go @@ -0,0 +1,31 @@ +package filetree + +import ( + "strings" + + "github.com/charmbracelet/lipgloss" +) + +func (m Model) View() string { + var fileList strings.Builder + + for i, file := range m.files { + if i < m.min || i > m.max { + continue + } + + if i == m.cursor { + fileList.WriteString(selectedItemStyle.Render(file.name) + "\n") + // fileList.WriteString(selectedItemStyle.Render(file.details) + "\n\n") + } else { + fileList.WriteString(file.name + "\n") + // fileList.WriteString(file.details + "\n\n") + } + } + + for i := lipgloss.Height(fileList.String()); i <= m.height; i++ { + fileList.WriteRune('\n') + } + + return fileList.String() +} diff --git a/go.mod b/go.mod index 740a9da..a5b10b6 100644 --- a/go.mod +++ b/go.mod @@ -3,35 +3,35 @@ module github.com/mistakenelf/fm go 1.22 require ( + github.com/alecthomas/chroma v0.10.0 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 + github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/lipgloss v0.10.0 + github.com/disintegration/imaging v1.6.2 + github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06 + github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mistakenelf/teacup v0.4.1 + github.com/muesli/reflow v0.3.0 github.com/spf13/cobra v1.8.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/alecthomas/chroma v0.10.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/charmbracelet/glamour v0.6.0 // indirect github.com/containerd/console v1.0.4 // indirect - github.com/disintegration/imaging v1.6.2 // indirect github.com/dlclark/regexp2 v1.11.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect - github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/rivo/uniseg v0.4.7 // indirect diff --git a/help/help.go b/help/help.go new file mode 100644 index 0000000..88d74ab --- /dev/null +++ b/help/help.go @@ -0,0 +1,135 @@ +// Package help implements a help bubble which can be used +// to display help information such as keymaps. +package help + +import ( + "fmt" + + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +const ( + keyWidth = 12 +) + +type TitleColor struct { + Background lipgloss.AdaptiveColor + Foreground lipgloss.AdaptiveColor +} + +// Entry represents a single entry in the help bubble. +type Entry struct { + Key string + Description string +} + +// Model represents the properties of a help bubble. +type Model struct { + Viewport viewport.Model + Entries []Entry + Title string + TitleColor TitleColor + Active bool +} + +// generateHelpScreen generates the help text based on the title and entries. +func generateHelpScreen(title string, titleColor TitleColor, entries []Entry, width, height int) string { + helpScreen := "" + + for _, content := range entries { + keyText := lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.AdaptiveColor{Dark: "#ffffff", Light: "#000000"}). + Width(keyWidth). + Render(content.Key) + + descriptionText := lipgloss.NewStyle(). + Foreground(lipgloss.AdaptiveColor{Dark: "#ffffff", Light: "#000000"}). + Render(content.Description) + + row := lipgloss.JoinHorizontal(lipgloss.Top, keyText, descriptionText) + helpScreen += fmt.Sprintf("%s\n", row) + } + + titleText := lipgloss.NewStyle().Bold(true). + Background(titleColor.Background). + Foreground(titleColor.Foreground). + Padding(0, 1). + Italic(true). + Render(title) + + return lipgloss.NewStyle(). + Width(width). + Height(height). + Render(lipgloss.JoinVertical( + lipgloss.Top, + titleText, + helpScreen, + )) +} + +// New creates a new instance of a help bubble. +func New( + active bool, + title string, + titleColor TitleColor, + entries []Entry, +) Model { + viewPort := viewport.New(0, 0) + viewPort.SetContent(generateHelpScreen(title, titleColor, entries, 0, 0)) + + return Model{ + Viewport: viewPort, + Entries: entries, + Title: title, + Active: active, + TitleColor: titleColor, + } +} + +// SetSize sets the size of the help bubble. +func (m *Model) SetSize(w, h int) { + m.Viewport.Width = w + m.Viewport.Height = h + + m.Viewport.SetContent(generateHelpScreen(m.Title, m.TitleColor, m.Entries, m.Viewport.Width, m.Viewport.Height)) +} + +// SetIsActive sets if the bubble is currently active. +func (m *Model) SetIsActive(active bool) { + m.Active = active +} + +// GotoTop jumps to the top of the viewport. +func (m *Model) GotoTop() { + m.Viewport.GotoTop() +} + +// SetTitleColor sets the color of the title. +func (m *Model) SetTitleColor(color TitleColor) { + m.TitleColor = color + + m.Viewport.SetContent(generateHelpScreen(m.Title, m.TitleColor, m.Entries, m.Viewport.Width, m.Viewport.Height)) +} + +// Update handles UI interactions with the help bubble. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + + if m.Active { + m.Viewport, cmd = m.Viewport.Update(msg) + cmds = append(cmds, cmd) + } + + return m, tea.Batch(cmds...) +} + +// View returns a string representation of the help bubble. +func (m Model) View() string { + return m.Viewport.View() +} diff --git a/icons/directories.go b/icons/directories.go new file mode 100644 index 0000000..759b883 --- /dev/null +++ b/icons/directories.go @@ -0,0 +1,62 @@ +package icons + +// IconDir is used to get the icon based on the type of directory. +var IconDir = map[string]*IconInfo{ + "config": IconSet["dir-config"], + ".config": IconSet["dir-config"], + "configs": IconSet["dir-config"], + "configuration": IconSet["dir-config"], + "configurations": IconSet["dir-config"], + "settings": IconSet["dir-config"], + ".settings": IconSet["dir-config"], + "META-INF": IconSet["dir-config"], + "controller": IconSet["dir-controller"], + "controllers": IconSet["dir-controller"], + "service": IconSet["dir-controller"], + "services": IconSet["dir-controller"], + "provider": IconSet["dir-controller"], + "providers": IconSet["dir-controller"], + ".git": IconSet["dir-git"], + "githooks": IconSet["dir-git"], + ".githooks": IconSet["dir-git"], + "submodules": IconSet["dir-git"], + ".submodules": IconSet["dir-git"], + ".github": IconSet["dir-github"], + "node_modules": IconSet["dir-npm"], + "include": IconSet["dir-include"], + "includes": IconSet["dir-include"], + "_includes": IconSet["dir-include"], + "import": IconSet["dir-import"], + "imports": IconSet["dir-import"], + "imported": IconSet["dir-import"], + "uploads": IconSet["dir-upload"], + "upload": IconSet["dir-upload"], + "downloads": IconSet["dir-download"], + "download": IconSet["dir-download"], + "auth": IconSet["dir-secure"], + "authentication": IconSet["dir-secure"], + "secure": IconSet["dir-secure"], + "security": IconSet["dir-secure"], + "cert": IconSet["dir-secure"], + "certs": IconSet["dir-secure"], + "certificate": IconSet["dir-secure"], + "certificates": IconSet["dir-secure"], + "ssl": IconSet["dir-secure"], + "images": IconSet["dir-images"], + "image": IconSet["dir-images"], + "pics": IconSet["dir-images"], + "pic": IconSet["dir-images"], + "pictures": IconSet["dir-images"], + "picture": IconSet["dir-images"], + "img": IconSet["dir-images"], + "icons": IconSet["dir-images"], + "icon": IconSet["dir-images"], + "ico": IconSet["dir-images"], + "screenshot": IconSet["dir-images"], + "screenshots": IconSet["dir-images"], + ".env": IconSet["dir-environment"], + ".environment": IconSet["dir-environment"], + "env": IconSet["dir-environment"], + "environment": IconSet["dir-environment"], + "environments": IconSet["dir-environment"], +} diff --git a/icons/extensions.go b/icons/extensions.go new file mode 100644 index 0000000..c4cd5df --- /dev/null +++ b/icons/extensions.go @@ -0,0 +1,537 @@ +package icons + +// IconExt is used to represent an icon with a specific extension. +var IconExt = map[string]*IconInfo{ + "htm": IconSet["html"], + "html": IconSet["html"], + "xhtml": IconSet["html"], + "html_vm": IconSet["html"], + "asp": IconSet["html"], + "jade": IconSet["pug"], + "pug": IconSet["pug"], + "md": IconSet["markdown"], + "markdown": IconSet["markdown"], + "rst": IconSet["markdown"], + "blink": IconSet["blink"], + "css": IconSet["css"], + "scss": IconSet["sass"], + "sass": IconSet["sass"], + "less": IconSet["less"], + "json": IconSet["json"], + "tsbuildinfo": IconSet["json"], + "json5": IconSet["json"], + "jsonl": IconSet["json"], + "ndjson": IconSet["json"], + "jinja": IconSet["jinja"], + "jinja2": IconSet["jinja"], + "j2": IconSet["jinja"], + "jinja-html": IconSet["jinja"], + "sublime-project": IconSet["sublime"], + "sublime-workspace": IconSet["sublime"], + "yaml": IconSet["yaml"], + "yaml-tmlanguage": IconSet["yaml"], + "yml": IconSet["yaml"], + "xml": IconSet["xml"], + "plist": IconSet["xml"], + "xsd": IconSet["xml"], + "dtd": IconSet["xml"], + "xsl": IconSet["xml"], + "xslt": IconSet["xml"], + "resx": IconSet["xml"], + "iml": IconSet["xml"], + "xquery": IconSet["xml"], + "tmLanguage": IconSet["xml"], + "manifest": IconSet["xml"], + "project": IconSet["xml"], + "png": IconSet["image"], + "jpeg": IconSet["image"], + "jpg": IconSet["image"], + "gif": IconSet["image"], + "ico": IconSet["image"], + "tif": IconSet["image"], + "tiff": IconSet["image"], + "psd": IconSet["image"], + "psb": IconSet["image"], + "ami": IconSet["image"], + "apx": IconSet["image"], + "bmp": IconSet["image"], + "bpg": IconSet["image"], + "brk": IconSet["image"], + "cur": IconSet["image"], + "dds": IconSet["image"], + "dng": IconSet["image"], + "exr": IconSet["image"], + "fpx": IconSet["image"], + "gbr": IconSet["image"], + "img": IconSet["image"], + "jbig2": IconSet["image"], + "jb2": IconSet["image"], + "jng": IconSet["image"], + "jxr": IconSet["image"], + "pbm": IconSet["image"], + "pgf": IconSet["image"], + "pic": IconSet["image"], + "raw": IconSet["image"], + "webp": IconSet["image"], + "eps": IconSet["image"], + "afphoto": IconSet["image"], + "ase": IconSet["image"], + "aseprite": IconSet["image"], + "clip": IconSet["image"], + "cpt": IconSet["image"], + "heif": IconSet["image"], + "heic": IconSet["image"], + "kra": IconSet["image"], + "mdp": IconSet["image"], + "ora": IconSet["image"], + "pdn": IconSet["image"], + "reb": IconSet["image"], + "sai": IconSet["image"], + "tga": IconSet["image"], + "xcf": IconSet["image"], + "js": IconSet["javascript"], + "esx": IconSet["javascript"], + "mj": IconSet["javascript"], + "jsx": IconSet["react"], + "tsx": IconSet["react_ts"], + "ini": IconSet["settings"], + "dlc": IconSet["settings"], + "dll": IconSet["settings"], + "config": IconSet["settings"], + "conf": IconSet["settings"], + "properties": IconSet["settings"], + "prop": IconSet["settings"], + "settings": IconSet["settings"], + "option": IconSet["settings"], + "props": IconSet["settings"], + "toml": IconSet["settings"], + "prefs": IconSet["settings"], + "dotsettings": IconSet["settings"], + "cfg": IconSet["settings"], + "ts": IconSet["typescript"], + "marko": IconSet["markojs"], + "pdf": IconSet["pdf"], + "xlsx": IconSet["table"], + "xls": IconSet["table"], + "csv": IconSet["table"], + "tsv": IconSet["table"], + "vscodeignore": IconSet["vscode"], + "vsixmanifest": IconSet["vscode"], + "vsix": IconSet["vscode"], + "code-workplace": IconSet["vscode"], + "csproj": IconSet["visualstudio"], + "ruleset": IconSet["visualstudio"], + "sln": IconSet["visualstudio"], + "suo": IconSet["visualstudio"], + "vb": IconSet["visualstudio"], + "vbs": IconSet["visualstudio"], + "vcxitems": IconSet["visualstudio"], + "vcxproj": IconSet["visualstudio"], + "pdb": IconSet["database"], + "sql": IconSet["mysql"], + "pks": IconSet["database"], + "pkb": IconSet["database"], + "accdb": IconSet["database"], + "mdb": IconSet["database"], + "sqlite": IconSet["sqlite"], + "sqlite3": IconSet["sqlite"], + "pgsql": IconSet["postgresql"], + "postgres": IconSet["postgresql"], + "psql": IconSet["postgresql"], + "cs": IconSet["csharp"], + "csx": IconSet["csharp"], + "qs": IconSet["qsharp"], + "zip": IconSet["zip"], + "tar": IconSet["zip"], + "gz": IconSet["zip"], + "xz": IconSet["zip"], + "br": IconSet["zip"], + "bzip2": IconSet["zip"], + "gzip": IconSet["zip"], + "brotli": IconSet["zip"], + "7z": IconSet["zip"], + "rar": IconSet["zip"], + "tgz": IconSet["zip"], + "vala": IconSet["vala"], + "zig": IconSet["zig"], + "exe": IconSet["exe"], + "msi": IconSet["exe"], + "java": IconSet["java"], + "jar": IconSet["java"], + "jsp": IconSet["java"], + "c": IconSet["c"], + "m": IconSet["c"], + "i": IconSet["c"], + "mi": IconSet["c"], + "h": IconSet["h"], + "cc": IconSet["cpp"], + "cpp": IconSet["cpp"], + "cxx": IconSet["cpp"], + "c++": IconSet["cpp"], + "cp": IconSet["cpp"], + "mm": IconSet["cpp"], + "mii": IconSet["cpp"], + "ii": IconSet["cpp"], + "hh": IconSet["hpp"], + "hpp": IconSet["hpp"], + "hxx": IconSet["hpp"], + "h++": IconSet["hpp"], + "hp": IconSet["hpp"], + "tcc": IconSet["hpp"], + "inl": IconSet["hpp"], + "go": IconSet["go"], + "py": IconSet["python"], + "pyc": IconSet["python-misc"], + "whl": IconSet["python-misc"], + "url": IconSet["url"], + "sh": IconSet["console"], + "ksh": IconSet["console"], + "csh": IconSet["console"], + "tcsh": IconSet["console"], + "zsh": IconSet["console"], + "bash": IconSet["console"], + "bat": IconSet["console"], + "cmd": IconSet["console"], + "awk": IconSet["console"], + "fish": IconSet["console"], + "ps1": IconSet["powershell"], + "psm1": IconSet["powershell"], + "psd1": IconSet["powershell"], + "ps1xml": IconSet["powershell"], + "psc1": IconSet["powershell"], + "pssc": IconSet["powershell"], + "gradle": IconSet["gradle"], + "doc": IconSet["word"], + "docx": IconSet["word"], + "rtf": IconSet["word"], + "cer": IconSet["certificate"], + "cert": IconSet["certificate"], + "crt": IconSet["certificate"], + "pub": IconSet["key"], + "key": IconSet["key"], + "pem": IconSet["key"], + "asc": IconSet["key"], + "gpg": IconSet["key"], + "woff": IconSet["font"], + "woff2": IconSet["font"], + "ttf": IconSet["font"], + "eot": IconSet["font"], + "suit": IconSet["font"], + "otf": IconSet["font"], + "bmap": IconSet["font"], + "fnt": IconSet["font"], + "odttf": IconSet["font"], + "ttc": IconSet["font"], + "font": IconSet["font"], + "fonts": IconSet["font"], + "sui": IconSet["font"], + "ntf": IconSet["font"], + "mrf": IconSet["font"], + "lib": IconSet["lib"], + "bib": IconSet["lib"], + "rb": IconSet["ruby"], + "erb": IconSet["ruby"], + "fs": IconSet["fsharp"], + "fsx": IconSet["fsharp"], + "fsi": IconSet["fsharp"], + "fsproj": IconSet["fsharp"], + "swift": IconSet["swift"], + "ino": IconSet["arduino"], + "dockerignore": IconSet["docker"], + "dockerfile": IconSet["docker"], + "tex": IconSet["tex"], + "sty": IconSet["tex"], + "dtx": IconSet["tex"], + "ltx": IconSet["tex"], + "pptx": IconSet["powerpoint"], + "ppt": IconSet["powerpoint"], + "pptm": IconSet["powerpoint"], + "potx": IconSet["powerpoint"], + "potm": IconSet["powerpoint"], + "ppsx": IconSet["powerpoint"], + "ppsm": IconSet["powerpoint"], + "pps": IconSet["powerpoint"], + "ppam": IconSet["powerpoint"], + "ppa": IconSet["powerpoint"], + "webm": IconSet["video"], + "mkv": IconSet["video"], + "flv": IconSet["video"], + "vob": IconSet["video"], + "ogv": IconSet["video"], + "ogg": IconSet["video"], + "gifv": IconSet["video"], + "avi": IconSet["video"], + "mov": IconSet["video"], + "qt": IconSet["video"], + "wmv": IconSet["video"], + "yuv": IconSet["video"], + "rm": IconSet["video"], + "rmvb": IconSet["video"], + "mp4": IconSet["video"], + "m4v": IconSet["video"], + "mpg": IconSet["video"], + "mp2": IconSet["video"], + "mpeg": IconSet["video"], + "mpe": IconSet["video"], + "mpv": IconSet["video"], + "m2v": IconSet["video"], + "vdi": IconSet["virtual"], + "vbox": IconSet["virtual"], + "vbox-prev": IconSet["virtual"], + "ics": IconSet["email"], + "mp3": IconSet["audio"], + "flac": IconSet["audio"], + "m4a": IconSet["audio"], + "wma": IconSet["audio"], + "aiff": IconSet["audio"], + "coffee": IconSet["coffee"], + "cson": IconSet["coffee"], + "iced": IconSet["coffee"], + "txt": IconSet["document"], + "graphql": IconSet["graphql"], + "gql": IconSet["graphql"], + "rs": IconSet["rust"], + "raml": IconSet["raml"], + "xaml": IconSet["xaml"], + "hs": IconSet["haskell"], + "kt": IconSet["kotlin"], + "kts": IconSet["kotlin"], + "patch": IconSet["git"], + "lua": IconSet["lua"], + "clj": IconSet["clojure"], + "cljs": IconSet["clojure"], + "cljc": IconSet["clojure"], + "groovy": IconSet["groovy"], + "r": IconSet["r"], + "rmd": IconSet["r"], + "dart": IconSet["dart"], + "as": IconSet["actionscript"], + "mxml": IconSet["mxml"], + "ahk": IconSet["autohotkey"], + "swf": IconSet["flash"], + "swc": IconSet["swc"], + "cmake": IconSet["cmake"], + "asm": IconSet["assembly"], + "a51": IconSet["assembly"], + "inc": IconSet["assembly"], + "nasm": IconSet["assembly"], + "s": IconSet["assembly"], + "ms": IconSet["assembly"], + "agc": IconSet["assembly"], + "ags": IconSet["assembly"], + "aea": IconSet["assembly"], + "argus": IconSet["assembly"], + "mitigus": IconSet["assembly"], + "binsource": IconSet["assembly"], + "vue": IconSet["vue"], + "ml": IconSet["ocaml"], + "mli": IconSet["ocaml"], + "cmx": IconSet["ocaml"], + "lock": IconSet["lock"], + "hbs": IconSet["handlebars"], + "mustache": IconSet["handlebars"], + "pm": IconSet["perl"], + "raku": IconSet["perl"], + "hx": IconSet["haxe"], + "pp": IconSet["puppet"], + "ex": IconSet["elixir"], + "exs": IconSet["elixir"], + "eex": IconSet["elixir"], + "leex": IconSet["elixir"], + "erl": IconSet["erlang"], + "twig": IconSet["twig"], + "jl": IconSet["julia"], + "elm": IconSet["elm"], + "pure": IconSet["purescript"], + "purs": IconSet["purescript"], + "tpl": IconSet["smarty"], + "styl": IconSet["stylus"], + "merlin": IconSet["merlin"], + "v": IconSet["verilog"], + "vhd": IconSet["verilog"], + "sv": IconSet["verilog"], + "svh": IconSet["verilog"], + "robot": IconSet["robot"], + "sol": IconSet["solidity"], + "yang": IconSet["yang"], + "mjml": IconSet["mjml"], + "tf": IconSet["terraform"], + "tfvars": IconSet["terraform"], + "tfstate": IconSet["terraform"], + "applescript": IconSet["applescript"], + "ipa": IconSet["applescript"], + "cake": IconSet["cake"], + "nim": IconSet["nim"], + "nimble": IconSet["nim"], + "apib": IconSet["apiblueprint"], + "apiblueprint": IconSet["apiblueprint"], + "pcss": IconSet["postcss"], + "sss": IconSet["postcss"], + "todo": IconSet["todo"], + "nix": IconSet["nix"], + "slim": IconSet["slim"], + "http": IconSet["http"], + "rest": IconSet["http"], + "apk": IconSet["android"], + "env": IconSet["tune"], + "jenkinsfile": IconSet["jenkins"], + "jenkins": IconSet["jenkins"], + "log": IconSet["log"], + "ejs": IconSet["ejs"], + "djt": IconSet["django"], + "pot": IconSet["i18n"], + "po": IconSet["i18n"], + "mo": IconSet["i18n"], + "d": IconSet["d"], + "mdx": IconSet["mdx"], + "gd": IconSet["godot"], + "godot": IconSet["godot-assets"], + "tres": IconSet["godot-assets"], + "tscn": IconSet["godot-assets"], + "azcli": IconSet["azure"], + "vagrantfile": IconSet["vagrant"], + "cshtml": IconSet["razor"], + "vbhtml": IconSet["razor"], + "ad": IconSet["asciidoc"], + "adoc": IconSet["asciidoc"], + "asciidoc": IconSet["asciidoc"], + "edge": IconSet["edge"], + "ss": IconSet["scheme"], + "scm": IconSet["scheme"], + "stl": IconSet["3d"], + "obj": IconSet["3d"], + "ac": IconSet["3d"], + "blend": IconSet["3d"], + "mesh": IconSet["3d"], + "mqo": IconSet["3d"], + "pmd": IconSet["3d"], + "pmx": IconSet["3d"], + "skp": IconSet["3d"], + "vac": IconSet["3d"], + "vdp": IconSet["3d"], + "vox": IconSet["3d"], + "svg": IconSet["svg"], + "vimrc": IconSet["vim"], + "gvimrc": IconSet["vim"], + "exrc": IconSet["vim"], + "moon": IconSet["moonscript"], + "iso": IconSet["disc"], + "f": IconSet["fortran"], + "f77": IconSet["fortran"], + "f90": IconSet["fortran"], + "f95": IconSet["fortran"], + "f03": IconSet["fortran"], + "f08": IconSet["fortran"], + "tcl": IconSet["tcl"], + "liquid": IconSet["liquid"], + "p": IconSet["prolog"], + "pro": IconSet["prolog"], + "coco": IconSet["coconut"], + "sketch": IconSet["sketch"], + "opam": IconSet["opam"], + "dhallb": IconSet["dhall"], + "pwn": IconSet["pawn"], + "amx": IconSet["pawn"], + "dhall": IconSet["dhall"], + "pas": IconSet["pascal"], + "unity": IconSet["shaderlab"], + "nupkg": IconSet["nuget"], + "command": IconSet["command"], + "dsc": IconSet["denizenscript"], + "deb": IconSet["debian"], + "rpm": IconSet["redhat"], + "snap": IconSet["ubuntu"], + "ebuild": IconSet["gentoo"], + "pkg": IconSet["applescript"], + "openbsd": IconSet["freebsd"], + // "ls": IconSet["livescript"], + // "re": IconSet["reason"], + // "rei": IconSet["reason"], + // "cmj": IconSet["bucklescript"], + // "nb": IconSet["mathematica"], + // "wl": IconSet["wolframlanguage"], + // "wls": IconSet["wolframlanguage"], + // "njk": IconSet["nunjucks"], + // "nunjucks": IconSet["nunjucks"], + // "au3": IconSet["autoit"], + // "haml": IconSet["haml"], + // "feature": IconSet["cucumber"], + // "riot": IconSet["riot"], + // "tag": IconSet["riot"], + // "vfl": IconSet["vfl"], + // "kl": IconSet["kl"], + // "cfml": IconSet["coldfusion"], + // "cfc": IconSet["coldfusion"], + // "lucee": IconSet["coldfusion"], + // "cfm": IconSet["coldfusion"], + // "cabal": IconSet["cabal"], + // "rql": IconSet["restql"], + // "restql": IconSet["restql"], + // "kv": IconSet["kivy"], + // "graphcool": IconSet["graphcool"], + // "sbt": IconSet["sbt"], + // "cr": IconSet["crystal"], + // "ecr": IconSet["crystal"], + // "cu": IconSet["cuda"], + // "cuh": IconSet["cuda"], + // "def": IconSet["dotjs"], + // "dot": IconSet["dotjs"], + // "jst": IconSet["dotjs"], + // "pde": IconSet["processing"], + // "wpy": IconSet["wepy"], + // "hcl": IconSet["hcl"], + // "san": IconSet["san"], + // "red": IconSet["red"], + // "fxp": IconSet["foxpro"], + // "prg": IconSet["foxpro"], + // "wat": IconSet["webassembly"], + // "wasm": IconSet["webassembly"], + // "ipynb": IconSet["jupyter"], + // "bal": IconSet["ballerina"], + // "balx": IconSet["ballerina"], + // "rkt": IconSet["racket"], + // "bzl": IconSet["bazel"], + // "bazel": IconSet["bazel"], + // "mint": IconSet["mint"], + // "vm": IconSet["velocity"], + // "fhtml": IconSet["velocity"], + // "vtl": IconSet["velocity"], + // "prisma": IconSet["prisma"], + // "abc": IconSet["abc"], + // "lisp": IconSet["lisp"], + // "lsp": IconSet["lisp"], + // "cl": IconSet["lisp"], + // "fast": IconSet["lisp"], + // "svelte": IconSet["svelte"], + // "prw": IconSet["advpl_prw"], + // "prx": IconSet["advpl_prw"], + // "ptm": IconSet["advpl_ptm"], + // "tlpp": IconSet["advpl_tlpp"], + // "ch": IconSet["advpl_include"], + // "4th": IconSet["forth"], + // "fth": IconSet["forth"], + // "frt": IconSet["forth"], + // "iuml": IconSet["uml"], + // "pu": IconSet["uml"], + // "puml": IconSet["uml"], + // "plantuml": IconSet["uml"], + // "wsd": IconSet["uml"], + // "sml": IconSet["sml"], + // "mlton": IconSet["sml"], + // "mlb": IconSet["sml"], + // "sig": IconSet["sml"], + // "fun": IconSet["sml"], + // "cm": IconSet["sml"], + // "lex": IconSet["sml"], + // "use": IconSet["sml"], + // "grm": IconSet["sml"], + // "imba": IconSet["imba"], + // "drawio": IconSet["drawio"], + // "dio": IconSet["drawio"], + // "sas": IconSet["sas"], + // "sas7bdat": IconSet["sas"], + // "sashdat": IconSet["sas"], + // "astore": IconSet["sas"], + // "ast": IconSet["sas"], + // "sast": IconSet["sas"], +} diff --git a/icons/filenames.go b/icons/filenames.go new file mode 100644 index 0000000..31779f9 --- /dev/null +++ b/icons/filenames.go @@ -0,0 +1,415 @@ +package icons + +// IconFileName is used to get the icon based on its filename. +var IconFileName = map[string]*IconInfo{ + ".pug-lintrc": IconSet["pug"], + ".pug-lintrc.js": IconSet["pug"], + ".pug-lintrc.json": IconSet["pug"], + ".jscsrc": IconSet["json"], + ".jshintrc": IconSet["json"], + "composer.lock": IconSet["json"], + ".jsbeautifyrc": IconSet["json"], + ".esformatter": IconSet["json"], + "cdp.pid": IconSet["json"], + ".mjmlconfig": IconSet["json"], + ".htaccess": IconSet["xml"], + ".jshintignore": IconSet["settings"], + ".buildignore": IconSet["settings"], + ".mrconfig": IconSet["settings"], + ".yardopts": IconSet["settings"], + "manifest.mf": IconSet["settings"], + ".clang-format": IconSet["settings"], + ".clang-tidy": IconSet["settings"], + "go.mod": IconSet["go-mod"], + "go.sum": IconSet["go-mod"], + "requirements.txt": IconSet["python-misc"], + "pipfile": IconSet["python-misc"], + ".python-version": IconSet["python-misc"], + "manifest.in": IconSet["python-misc"], + "gradle.properties": IconSet["gradle"], + "gradlew": IconSet["gradle"], + "gradle-wrapper.properties": IconSet["gradle"], + "license": IconSet["certificate"], + "license.md": IconSet["certificate"], + "license.txt": IconSet["certificate"], + "licence": IconSet["certificate"], + "licence.md": IconSet["certificate"], + "licence.txt": IconSet["certificate"], + "unlicense": IconSet["certificate"], + "unlicense.md": IconSet["certificate"], + "unlicense.txt": IconSet["certificate"], + ".htpasswd": IconSet["key"], + "gemfile": IconSet["gemfile"], + "dockerfile": IconSet["docker"], + "dockerfile.prod": IconSet["docker"], + "dockerfile.production": IconSet["docker"], + "docker-compose.yml": IconSet["docker"], + "docker-compose.yaml": IconSet["docker"], + "docker-compose.dev.yml": IconSet["docker"], + "docker-compose.local.yml": IconSet["docker"], + "docker-compose.ci.yml": IconSet["docker"], + "docker-compose.override.yml": IconSet["docker"], + "docker-compose.staging.yml": IconSet["docker"], + "docker-compose.prod.yml": IconSet["docker"], + "docker-compose.production.yml": IconSet["docker"], + "docker-compose.test.yml": IconSet["docker"], + ".mailmap": IconSet["email"], + ".graphqlconfig": IconSet["graphql"], + ".gitignore": IconSet["git"], + ".gitconfig": IconSet["git"], + ".gitattributes": IconSet["git"], + ".gitmodules": IconSet["git"], + ".gitkeep": IconSet["git"], + "git-history": IconSet["git"], + ".luacheckrc": IconSet["lua"], + ".Rhistory": IconSet["r"], + "cmakelists.txt": IconSet["cmake"], + "cmakecache.txt": IconSet["cmake"], + "vue.config.js": IconSet["vue-config"], + "vue.config.ts": IconSet["vue-config"], + "nuxt.config.js": IconSet["nuxt"], + "nuxt.config.ts": IconSet["nuxt"], + "security.md": IconSet["lock"], + "security.txt": IconSet["lock"], + "security": IconSet["lock"], + "vercel.json": IconSet["vercel"], + ".vercelignore": IconSet["vercel"], + "now.json": IconSet["vercel"], + ".nowignore": IconSet["vercel"], + "postcss.config.js": IconSet["postcss"], + ".postcssrc.js": IconSet["postcss"], + ".postcssrc": IconSet["postcss"], + ".postcssrc.json": IconSet["postcss"], + ".postcssrc.yml": IconSet["postcss"], + "CNAME": IconSet["http"], + "webpack.js": IconSet["webpack"], + "webpack.ts": IconSet["webpack"], + "webpack.base.js": IconSet["webpack"], + "webpack.base.ts": IconSet["webpack"], + "webpack.config.js": IconSet["webpack"], + "webpack.config.ts": IconSet["webpack"], + "webpack.common.js": IconSet["webpack"], + "webpack.common.ts": IconSet["webpack"], + "webpack.config.common.js": IconSet["webpack"], + "webpack.config.common.ts": IconSet["webpack"], + "webpack.config.common.babel.js": IconSet["webpack"], + "webpack.config.common.babel.ts": IconSet["webpack"], + "webpack.dev.js": IconSet["webpack"], + "webpack.dev.ts": IconSet["webpack"], + "webpack.development.js": IconSet["webpack"], + "webpack.development.ts": IconSet["webpack"], + "webpack.config.dev.js": IconSet["webpack"], + "webpack.config.dev.ts": IconSet["webpack"], + "webpack.config.dev.babel.js": IconSet["webpack"], + "webpack.config.dev.babel.ts": IconSet["webpack"], + "webpack.prod.js": IconSet["webpack"], + "webpack.prod.ts": IconSet["webpack"], + "webpack.production.js": IconSet["webpack"], + "webpack.production.ts": IconSet["webpack"], + "webpack.server.js": IconSet["webpack"], + "webpack.server.ts": IconSet["webpack"], + "webpack.client.js": IconSet["webpack"], + "webpack.client.ts": IconSet["webpack"], + "webpack.config.server.js": IconSet["webpack"], + "webpack.config.server.ts": IconSet["webpack"], + "webpack.config.client.js": IconSet["webpack"], + "webpack.config.client.ts": IconSet["webpack"], + "webpack.config.production.babel.js": IconSet["webpack"], + "webpack.config.production.babel.ts": IconSet["webpack"], + "webpack.config.prod.babel.js": IconSet["webpack"], + "webpack.config.prod.babel.ts": IconSet["webpack"], + "webpack.config.prod.js": IconSet["webpack"], + "webpack.config.prod.ts": IconSet["webpack"], + "webpack.config.production.js": IconSet["webpack"], + "webpack.config.production.ts": IconSet["webpack"], + "webpack.config.staging.js": IconSet["webpack"], + "webpack.config.staging.ts": IconSet["webpack"], + "webpack.config.babel.js": IconSet["webpack"], + "webpack.config.babel.ts": IconSet["webpack"], + "webpack.config.base.babel.js": IconSet["webpack"], + "webpack.config.base.babel.ts": IconSet["webpack"], + "webpack.config.base.js": IconSet["webpack"], + "webpack.config.base.ts": IconSet["webpack"], + "webpack.config.staging.babel.js": IconSet["webpack"], + "webpack.config.staging.babel.ts": IconSet["webpack"], + "webpack.config.coffee": IconSet["webpack"], + "webpack.config.test.js": IconSet["webpack"], + "webpack.config.test.ts": IconSet["webpack"], + "webpack.config.vendor.js": IconSet["webpack"], + "webpack.config.vendor.ts": IconSet["webpack"], + "webpack.config.vendor.production.js": IconSet["webpack"], + "webpack.config.vendor.production.ts": IconSet["webpack"], + "webpack.test.js": IconSet["webpack"], + "webpack.test.ts": IconSet["webpack"], + "webpack.dist.js": IconSet["webpack"], + "webpack.dist.ts": IconSet["webpack"], + "webpackfile.js": IconSet["webpack"], + "webpackfile.ts": IconSet["webpack"], + "ionic.config.json": IconSet["ionic"], + ".io-config.json": IconSet["ionic"], + "gulpfile.js": IconSet["gulp"], + "gulpfile.mjs": IconSet["gulp"], + "gulpfile.ts": IconSet["gulp"], + "gulpfile.babel.js": IconSet["gulp"], + "package.json": IconSet["nodejs"], + "package-lock.json": IconSet["nodejs"], + ".nvmrc": IconSet["nodejs"], + ".esmrc": IconSet["nodejs"], + ".node-version": IconSet["nodejs"], + ".npmignore": IconSet["npm"], + ".npmrc": IconSet["npm"], + ".yarnrc": IconSet["yarn"], + "yarn.lock": IconSet["yarn"], + ".yarnclean": IconSet["yarn"], + ".yarn-integrity": IconSet["yarn"], + "yarn-error.log": IconSet["yarn"], + ".yarnrc.yml": IconSet["yarn"], + ".yarnrc.yaml": IconSet["yarn"], + "androidmanifest.xml": IconSet["android"], + ".env.defaults": IconSet["tune"], + ".env.example": IconSet["tune"], + ".env.sample": IconSet["tune"], + ".env.schema": IconSet["tune"], + ".env.local": IconSet["tune"], + ".env.dev": IconSet["tune"], + ".env.development": IconSet["tune"], + ".env.qa": IconSet["tune"], + ".env.prod": IconSet["tune"], + ".env.production": IconSet["tune"], + ".env.staging": IconSet["tune"], + ".env.preview": IconSet["tune"], + ".env.test": IconSet["tune"], + ".env.testing": IconSet["tune"], + ".env.development.local": IconSet["tune"], + ".env.qa.local": IconSet["tune"], + ".env.production.local": IconSet["tune"], + ".env.staging.local": IconSet["tune"], + ".env.test.local": IconSet["tune"], + ".babelrc": IconSet["babel"], + ".babelrc.js": IconSet["babel"], + ".babelrc.json": IconSet["babel"], + "babel.config.json": IconSet["babel"], + "babel.config.js": IconSet["babel"], + "contributing.md": IconSet["contributing"], + "readme.md": IconSet["readme"], + "readme.txt": IconSet["readme"], + "readme": IconSet["readme"], + "changelog": IconSet["changelog"], + "changelog.md": IconSet["changelog"], + "changelog.txt": IconSet["changelog"], + "changes": IconSet["changelog"], + "changes.md": IconSet["changelog"], + "changes.txt": IconSet["changelog"], + "credits": IconSet["credits"], + "credits.txt": IconSet["credits"], + "credits.md": IconSet["credits"], + "authors": IconSet["authors"], + "authors.md": IconSet["authors"], + "authors.txt": IconSet["authors"], + "favicon.ico": IconSet["favicon"], + "karma.conf.js": IconSet["karma"], + "karma.conf.ts": IconSet["karma"], + "karma.conf.coffee": IconSet["karma"], + "karma.config.js": IconSet["karma"], + "karma.config.ts": IconSet["karma"], + "karma-main.js": IconSet["karma"], + "karma-main.ts": IconSet["karma"], + ".travis.yml": IconSet["travis"], + ".codecov.yml": IconSet["codecov"], + "codecov.yml": IconSet["codecov"], + "protractor.conf.js": IconSet["protractor"], + "protractor.conf.ts": IconSet["protractor"], + "protractor.conf.coffee": IconSet["protractor"], + "protractor.config.js": IconSet["protractor"], + "protractor.config.ts": IconSet["protractor"], + "procfile": IconSet["heroku"], + "procfile.windows": IconSet["heroku"], + ".bowerrc": IconSet["bower"], + "bower.json": IconSet["bower"], + ".eslintrc.js": IconSet["eslint"], + ".eslintrc.cjs": IconSet["eslint"], + ".eslintrc.yaml": IconSet["eslint"], + ".eslintrc.yml": IconSet["eslint"], + ".eslintrc.json": IconSet["eslint"], + ".eslintrc": IconSet["eslint"], + ".eslintignore": IconSet["eslint"], + ".eslintcache": IconSet["eslint"], + "code_of_conduct.md": IconSet["conduct"], + "code_of_conduct.txt": IconSet["conduct"], + "mocha.opts": IconSet["mocha"], + ".mocharc.yml": IconSet["mocha"], + ".mocharc.yaml": IconSet["mocha"], + ".mocharc.js": IconSet["mocha"], + ".mocharc.json": IconSet["mocha"], + ".mocharc.jsonc": IconSet["mocha"], + "jenkinsfile": IconSet["jenkins"], + "firebase.json": IconSet["firebase"], + ".firebaserc": IconSet["firebase"], + "firestore.rules": IconSet["firebase"], + "firestore.indexes.json": IconSet["firebase"], + ".stylelintrc": IconSet["stylelint"], + "stylelint.config.js": IconSet["stylelint"], + ".stylelintrc.json": IconSet["stylelint"], + ".stylelintrc.yaml": IconSet["stylelint"], + ".stylelintrc.yml": IconSet["stylelint"], + ".stylelintrc.js": IconSet["stylelint"], + ".stylelintignore": IconSet["stylelint"], + ".codeclimate.yml": IconSet["code-climate"], + ".prettierrc": IconSet["prettier"], + "prettier.config.js": IconSet["prettier"], + ".prettierrc.js": IconSet["prettier"], + ".prettierrc.json": IconSet["prettier"], + ".prettierrc.yaml": IconSet["prettier"], + ".prettierrc.yml": IconSet["prettier"], + ".prettierignore": IconSet["prettier"], + "gruntfile.js": IconSet["grunt"], + "gruntfile.ts": IconSet["grunt"], + "gruntfile.coffee": IconSet["grunt"], + "gruntfile.babel.js": IconSet["grunt"], + "gruntfile.babel.ts": IconSet["grunt"], + "gruntfile.babel.coffee": IconSet["grunt"], + "jest.config.js": IconSet["jest"], + "jest.config.ts": IconSet["jest"], + "jest.config.cjs": IconSet["jest"], + "jest.config.mjs": IconSet["jest"], + "jest.config.json": IconSet["jest"], + "jest.e2e.config.js": IconSet["jest"], + "jest.e2e.config.ts": IconSet["jest"], + "jest.e2e.config.cjs": IconSet["jest"], + "jest.e2e.config.mjs": IconSet["jest"], + "jest.e2e.config.json": IconSet["jest"], + "jest.setup.js": IconSet["jest"], + "jest.setup.ts": IconSet["jest"], + "jest.json": IconSet["jest"], + ".jestrc": IconSet["jest"], + ".jestrc.js": IconSet["jest"], + ".jestrc.json": IconSet["jest"], + "jest.teardown.js": IconSet["jest"], + "fastfile": IconSet["fastlane"], + "appfile": IconSet["fastlane"], + ".helmignore": IconSet["helm"], + "makefile": IconSet["makefile"], + ".releaserc": IconSet["semantic-release"], + ".releaserc.yaml": IconSet["semantic-release"], + ".releaserc.yml": IconSet["semantic-release"], + ".releaserc.json": IconSet["semantic-release"], + ".releaserc.js": IconSet["semantic-release"], + "release.config.js": IconSet["semantic-release"], + "bitbucket-pipelines.yaml": IconSet["bitbucket"], + "bitbucket-pipelines.yml": IconSet["bitbucket"], + "azure-pipelines.yml": IconSet["azure-pipelines"], + "azure-pipelines.yaml": IconSet["azure-pipelines"], + "vagrantfile": IconSet["vagrant"], + "tailwind.js": IconSet["tailwindcss"], + "tailwind.config.js": IconSet["tailwindcss"], + "codeowners": IconSet["codeowners"], + ".gcloudignore": IconSet["gcp"], + ".huskyrc": IconSet["husky"], + "husky.config.js": IconSet["husky"], + ".huskyrc.json": IconSet["husky"], + ".huskyrc.js": IconSet["husky"], + ".huskyrc.yaml": IconSet["husky"], + ".huskyrc.yml": IconSet["husky"], + ".commitlintrc": IconSet["commitlint"], + ".commitlintrc.js": IconSet["commitlint"], + "commitlint.config.js": IconSet["commitlint"], + ".commitlintrc.json": IconSet["commitlint"], + ".commitlint.yaml": IconSet["commitlint"], + ".commitlint.yml": IconSet["commitlint"], + "dune": IconSet["dune"], + "dune-project": IconSet["dune"], + "roadmap.md": IconSet["roadmap"], + "roadmap.txt": IconSet["roadmap"], + "timeline.md": IconSet["roadmap"], + "timeline.txt": IconSet["roadmap"], + "milestones.md": IconSet["roadmap"], + "milestones.txt": IconSet["roadmap"], + "nuget.config": IconSet["nuget"], + ".nuspec": IconSet["nuget"], + "nuget.exe": IconSet["nuget"], + "stryker.conf.js": IconSet["stryker"], + "stryker.conf.json": IconSet["stryker"], + ".modernizrrc": IconSet["modernizr"], + ".modernizrrc.js": IconSet["modernizr"], + ".modernizrrc.json": IconSet["modernizr"], + "routing.ts": IconSet["routing"], + "routing.tsx": IconSet["routing"], + "routing.js": IconSet["routing"], + "routing.jsx": IconSet["routing"], + "routes.ts": IconSet["routing"], + "routes.tsx": IconSet["routing"], + "routes.js": IconSet["routing"], + "routes.jsx": IconSet["routing"], + // ".vfl": IconSet["vfl"], + // ".kl": IconSet["kl"], + // "project.graphcool": IconSet["graphcool"], + // ".flowconfig": IconSet["flow"], + // ".bithoundrc": IconSet["bithound"], + // ".appveyor.yml": IconSet["appveyor"], + // "appveyor.yml": IconSet["appveyor"], + // "fuse.js": IconSet["fusebox"], + // ".editorconfig": IconSet["editorconfig"], + // ".watchmanconfig": IconSet["watchman"], + // "aurelia.json": IconSet["aurelia"], + // "rollup.config.js": IconSet["rollup"], + // "rollup.config.ts": IconSet["rollup"], + // "rollup-config.js": IconSet["rollup"], + // "rollup-config.ts": IconSet["rollup"], + // "rollup.config.common.js": IconSet["rollup"], + // "rollup.config.common.ts": IconSet["rollup"], + // "rollup.config.base.js": IconSet["rollup"], + // "rollup.config.base.ts": IconSet["rollup"], + // "rollup.config.prod.js": IconSet["rollup"], + // "rollup.config.prod.ts": IconSet["rollup"], + // "rollup.config.dev.js": IconSet["rollup"], + // "rollup.config.dev.ts": IconSet["rollup"], + // "rollup.config.prod.vendor.js": IconSet["rollup"], + // "rollup.config.prod.vendor.ts": IconSet["rollup"], + // ".hhconfig": IconSet["hack"], + // "apollo.config.js": IconSet["apollo"], + // "nodemon.json": IconSet["nodemon"], + // "nodemon-debug.json": IconSet["nodemon"], + // ".hintrc": IconSet["webhint"], + // "browserslist": IconSet["browserlist"], + // ".browserslistrc": IconSet["browserlist"], + // ".snyk": IconSet["snyk"], + // ".drone.yml": IconSet["drone"], + // ".sequelizerc": IconSet["sequelize"], + // "gatsby.config.js": IconSet["gatsby"], + // "gatsby-config.js": IconSet["gatsby"], + // "gatsby-node.js": IconSet["gatsby"], + // "gatsby-browser.js": IconSet["gatsby"], + // "gatsby-ssr.js": IconSet["gatsby"], + // ".wakatime-project": IconSet["wakatime"], + // "circle.yml": IconSet["circleci"], + // ".cfignore": IconSet["cloudfoundry"], + // "wallaby.js": IconSet["wallaby"], + // "wallaby.conf.js": IconSet["wallaby"], + // "stencil.config.js": IconSet["stencil"], + // "stencil.config.ts": IconSet["stencil"], + // ".bazelignore": IconSet["bazel"], + // ".bazelrc": IconSet["bazel"], + // "prisma.yml": IconSet["prisma"], + // ".nycrc": IconSet["istanbul"], + // ".nycrc.json": IconSet["istanbul"], + // "buildkite.yml": IconSet["buildkite"], + // "buildkite.yaml": IconSet["buildkite"], + // "netlify.json": IconSet["netlify"], + // "netlify.yml": IconSet["netlify"], + // "netlify.yaml": IconSet["netlify"], + // "netlify.toml": IconSet["netlify"], + // "nest-cli.json": IconSet["nest"], + // ".nest-cli.json": IconSet["nest"], + // "nestconfig.json": IconSet["nest"], + // ".nestconfig.json": IconSet["nest"], + // ".percy.yml": IconSet["percy"], + // ".gitpod.yml": IconSet["gitpod"], + // "tiltfile": IconSet["tilt"], + // "capacitor.config.json": IconSet["capacitor"], + // ".adonisrc.json": IconSet["adonis"], + // "ace": IconSet["adonis"], + // "meson.build": IconSet["meson"], + // ".buckconfig": IconSet["buck"], + // "nx.json": IconSet["nrwl"], + // ".slugignore": IconSet["slug"], +} diff --git a/icons/glyphs.go b/icons/glyphs.go new file mode 100644 index 0000000..d220b2c --- /dev/null +++ b/icons/glyphs.go @@ -0,0 +1,362 @@ +package icons + +import "fmt" + +// IconInfo is a struct that holds information about an icon. +type IconInfo struct { + icon string + color [3]uint8 + executable bool +} + +// GetGlyph returns the glyph for the icon. +func (i *IconInfo) GetGlyph() string { + return i.icon +} + +// GetColor returns the color for the icon. +func (i *IconInfo) GetColor(f uint8) string { + switch { + case i.executable: + return "\033[38;2;76;175;080m" + default: + return fmt.Sprintf("\033[38;2;%d;%d;%dm", i.color[0], i.color[1], i.color[2]) + } +} + +// MakeExe is a function that returns a new IconInfo struct with the executable flag set to true. +func (i *IconInfo) MakeExe() { + i.executable = true +} + +// IconSet is a map to represent all the icons. +var IconSet = map[string]*IconInfo{ + "html": {icon: "\uf13b", color: [3]uint8{228, 79, 57}}, // html + "markdown": {icon: "\uf853", color: [3]uint8{66, 165, 245}}, // markdown + "css": {icon: "\uf81b", color: [3]uint8{66, 165, 245}}, // css + "css-map": {icon: "\ue749", color: [3]uint8{66, 165, 245}}, // css-map + "sass": {icon: "\ue603", color: [3]uint8{237, 80, 122}}, // sass + "less": {icon: "\ue60b", color: [3]uint8{2, 119, 189}}, // less + "json": {icon: "\ue60b", color: [3]uint8{251, 193, 60}}, // json + "yaml": {icon: "\ue60b", color: [3]uint8{244, 68, 62}}, // yaml + "xml": {icon: "\uf72d", color: [3]uint8{64, 153, 69}}, // xml + "image": {icon: "\uf71e", color: [3]uint8{48, 166, 154}}, // image + "javascript": {icon: "\ue74e", color: [3]uint8{255, 202, 61}}, // javascript + "javascript-map": {icon: "\ue781", color: [3]uint8{255, 202, 61}}, // javascript-map + "test-jsx": {icon: "\uf595", color: [3]uint8{35, 188, 212}}, // test-jsx + "test-js": {icon: "\uf595", color: [3]uint8{255, 202, 61}}, // test-js + "react": {icon: "\ue7ba", color: [3]uint8{35, 188, 212}}, // react + "react_ts": {icon: "\ue7ba", color: [3]uint8{36, 142, 211}}, // react_ts + "settings": {icon: "\uf013", color: [3]uint8{66, 165, 245}}, // settings + "typescript": {icon: "\ue628", color: [3]uint8{3, 136, 209}}, // typescript + "typescript-def": {icon: "\ufbe4", color: [3]uint8{3, 136, 209}}, // typescript-def + "test-ts": {icon: "\uf595", color: [3]uint8{3, 136, 209}}, // test-ts + "pdf": {icon: "\uf724", color: [3]uint8{244, 68, 62}}, // pdf + "table": {icon: "\uf71a", color: [3]uint8{139, 195, 74}}, // table + "visualstudio": {icon: "\ue70c", color: [3]uint8{173, 99, 188}}, // visualstudio + "database": {icon: "\ue706", color: [3]uint8{255, 202, 61}}, // database + "mysql": {icon: "\ue704", color: [3]uint8{1, 94, 134}}, // mysql + "postgresql": {icon: "\ue76e", color: [3]uint8{49, 99, 140}}, // postgresql + "sqlite": {icon: "\ue7c4", color: [3]uint8{1, 57, 84}}, // sqlite + "csharp": {icon: "\uf81a", color: [3]uint8{2, 119, 189}}, // csharp + "zip": {icon: "\uf410", color: [3]uint8{175, 180, 43}}, // zip + "exe": {icon: "\uf2d0", color: [3]uint8{229, 77, 58}}, // exe + "java": {icon: "\uf675", color: [3]uint8{244, 68, 62}}, // java + "c": {icon: "\ufb70", color: [3]uint8{2, 119, 189}}, // c + "cpp": {icon: "\ufb71", color: [3]uint8{2, 119, 189}}, // cpp + "go": {icon: "\ufcd1", color: [3]uint8{32, 173, 194}}, // go + "go-mod": {icon: "\ufcd1", color: [3]uint8{237, 80, 122}}, // go-mod + "go-test": {icon: "\ufcd1", color: [3]uint8{255, 213, 79}}, // go-test + "python": {icon: "\uf81f", color: [3]uint8{52, 102, 143}}, // python + "python-misc": {icon: "\uf820", color: [3]uint8{130, 61, 28}}, // python-misc + "url": {icon: "\uf836", color: [3]uint8{66, 165, 245}}, // url + "console": {icon: "\uf68c", color: [3]uint8{250, 111, 66}}, // console + "word": {icon: "\uf72b", color: [3]uint8{1, 87, 155}}, // word + "certificate": {icon: "\uf623", color: [3]uint8{249, 89, 63}}, // certificate + "key": {icon: "\uf805", color: [3]uint8{48, 166, 154}}, // key + "font": {icon: "\uf031", color: [3]uint8{244, 68, 62}}, // font + "lib": {icon: "\uf831", color: [3]uint8{139, 195, 74}}, // lib + "ruby": {icon: "\ue739", color: [3]uint8{229, 61, 58}}, // ruby + "gemfile": {icon: "\ue21e", color: [3]uint8{229, 61, 58}}, // gemfile + "fsharp": {icon: "\ue7a7", color: [3]uint8{55, 139, 186}}, // fsharp + "swift": {icon: "\ufbe3", color: [3]uint8{249, 95, 63}}, // swift + "docker": {icon: "\uf308", color: [3]uint8{1, 135, 201}}, // docker + "powerpoint": {icon: "\uf726", color: [3]uint8{209, 71, 51}}, // powerpoint + "video": {icon: "\uf72a", color: [3]uint8{253, 154, 62}}, // video + "virtual": {icon: "\uf822", color: [3]uint8{3, 155, 229}}, // virtual + "email": {icon: "\uf6ed", color: [3]uint8{66, 165, 245}}, // email + "audio": {icon: "\ufb75", color: [3]uint8{239, 83, 80}}, // audio + "coffee": {icon: "\uf675", color: [3]uint8{66, 165, 245}}, // coffee + "document": {icon: "\uf718", color: [3]uint8{66, 165, 245}}, // document + "rust": {icon: "\ue7a8", color: [3]uint8{250, 111, 66}}, // rust + "raml": {icon: "\ue60b", color: [3]uint8{66, 165, 245}}, // raml + "xaml": {icon: "\ufb72", color: [3]uint8{66, 165, 245}}, // xaml + "haskell": {icon: "\ue61f", color: [3]uint8{254, 168, 62}}, // haskell + "git": {icon: "\ue702", color: [3]uint8{229, 77, 58}}, // git + "lua": {icon: "\ue620", color: [3]uint8{66, 165, 245}}, // lua + "clojure": {icon: "\ue76a", color: [3]uint8{100, 221, 23}}, // clojure + "groovy": {icon: "\uf2a6", color: [3]uint8{41, 198, 218}}, // groovy + "r": {icon: "\ufcd2", color: [3]uint8{25, 118, 210}}, // r + "dart": {icon: "\ue798", color: [3]uint8{87, 182, 240}}, // dart + "mxml": {icon: "\uf72d", color: [3]uint8{254, 168, 62}}, // mxml + "assembly": {icon: "\uf471", color: [3]uint8{250, 109, 63}}, // assembly + "vue": {icon: "\ufd42", color: [3]uint8{65, 184, 131}}, // vue + "vue-config": {icon: "\ufd42", color: [3]uint8{58, 121, 110}}, // vue-config + "lock": {icon: "\uf83d", color: [3]uint8{255, 213, 79}}, // lock + "handlebars": {icon: "\ue60f", color: [3]uint8{250, 111, 66}}, // handlebars + "perl": {icon: "\ue769", color: [3]uint8{149, 117, 205}}, // perl + "elixir": {icon: "\ue62d", color: [3]uint8{149, 117, 205}}, // elixir + "erlang": {icon: "\ue7b1", color: [3]uint8{244, 68, 62}}, // erlang + "twig": {icon: "\ue61c", color: [3]uint8{155, 185, 47}}, // twig + "julia": {icon: "\ue624", color: [3]uint8{134, 82, 159}}, // julia + "elm": {icon: "\ue62c", color: [3]uint8{96, 181, 204}}, // elm + "smarty": {icon: "\uf834", color: [3]uint8{255, 207, 60}}, // smarty + "stylus": {icon: "\ue600", color: [3]uint8{192, 202, 51}}, // stylus + "verilog": {icon: "\ufb19", color: [3]uint8{250, 111, 66}}, // verilog + "robot": {icon: "\ufba7", color: [3]uint8{249, 89, 63}}, // robot + "solidity": {icon: "\ufcb9", color: [3]uint8{3, 136, 209}}, // solidity + "yang": {icon: "\ufb7e", color: [3]uint8{66, 165, 245}}, // yang + "vercel": {icon: "\uf47e", color: [3]uint8{207, 216, 220}}, // vercel + "applescript": {icon: "\uf302", color: [3]uint8{120, 144, 156}}, // applescript + "cake": {icon: "\uf5ea", color: [3]uint8{250, 111, 66}}, // cake + "nim": {icon: "\uf6a4", color: [3]uint8{255, 202, 61}}, // nim + "todo": {icon: "\uf058", color: [3]uint8{124, 179, 66}}, // task + "nix": {icon: "\uf313", color: [3]uint8{80, 117, 193}}, // nix + "http": {icon: "\uf484", color: [3]uint8{66, 165, 245}}, // http + "webpack": {icon: "\ufc29", color: [3]uint8{142, 214, 251}}, // webpack + "ionic": {icon: "\ue7a9", color: [3]uint8{79, 143, 247}}, // ionic + "gulp": {icon: "\ue763", color: [3]uint8{229, 61, 58}}, // gulp + "nodejs": {icon: "\uf898", color: [3]uint8{139, 195, 74}}, // nodejs + "npm": {icon: "\ue71e", color: [3]uint8{203, 56, 55}}, // npm + "yarn": {icon: "\uf61a", color: [3]uint8{44, 142, 187}}, // yarn + "android": {icon: "\uf531", color: [3]uint8{139, 195, 74}}, // android + "tune": {icon: "\ufb69", color: [3]uint8{251, 193, 60}}, // tune + "contributing": {icon: "\uf64d", color: [3]uint8{255, 202, 61}}, // contributing + "readme": {icon: "\uf7fb", color: [3]uint8{66, 165, 245}}, // readme + "changelog": {icon: "\ufba6", color: [3]uint8{139, 195, 74}}, // changelog + "credits": {icon: "\uf75f", color: [3]uint8{156, 204, 101}}, // credits + "authors": {icon: "\uf0c0", color: [3]uint8{244, 68, 62}}, // authors + "favicon": {icon: "\ue623", color: [3]uint8{255, 213, 79}}, // favicon + "karma": {icon: "\ue622", color: [3]uint8{60, 190, 174}}, // karma + "travis": {icon: "\ue77e", color: [3]uint8{203, 58, 73}}, // travis + "heroku": {icon: "\ue607", color: [3]uint8{105, 99, 185}}, // heroku + "gitlab": {icon: "\uf296", color: [3]uint8{226, 69, 57}}, // gitlab + "bower": {icon: "\ue61a", color: [3]uint8{239, 88, 60}}, // bower + "conduct": {icon: "\uf64b", color: [3]uint8{205, 220, 57}}, // conduct + "jenkins": {icon: "\ue767", color: [3]uint8{240, 214, 183}}, // jenkins + "code-climate": {icon: "\uf7f4", color: [3]uint8{238, 238, 238}}, // code-climate + "log": {icon: "\uf719", color: [3]uint8{175, 180, 43}}, // log + "ejs": {icon: "\ue618", color: [3]uint8{255, 202, 61}}, // ejs + "grunt": {icon: "\ue611", color: [3]uint8{251, 170, 61}}, // grunt + "django": {icon: "\ue71d", color: [3]uint8{67, 160, 71}}, // django + "makefile": {icon: "\uf728", color: [3]uint8{239, 83, 80}}, // makefile + "bitbucket": {icon: "\uf171", color: [3]uint8{31, 136, 229}}, // bitbucket + "d": {icon: "\ue7af", color: [3]uint8{244, 68, 62}}, // d + "mdx": {icon: "\uf853", color: [3]uint8{255, 202, 61}}, // mdx + "azure-pipelines": {icon: "\uf427", color: [3]uint8{20, 101, 192}}, // azure-pipelines + "azure": {icon: "\ufd03", color: [3]uint8{31, 136, 229}}, // azure + "razor": {icon: "\uf564", color: [3]uint8{66, 165, 245}}, // razor + "asciidoc": {icon: "\uf718", color: [3]uint8{244, 68, 62}}, // asciidoc + "edge": {icon: "\uf564", color: [3]uint8{239, 111, 60}}, // edge + "scheme": {icon: "\ufb26", color: [3]uint8{244, 68, 62}}, // scheme + "3d": {icon: "\ue79b", color: [3]uint8{40, 182, 246}}, // 3d + "svg": {icon: "\ufc1f", color: [3]uint8{255, 181, 62}}, // svg + "vim": {icon: "\ue62b", color: [3]uint8{67, 160, 71}}, // vim + "moonscript": {icon: "\uf186", color: [3]uint8{251, 193, 60}}, // moonscript + "codeowners": {icon: "\uf507", color: [3]uint8{175, 180, 43}}, // codeowners + "disc": {icon: "\ue271", color: [3]uint8{176, 190, 197}}, // disc + "fortran": {icon: "F", color: [3]uint8{250, 111, 66}}, // fortran + "tcl": {icon: "\ufbd1", color: [3]uint8{239, 83, 80}}, // tcl + "liquid": {icon: "\ue275", color: [3]uint8{40, 182, 246}}, // liquid + "prolog": {icon: "\ue7a1", color: [3]uint8{239, 83, 80}}, // prolog + "husky": {icon: "\uf8e8", color: [3]uint8{229, 229, 229}}, // husky + "coconut": {icon: "\uf5d2", color: [3]uint8{141, 110, 99}}, // coconut + "sketch": {icon: "\uf6c7", color: [3]uint8{255, 194, 61}}, // sketch + "pawn": {icon: "\ue261", color: [3]uint8{239, 111, 60}}, // pawn + "commitlint": {icon: "\ufc16", color: [3]uint8{43, 150, 137}}, // commitlint + "dhall": {icon: "\uf448", color: [3]uint8{120, 144, 156}}, // dhall + "dune": {icon: "\uf7f4", color: [3]uint8{244, 127, 61}}, // dune + "shaderlab": {icon: "\ufbad", color: [3]uint8{25, 118, 210}}, // shaderlab + "command": {icon: "\ufb32", color: [3]uint8{175, 188, 194}}, // command + "stryker": {icon: "\uf05b", color: [3]uint8{239, 83, 80}}, // stryker + "modernizr": {icon: "\ue720", color: [3]uint8{234, 72, 99}}, // modernizr + "roadmap": {icon: "\ufb6d", color: [3]uint8{48, 166, 154}}, // roadmap + "debian": {icon: "\uf306", color: [3]uint8{211, 61, 76}}, // debian + "ubuntu": {icon: "\uf31c", color: [3]uint8{214, 73, 53}}, // ubuntu + "arch": {icon: "\uf303", color: [3]uint8{33, 142, 202}}, // arch + "redhat": {icon: "\uf316", color: [3]uint8{231, 61, 58}}, // redhat + "gentoo": {icon: "\uf30d", color: [3]uint8{148, 141, 211}}, // gentoo + "linux": {icon: "\ue712", color: [3]uint8{238, 207, 55}}, // linux + "raspberry-pi": {icon: "\uf315", color: [3]uint8{208, 60, 76}}, // raspberry-pi + "manjaro": {icon: "\uf312", color: [3]uint8{73, 185, 90}}, // manjaro + "opensuse": {icon: "\uf314", color: [3]uint8{111, 180, 36}}, // opensuse + "fedora": {icon: "\uf30a", color: [3]uint8{52, 103, 172}}, // fedora + "freebsd": {icon: "\uf30c", color: [3]uint8{175, 44, 42}}, // freebsd + "centOS": {icon: "\uf304", color: [3]uint8{157, 83, 135}}, // centOS + "alpine": {icon: "\uf300", color: [3]uint8{14, 87, 123}}, // alpine + "mint": {icon: "\uf30f", color: [3]uint8{125, 190, 58}}, // mint + "routing": {icon: "\ufb40", color: [3]uint8{67, 160, 71}}, // routing + "laravel": {icon: "\ue73f", color: [3]uint8{248, 80, 81}}, // laravel + "pug": {icon: "\ue60e", color: [3]uint8{239, 204, 163}}, // pug (Not supported by nerdFont) + "blink": {icon: "\uf72a", color: [3]uint8{249, 169, 60}}, // blink (The Foundry Nuke) (Not supported by nerdFont) + "postcss": {icon: "\uf81b", color: [3]uint8{244, 68, 62}}, // postcss (Not supported by nerdFont) + "jinja": {icon: "\ue000", color: [3]uint8{174, 44, 42}}, // jinja (Not supported by nerdFont) + "sublime": {icon: "\ue7aa", color: [3]uint8{239, 148, 58}}, // sublime (Not supported by nerdFont) + "markojs": {icon: "\uf13b", color: [3]uint8{2, 119, 189}}, // markojs (Not supported by nerdFont) + "vscode": {icon: "\ue70c", color: [3]uint8{33, 150, 243}}, // vscode (Not supported by nerdFont) + "qsharp": {icon: "\uf292", color: [3]uint8{251, 193, 60}}, // qsharp (Not supported by nerdFont) + "vala": {icon: "\uf7ab", color: [3]uint8{149, 117, 205}}, // vala (Not supported by nerdFont) + "zig": {icon: "Z", color: [3]uint8{249, 169, 60}}, // zig (Not supported by nerdFont) + "h": {icon: "h", color: [3]uint8{2, 119, 189}}, // h (Not supported by nerdFont) + "hpp": {icon: "h", color: [3]uint8{2, 119, 189}}, // hpp (Not supported by nerdFont) + "powershell": {icon: "\ufcb5", color: [3]uint8{5, 169, 244}}, // powershell (Not supported by nerdFont) + "gradle": {icon: "\ufcc4", color: [3]uint8{29, 151, 167}}, // gradle (Not supported by nerdFont) + "arduino": {icon: "\ue255", color: [3]uint8{35, 151, 156}}, // arduino (Not supported by nerdFont) + "tex": {icon: "\uf783", color: [3]uint8{66, 165, 245}}, // tex (Not supported by nerdFont) + "graphql": {icon: "\ue284", color: [3]uint8{237, 80, 122}}, // graphql (Not supported by nerdFont) + "kotlin": {icon: "\ue70e", color: [3]uint8{139, 195, 74}}, // kotlin (Not supported by nerdFont) + "actionscript": {icon: "\ufb25", color: [3]uint8{244, 68, 62}}, // actionscript (Not supported by nerdFont) + "autohotkey": {icon: "\uf812", color: [3]uint8{76, 175, 80}}, // autohotkey (Not supported by nerdFont) + "flash": {icon: "\uf740", color: [3]uint8{198, 52, 54}}, // flash (Not supported by nerdFont) + "swc": {icon: "\ufbd3", color: [3]uint8{198, 52, 54}}, // swc (Not supported by nerdFont) + "cmake": {icon: "\uf425", color: [3]uint8{178, 178, 179}}, // cmake (Not supported by nerdFont) + "nuxt": {icon: "\ue2a6", color: [3]uint8{65, 184, 131}}, // nuxt (Not supported by nerdFont) + "ocaml": {icon: "\uf1ce", color: [3]uint8{253, 154, 62}}, // ocaml (Not supported by nerdFont) + "haxe": {icon: "\uf425", color: [3]uint8{246, 137, 61}}, // haxe (Not supported by nerdFont) + "puppet": {icon: "\uf595", color: [3]uint8{251, 193, 60}}, // puppet (Not supported by nerdFont) + "purescript": {icon: "\uf670", color: [3]uint8{66, 165, 245}}, // purescript (Not supported by nerdFont) + "merlin": {icon: "\uf136", color: [3]uint8{66, 165, 245}}, // merlin (Not supported by nerdFont) + "mjml": {icon: "\ue714", color: [3]uint8{249, 89, 63}}, // mjml (Not supported by nerdFont) + "terraform": {icon: "\ue20f", color: [3]uint8{92, 107, 192}}, // terraform (Not supported by nerdFont) + "apiblueprint": {icon: "\uf031", color: [3]uint8{66, 165, 245}}, // apiblueprint (Not supported by nerdFont) + "slim": {icon: "\uf24e", color: [3]uint8{245, 129, 61}}, // slim (Not supported by nerdFont) + "babel": {icon: "\uf5a0", color: [3]uint8{253, 217, 59}}, // babel (Not supported by nerdFont) + "codecov": {icon: "\ue37c", color: [3]uint8{237, 80, 122}}, // codecov (Not supported by nerdFont) + "protractor": {icon: "\uf288", color: [3]uint8{229, 61, 58}}, // protractor (Not supported by nerdFont) + "eslint": {icon: "\ufbf6", color: [3]uint8{121, 134, 203}}, // eslint (Not supported by nerdFont) + "mocha": {icon: "\uf6a9", color: [3]uint8{161, 136, 127}}, // mocha (Not supported by nerdFont) + "firebase": {icon: "\ue787", color: [3]uint8{251, 193, 60}}, // firebase (Not supported by nerdFont) + "stylelint": {icon: "\ufb76", color: [3]uint8{207, 216, 220}}, // stylelint (Not supported by nerdFont) + "prettier": {icon: "\uf8e2", color: [3]uint8{86, 179, 180}}, // prettier (Not supported by nerdFont) + "jest": {icon: "J", color: [3]uint8{244, 85, 62}}, // jest (Not supported by nerdFont) + "storybook": {icon: "\ufd2c", color: [3]uint8{237, 80, 122}}, // storybook (Not supported by nerdFont) + "fastlane": {icon: "\ufbff", color: [3]uint8{149, 119, 232}}, // fastlane (Not supported by nerdFont) + "helm": {icon: "\ufd31", color: [3]uint8{32, 173, 194}}, // helm (Not supported by nerdFont) + "i18n": {icon: "\uf7be", color: [3]uint8{121, 134, 203}}, // i18n (Not supported by nerdFont) + "semantic-release": {icon: "\uf70f", color: [3]uint8{245, 245, 245}}, // semantic-release (Not supported by nerdFont) + "godot": {icon: "\ufba7", color: [3]uint8{79, 195, 247}}, // godot (Not supported by nerdFont) + "godot-assets": {icon: "\ufba7", color: [3]uint8{129, 199, 132}}, // godot-assets (Not supported by nerdFont) + "vagrant": {icon: "\uf27d", color: [3]uint8{20, 101, 192}}, // vagrant (Not supported by nerdFont) + "tailwindcss": {icon: "\ufc8b", color: [3]uint8{77, 182, 172}}, // tailwindcss (Not supported by nerdFont) + "gcp": {icon: "\uf662", color: [3]uint8{70, 136, 250}}, // gcp (Not supported by nerdFont) + "opam": {icon: "\uf1ce", color: [3]uint8{255, 213, 79}}, // opam (Not supported by nerdFont) + "pascal": {icon: "\uf8da", color: [3]uint8{3, 136, 209}}, // pascal (Not supported by nerdFont) + "nuget": {icon: "\ue77f", color: [3]uint8{3, 136, 209}}, // nuget (Not supported by nerdFont) + "denizenscript": {icon: "D", color: [3]uint8{255, 213, 79}}, // denizenscript (Not supported by nerdFont) + // "riot": {icon:"\u", color:[3]uint8{255, 255, 255}}, // riot + // "autoit": {icon:"\u", color:[3]uint8{255, 255, 255}}, // autoit + // "livescript": {icon:"\u", color:[3]uint8{255, 255, 255}}, // livescript + // "reason": {icon:"\u", color:[3]uint8{255, 255, 255}}, // reason + // "bucklescript": {icon:"\u", color:[3]uint8{255, 255, 255}}, // bucklescript + // "mathematica": {icon:"\u", color:[3]uint8{255, 255, 255}}, // mathematica + // "wolframlanguage": {icon:"\u", color:[3]uint8{255, 255, 255}}, // wolframlanguage + // "nunjucks": {icon:"\u", color:[3]uint8{255, 255, 255}}, // nunjucks + // "haml": {icon:"\u", color:[3]uint8{255, 255, 255}}, // haml + // "cucumber": {icon:"\u", color:[3]uint8{255, 255, 255}}, // cucumber + // "vfl": {icon:"\u", color:[3]uint8{255, 255, 255}}, // vfl + // "kl": {icon:"\u", color:[3]uint8{255, 255, 255}}, // kl + // "coldfusion": {icon:"\u", color:[3]uint8{255, 255, 255}}, // coldfusion + // "cabal": {icon:"\u", color:[3]uint8{255, 255, 255}}, // cabal + // "restql": {icon:"\u", color:[3]uint8{255, 255, 255}}, // restql + // "kivy": {icon:"\u", color:[3]uint8{255, 255, 255}}, // kivy + // "graphcool": {icon:"\u", color:[3]uint8{255, 255, 255}}, // graphcool + // "sbt": {icon:"\u", color:[3]uint8{255, 255, 255}}, // sbt + // "flow": {icon:"\u", color:[3]uint8{255, 255, 255}}, // flow + // "bithound": {icon:"\u", color:[3]uint8{255, 255, 255}}, // bithound + // "appveyor": {icon:"\u", color:[3]uint8{255, 255, 255}}, // appveyor + // "fusebox": {icon:"\u", color:[3]uint8{255, 255, 255}}, // fusebox + // "editorconfig": {icon:"\u", color:[3]uint8{255, 255, 255}}, // editorconfig + // "watchman": {icon:"\u", color:[3]uint8{255, 255, 255}}, // watchman + // "aurelia": {icon:"\u", color:[3]uint8{255, 255, 255}}, // aurelia + // "rollup": {icon:"\u", color:[3]uint8{255, 255, 255}}, // rollup + // "hack": {icon:"\u", color:[3]uint8{255, 255, 255}}, // hack + // "apollo": {icon:"\u", color:[3]uint8{255, 255, 255}}, // apollo + // "nodemon": {icon:"\u", color:[3]uint8{255, 255, 255}}, // nodemon + // "webhint": {icon:"\u", color:[3]uint8{255, 255, 255}}, // webhint + // "browserlist": {icon:"\u", color:[3]uint8{255, 255, 255}}, // browserlist + // "crystal": {icon:"\u", color:[3]uint8{255, 255, 255}}, // crystal + // "snyk": {icon:"\u", color:[3]uint8{255, 255, 255}}, // snyk + // "drone": {icon:"\u", color:[3]uint8{255, 255, 255}}, // drone + // "cuda": {icon:"\u", color:[3]uint8{255, 255, 255}}, // cuda + // "dotjs": {icon:"\u", color:[3]uint8{255, 255, 255}}, // dotjs + // "sequelize": {icon:"\u", color:[3]uint8{255, 255, 255}}, // sequelize + // "gatsby": {icon:"\u", color:[3]uint8{255, 255, 255}}, // gatsby + // "wakatime": {icon:"\u", color:[3]uint8{255, 255, 255}}, // wakatime + // "circleci": {icon:"\u", color:[3]uint8{255, 255, 255}}, // circleci + // "cloudfoundry": {icon:"\u", color:[3]uint8{255, 255, 255}}, // cloudfoundry + // "processing": {icon:"\u", color:[3]uint8{255, 255, 255}}, // processing + // "wepy": {icon:"\u", color:[3]uint8{255, 255, 255}}, // wepy + // "hcl": {icon:"\u", color:[3]uint8{255, 255, 255}}, // hcl + // "san": {icon:"\u", color:[3]uint8{255, 255, 255}}, // san + // "wallaby": {icon:"\u", color:[3]uint8{255, 255, 255}}, // wallaby + // "stencil": {icon:"\u", color:[3]uint8{255, 255, 255}}, // stencil + // "red": {icon:"\u", color:[3]uint8{255, 255, 255}}, // red + // "webassembly": {icon:"\u", color:[3]uint8{255, 255, 255}}, // webassembly + // "foxpro": {icon:"\u", color:[3]uint8{255, 255, 255}}, // foxpro + // "jupyter": {icon:"\u", color:[3]uint8{255, 255, 255}}, // jupyter + // "ballerina": {icon:"\u", color:[3]uint8{255, 255, 255}}, // ballerina + // "racket": {icon:"\u", color:[3]uint8{255, 255, 255}}, // racket + // "bazel": {icon:"\u", color:[3]uint8{255, 255, 255}}, // bazel + // "mint": {icon:"\u", color:[3]uint8{255, 255, 255}}, // mint + // "velocity": {icon:"\u", color:[3]uint8{255, 255, 255}}, // velocity + // "prisma": {icon:"\u", color:[3]uint8{255, 255, 255}}, // prisma + // "abc": {icon:"\u", color:[3]uint8{255, 255, 255}}, // abc + // "istanbul": {icon:"\u", color:[3]uint8{255, 255, 255}}, // istanbul + // "lisp": {icon:"\u", color:[3]uint8{255, 255, 255}}, // lisp + // "buildkite": {icon:"\u", color:[3]uint8{255, 255, 255}}, // buildkite + // "netlify": {icon:"\u", color:[3]uint8{255, 255, 255}}, // netlify + // "svelte": {icon:"\u", color:[3]uint8{255, 255, 255}}, // svelte + // "nest": {icon:"\u", color:[3]uint8{255, 255, 255}}, // nest + // "percy": {icon:"\u", color:[3]uint8{255, 255, 255}}, // percy + // "gitpod": {icon:"\u", color:[3]uint8{255, 255, 255}}, // gitpod + // "advpl_prw": {icon:"\u", color:[3]uint8{255, 255, 255}}, // advpl_prw + // "advpl_ptm": {icon:"\u", color:[3]uint8{255, 255, 255}}, // advpl_ptm + // "advpl_tlpp": {icon:"\u", color:[3]uint8{255, 255, 255}}, // advpl_tlpp + // "advpl_include": {icon:"\u", color:[3]uint8{255, 255, 255}}, // advpl_include + // "tilt": {icon:"\u", color:[3]uint8{255, 255, 255}}, // tilt + // "capacitor": {icon:"\u", color:[3]uint8{255, 255, 255}}, // capacitor + // "adonis": {icon:"\u", color:[3]uint8{255, 255, 255}}, // adonis + // "forth": {icon:"\u", color:[3]uint8{255, 255, 255}}, // forth + // "uml": {icon:"\u", color:[3]uint8{255, 255, 255}}, // uml + // "meson": {icon:"\u", color:[3]uint8{255, 255, 255}}, // meson + // "buck": {icon:"\u", color:[3]uint8{255, 255, 255}}, // buck + // "sml": {icon:"\u", color:[3]uint8{255, 255, 255}}, // sml + // "nrwl": {icon:"\u", color:[3]uint8{255, 255, 255}}, // nrwl + // "imba": {icon:"\u", color:[3]uint8{255, 255, 255}}, // imba + // "drawio": {icon:"\u", color:[3]uint8{255, 255, 255}}, // drawio + // "sas": {icon:"\u", color:[3]uint8{255, 255, 255}}, // sas + // "slug": {icon:"\u", color:[3]uint8{255, 255, 255}}, // slug + + "dir-config": {icon: "\ue5fc", color: [3]uint8{32, 173, 194}}, // dir-config + "dir-controller": {icon: "\ue5fc", color: [3]uint8{255, 194, 61}}, // dir-controller + "dir-git": {icon: "\ue5fb", color: [3]uint8{250, 111, 66}}, // dir-git + "dir-github": {icon: "\ue5fd", color: [3]uint8{84, 110, 122}}, // dir-github + "dir-npm": {icon: "\ue5fa", color: [3]uint8{203, 56, 55}}, // dir-npm + "dir-include": {icon: "\uf756", color: [3]uint8{3, 155, 229}}, // dir-include + "dir-import": {icon: "\uf756", color: [3]uint8{175, 180, 43}}, // dir-import + "dir-upload": {icon: "\uf758", color: [3]uint8{250, 111, 66}}, // dir-upload + "dir-download": {icon: "\uf74c", color: [3]uint8{76, 175, 80}}, // dir-download + "dir-secure": {icon: "\uf74f", color: [3]uint8{249, 169, 60}}, // dir-secure + "dir-images": {icon: "\uf74e", color: [3]uint8{43, 150, 137}}, // dir-images + "dir-environment": {icon: "\uf74e", color: [3]uint8{102, 187, 106}}, // dir-environment +} + +// IconDef is a map of default icons if none can be found. +var IconDef = map[string]*IconInfo{ + "dir": {icon: "\uf74a", color: [3]uint8{224, 177, 77}}, + "diropen": {icon: "\ufc6e", color: [3]uint8{224, 177, 77}}, + "hiddendir": {icon: "\uf755", color: [3]uint8{224, 177, 77}}, + "exe": {icon: "\uf713", color: [3]uint8{76, 175, 80}}, + "file": {icon: "\uf723", color: [3]uint8{65, 129, 190}}, + "hiddenfile": {icon: "\ufb12", color: [3]uint8{65, 129, 190}}, +} diff --git a/icons/icons.go b/icons/icons.go new file mode 100644 index 0000000..e4550eb --- /dev/null +++ b/icons/icons.go @@ -0,0 +1,87 @@ +// Package icons provides a set of unicode icons +// based on type and extension. +package icons + +import ( + "os" + "strings" +) + +// GetIndicator returns the indicator for the given file. +func GetIndicator(modebit os.FileMode) (i string) { + switch { + case modebit&os.ModeDir > 0: + i = "/" + case modebit&os.ModeNamedPipe > 0: + i = "|" + case modebit&os.ModeSymlink > 0: + i = "@" + case modebit&os.ModeSocket > 0: + i = "=" + case modebit&1000000 > 0: + i = "*" + } + + return i +} + +// GetIcon returns the icon based on its name, extension and indicator. +func GetIcon(name, ext, indicator string) (icon, color string) { + var i *IconInfo + var ok bool + const DOT = '.' + + switch indicator { + case "/": + i, ok = IconDir[strings.ToLower(name+ext)] + if ok { + break + } + if len(name) == 0 || DOT == name[0] { + i = IconDef["hiddendir"] + break + } + i = IconDef["dir"] + default: + i, ok = IconFileName[strings.ToLower(name+ext)] + if ok { + break + } + + if ext == ".go" && strings.HasSuffix(name, "_test") { + i = IconSet["go-test"] + + break + } + + t := strings.Split(name, ".") + if len(t) > 1 && t[0] != "" { + i, ok = IconSubExt[strings.ToLower(t[len(t)-1]+ext)] + if ok { + break + } + } + + i, ok = IconExt[strings.ToLower(strings.TrimPrefix(ext, "."))] + if ok { + break + } + + if len(name) == 0 || DOT == name[0] { + i = IconDef["hiddenfile"] + + break + } + i = IconDef["file"] + } + + if indicator == "*" { + if i.GetGlyph() == "\uf723" { + i = IconDef["exe"] + } + + i.MakeExe() + } + + return i.GetGlyph(), i.GetColor(1) +} diff --git a/icons/sub_extensions.go b/icons/sub_extensions.go new file mode 100644 index 0000000..a293422 --- /dev/null +++ b/icons/sub_extensions.go @@ -0,0 +1,48 @@ +package icons + +// IconSubExt is a map to get the icon based on its subdirectory extension. +var IconSubExt = map[string]*IconInfo{ + "routing.ts": IconSet["routing"], + "routing.tsx": IconSet["routing"], + "routing.js": IconSet["routing"], + "routing.jsx": IconSet["routing"], + "routes.ts": IconSet["routing"], + "routes.tsx": IconSet["routing"], + "routes.js": IconSet["routing"], + "routes.jsx": IconSet["routing"], + "sln.dotsettings": IconSet["settings"], + "d.ts": IconSet["typescript-def"], + "vcxitems.filters": IconSet["visualstudio"], + "vcxproj.filters": IconSet["visualstudio"], + "js.map": IconSet["javascript-map"], + "mjs.map": IconSet["javascript-map"], + "css.map": IconSet["css-map"], + "spec.ts": IconSet["test-ts"], + "e2e-spec.ts": IconSet["test-ts"], + "test.ts": IconSet["test-ts"], + "ts.snap": IconSet["test-ts"], + "spec.tsx": IconSet["test-jsx"], + "test.tsx": IconSet["test-jsx"], + "tsx.snap": IconSet["test-jsx"], + "spec.jsx": IconSet["test-jsx"], + "test.jsx": IconSet["test-jsx"], + "jsx.snap": IconSet["test-jsx"], + "spec.js": IconSet["test-js"], + "e2e-spec.js": IconSet["test-js"], + "test.js": IconSet["test-js"], + "js.snap": IconSet["test-js"], + "tf.json": IconSet["terraform"], + "blade.php": IconSet["laravel"], + "inky.php": IconSet["laravel"], + "gitlab-ci.yml": IconSet["gitlab"], + "stories.js": IconSet["storybook"], + "stories.jsx": IconSet["storybook"], + "story.js": IconSet["storybook"], + "story.jsx": IconSet["storybook"], + "stories.ts": IconSet["storybook"], + "stories.tsx": IconSet["storybook"], + "story.ts": IconSet["storybook"], + "story.tsx": IconSet["storybook"], + "azure-pipelines.yml": IconSet["azure-pipelines"], + "azure-pipelines.yaml": IconSet["azure-pipelines"], +} diff --git a/image/image.go b/image/image.go new file mode 100644 index 0000000..5274c07 --- /dev/null +++ b/image/image.go @@ -0,0 +1,167 @@ +// Package image provides an image bubble which can render +// images as strings. +package image + +import ( + "image" + "os" + "path/filepath" + "strings" + + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/disintegration/imaging" + "github.com/lucasb-eyer/go-colorful" +) + +type convertImageToStringMsg string +type errorMsg error + +// ToString converts an image to a string representation of an image. +func ToString(width int, img image.Image) string { + img = imaging.Resize(img, width, 0, imaging.Lanczos) + b := img.Bounds() + imageWidth := b.Max.X + h := b.Max.Y + str := strings.Builder{} + + for heightCounter := 0; heightCounter < h; heightCounter += 2 { + for x := imageWidth; x < width; x += 2 { + str.WriteString(" ") + } + + for x := 0; x < imageWidth; x++ { + c1, _ := colorful.MakeColor(img.At(x, heightCounter)) + color1 := lipgloss.Color(c1.Hex()) + c2, _ := colorful.MakeColor(img.At(x, heightCounter+1)) + color2 := lipgloss.Color(c2.Hex()) + str.WriteString(lipgloss.NewStyle().Foreground(color1). + Background(color2).Render("▀")) + } + + str.WriteString("\n") + } + + return str.String() +} + +// convertImageToStringCmd redraws the image based on the width provided. +func convertImageToStringCmd(width int, filename string) tea.Cmd { + return func() tea.Msg { + imageContent, err := os.Open(filepath.Clean(filename)) + if err != nil { + return errorMsg(err) + } + + img, _, err := image.Decode(imageContent) + if err != nil { + return errorMsg(err) + } + + imageString := ToString(width, img) + + return convertImageToStringMsg(imageString) + } +} + +// Model represents the properties of a code bubble. +type Model struct { + Viewport viewport.Model + Active bool + Borderless bool + FileName string + ImageString string +} + +// New creates a new instance of code. +func New(active, borderless bool, borderColor lipgloss.AdaptiveColor) Model { + viewPort := viewport.New(0, 0) + + return Model{ + Viewport: viewPort, + Active: active, + } +} + +// Init initializes the code bubble. +func (m Model) Init() tea.Cmd { + return nil +} + +// SetFileName sets current file to highlight, this +// returns a cmd which will highlight the text. +func (m *Model) SetFileName(filename string) tea.Cmd { + m.FileName = filename + + return convertImageToStringCmd(m.Viewport.Width, filename) +} + +// SetSize sets the size of the bubble. +func (m *Model) SetSize(w, h int) tea.Cmd { + m.Viewport.Width = w + m.Viewport.Height = h + + if m.FileName != "" { + return convertImageToStringCmd(m.Viewport.Width, m.FileName) + } + + return nil +} + +// SetIsActive sets if the bubble is currently active +func (m *Model) SetIsActive(active bool) { + m.Active = active +} + +// GotoTop jumps to the top of the viewport. +func (m *Model) GotoTop() { + m.Viewport.GotoTop() +} + +// SetBorderless sets weather or not to show the border. +func (m *Model) SetBorderless(borderless bool) { + m.Borderless = borderless +} + +// Update handles updating the UI of a code bubble. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + + switch msg := msg.(type) { + case convertImageToStringMsg: + m.ImageString = lipgloss.NewStyle(). + Width(m.Viewport.Width). + Height(m.Viewport.Height). + Render(string(msg)) + + m.Viewport.SetContent(m.ImageString) + + return m, nil + case errorMsg: + m.FileName = "" + m.ImageString = lipgloss.NewStyle(). + Width(m.Viewport.Width). + Height(m.Viewport.Height). + Render("Error: " + msg.Error()) + + m.Viewport.SetContent(m.ImageString) + + return m, nil + } + + if m.Active { + m.Viewport, cmd = m.Viewport.Update(msg) + cmds = append(cmds, cmd) + } + + return m, tea.Batch(cmds...) +} + +// View returns a string representation of the code bubble. +func (m Model) View() string { + return m.Viewport.View() +} diff --git a/internal/tui/model.go b/internal/tui/model.go index cdf273b..d097429 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -7,14 +7,14 @@ import ( "github.com/mistakenelf/fm/internal/theme" "github.com/charmbracelet/lipgloss" + "github.com/mistakenelf/fm/help" + "github.com/mistakenelf/fm/statusbar" "github.com/mistakenelf/teacup/code" "github.com/mistakenelf/teacup/csv" "github.com/mistakenelf/teacup/filetree" - "github.com/mistakenelf/teacup/help" "github.com/mistakenelf/teacup/image" "github.com/mistakenelf/teacup/markdown" "github.com/mistakenelf/teacup/pdf" - "github.com/mistakenelf/teacup/statusbar" ) type sessionState int @@ -98,13 +98,11 @@ func New(startDir, selectionPath string) model { helpModel := help.New( false, - cfg.Settings.Borderless, "Help", help.TitleColor{ Background: theme.TitleBackgroundColor, Foreground: theme.TitleForegroundColor, }, - theme.InactiveBoxBorderColor, []help.Entry{ {Key: "ctrl+c, q", Description: "Exit FM"}, {Key: "j/up", Description: "Move up"}, diff --git a/internal/tui/update.go b/internal/tui/update.go index 831ef63..a7f3aa9 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -9,9 +9,9 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/mistakenelf/teacup/help" - "github.com/mistakenelf/teacup/icons" - "github.com/mistakenelf/teacup/statusbar" + "github.com/mistakenelf/fm/help" + "github.com/mistakenelf/fm/icons" + "github.com/mistakenelf/fm/statusbar" ) var forbiddenExtensions = []string{ @@ -55,7 +55,6 @@ func (m *model) deactivateAllBubbles() { // resetBorderColors resets all bubble border colors to default. func (m *model) resetBorderColors() { m.filetree.SetBorderColor(m.theme.InactiveBoxBorderColor) - m.help.SetBorderColor(m.theme.InactiveBoxBorderColor) m.code.SetBorderColor(m.theme.InactiveBoxBorderColor) m.image.SetBorderColor(m.theme.InactiveBoxBorderColor) m.markdown.SetBorderColor(m.theme.InactiveBoxBorderColor) @@ -113,7 +112,6 @@ func (m *model) reloadConfig() []tea.Cmd { m.filetree.SetBorderless(cfg.Settings.Borderless) m.code.SetBorderless(cfg.Settings.Borderless) - m.help.SetBorderless(cfg.Settings.Borderless) m.markdown.SetBorderless(cfg.Settings.Borderless) m.pdf.SetBorderless(cfg.Settings.Borderless) m.image.SetBorderless(cfg.Settings.Borderless) @@ -129,7 +127,6 @@ func (m *model) reloadConfig() []tea.Cmd { m.deactivateAllBubbles() m.help.SetIsActive(true) m.resetBorderColors() - m.help.SetBorderColor(theme.ActiveBoxBorderColor) case showCodeState: m.deactivateAllBubbles() m.code.SetIsActive(true) @@ -207,7 +204,6 @@ func (m *model) toggleBox() { m.deactivateAllBubbles() m.help.SetIsActive(true) m.resetBorderColors() - m.help.SetBorderColor(m.theme.ActiveBoxBorderColor) case showCodeState: m.deactivateAllBubbles() m.code.SetIsActive(true) diff --git a/markdown/markdown.go b/markdown/markdown.go new file mode 100644 index 0000000..7fe8539 --- /dev/null +++ b/markdown/markdown.go @@ -0,0 +1,143 @@ +// Package markdown provides an markdown bubble which can render +// markdown in a pretty manor. +package markdown + +import ( + "errors" + + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" + "github.com/mistakenelf/fm/filesystem" +) + +type renderMarkdownMsg string +type errorMsg error + +// Model represents the properties of a code bubble. +type Model struct { + Viewport viewport.Model + Active bool + FileName string +} + +// RenderMarkdown renders the markdown content with glamour. +func RenderMarkdown(width int, content string) (string, error) { + background := "light" + + if lipgloss.HasDarkBackground() { + background = "dark" + } + + r, _ := glamour.NewTermRenderer( + glamour.WithWordWrap(width), + glamour.WithStandardStyle(background), + ) + + out, err := r.Render(content) + if err != nil { + return "", errors.Unwrap(err) + } + + return out, nil +} + +// renderMarkdownCmd renders text as pretty markdown. +func renderMarkdownCmd(width int, filename string) tea.Cmd { + return func() tea.Msg { + content, err := filesystem.ReadFileContent(filename) + if err != nil { + return errorMsg(err) + } + + markdownContent, err := RenderMarkdown(width, content) + if err != nil { + return errorMsg(err) + } + + return renderMarkdownMsg(markdownContent) + } +} + +// New creates a new instance of markdown. +func New(active bool) Model { + viewPort := viewport.New(0, 0) + + return Model{ + Viewport: viewPort, + Active: active, + } +} + +// Init initializes the code bubble. +func (m Model) Init() tea.Cmd { + return nil +} + +// SetFileName sets current file to render, this +// returns a cmd which will render the text. +func (m *Model) SetFileName(filename string) tea.Cmd { + m.FileName = filename + + return renderMarkdownCmd(m.Viewport.Width, filename) +} + +// SetSize sets the size of the bubble. +func (m *Model) SetSize(w, h int) tea.Cmd { + m.Viewport.Width = w + m.Viewport.Height = h + + if m.FileName != "" { + return renderMarkdownCmd(m.Viewport.Width, m.FileName) + } + + return nil +} + +// GotoTop jumps to the top of the viewport. +func (m *Model) GotoTop() { + m.Viewport.GotoTop() +} + +// SetIsActive sets if the bubble is currently active. +func (m *Model) SetIsActive(active bool) { + m.Active = active +} + +// Update handles updating the UI of a code bubble. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + + switch msg := msg.(type) { + case renderMarkdownMsg: + content := lipgloss.NewStyle(). + Width(m.Viewport.Width). + Height(m.Viewport.Height). + Render(string(msg)) + + m.Viewport.SetContent(content) + + return m, nil + case errorMsg: + m.FileName = "" + m.Viewport.SetContent(msg.Error()) + + return m, nil + } + + if m.Active { + m.Viewport, cmd = m.Viewport.Update(msg) + cmds = append(cmds, cmd) + } + + return m, tea.Batch(cmds...) +} + +// View returns a string representation of the markdown bubble. +func (m Model) View() string { + return m.Viewport.View() +} diff --git a/pdf/pdf.go b/pdf/pdf.go new file mode 100644 index 0000000..ae4693f --- /dev/null +++ b/pdf/pdf.go @@ -0,0 +1,139 @@ +// Package pdf provides an pdf bubble which can render +// pdf files as strings. +package pdf + +import ( + "bytes" + "errors" + + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/ledongthuc/pdf" +) + +type renderPDFMsg string +type errorMsg error + +// Model represents the properties of a pdf bubble. +type Model struct { + Viewport viewport.Model + Active bool + FileName string +} + +// readPdf reads a PDF file given a name. +func readPdf(name string) (string, error) { + file, reader, err := pdf.Open(name) + if err != nil { + return "", errors.Unwrap(err) + } + + defer func() { + if e := file.Close(); e != nil { + err = e + } + }() + + buf := new(bytes.Buffer) + buffer, err := reader.GetPlainText() + + if err != nil { + return "", errors.Unwrap(err) + } + + _, err = buf.ReadFrom(buffer) + if err != nil { + return "", errors.Unwrap(err) + } + + return buf.String(), nil +} + +// renderPDFCmd reads the content of a PDF and returns its content as a string. +func renderPDFCmd(filename string) tea.Cmd { + return func() tea.Msg { + pdfContent, err := readPdf(filename) + if err != nil { + return errorMsg(err) + } + + return renderPDFMsg(pdfContent) + } +} + +// New creates a new instance of a PDF. +func New(active bool) Model { + viewPort := viewport.New(0, 0) + + return Model{ + Viewport: viewPort, + Active: active, + } +} + +// Init initializes the PDF bubble. +func (m Model) Init() tea.Cmd { + return nil +} + +// SetFileName sets current file to render, this +// returns a cmd which will render the pdf. +func (m *Model) SetFileName(filename string) tea.Cmd { + m.FileName = filename + + return renderPDFCmd(filename) +} + +// SetSize sets the size of the bubble. +func (m *Model) SetSize(w, h int) { + m.Viewport.Width = w + m.Viewport.Height = h +} + +// SetIsActive sets if the bubble is currently active. +func (m *Model) SetIsActive(active bool) { + m.Active = active +} + +// GotoTop jumps to the top of the viewport. +func (m *Model) GotoTop() { + m.Viewport.GotoTop() +} + +// Update handles updating the UI of a code bubble. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + + switch msg := msg.(type) { + case renderPDFMsg: + pdfContent := lipgloss.NewStyle(). + Width(m.Viewport.Width). + Height(m.Viewport.Height). + Render(string(msg)) + + m.Viewport.SetContent(pdfContent) + + return m, nil + case errorMsg: + m.FileName = "" + m.Viewport.SetContent(msg.Error()) + + return m, nil + } + + if m.Active { + m.Viewport, cmd = m.Viewport.Update(msg) + cmds = append(cmds, cmd) + } + + return m, tea.Batch(cmds...) +} + +// View returns a string representation of the markdown bubble. +func (m Model) View() string { + return m.Viewport.View() +} diff --git a/statusbar/statusbar.go b/statusbar/statusbar.go new file mode 100644 index 0000000..b5fe452 --- /dev/null +++ b/statusbar/statusbar.go @@ -0,0 +1,119 @@ +// Package statusbar provides an statusbar bubble which can render +// four different status sections +package statusbar + +import ( + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/muesli/reflow/truncate" +) + +// Height represents the height of the statusbar. +const Height = 1 + +// ColorConfig +type ColorConfig struct { + Foreground lipgloss.AdaptiveColor + Background lipgloss.AdaptiveColor +} + +// Model represents the properties of the statusbar. +type Model struct { + Width int + Height int + FirstColumn string + SecondColumn string + ThirdColumn string + FourthColumn string + FirstColumnColors ColorConfig + SecondColumnColors ColorConfig + ThirdColumnColors ColorConfig + FourthColumnColors ColorConfig +} + +// New creates a new instance of the statusbar. +func New(firstColumnColors, secondColumnColors, thirdColumnColors, fourthColumnColors ColorConfig) Model { + return Model{ + FirstColumnColors: firstColumnColors, + SecondColumnColors: secondColumnColors, + ThirdColumnColors: thirdColumnColors, + FourthColumnColors: fourthColumnColors, + } +} + +// SetSize sets the width of the statusbar. +func (m *Model) SetSize(width int) { + m.Width = width +} + +// Update updates the size of the statusbar. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.SetSize(msg.Width) + } + + return m, nil +} + +// SetContent sets the content of the statusbar. +func (m *Model) SetContent(firstColumn, secondColumn, thirdColumn, fourthColumn string) { + m.FirstColumn = firstColumn + m.SecondColumn = secondColumn + m.ThirdColumn = thirdColumn + m.FourthColumn = fourthColumn +} + +// SetColors sets the colors of the 4 columns. +func (m *Model) SetColors(firstColumnColors, secondColumnColors, thirdColumnColors, fourthColumnColors ColorConfig) { + m.FirstColumnColors = firstColumnColors + m.SecondColumnColors = secondColumnColors + m.ThirdColumnColors = thirdColumnColors + m.FourthColumnColors = fourthColumnColors +} + +// View returns a string representation of a statusbar. +func (m Model) View() string { + width := lipgloss.Width + + firstColumn := lipgloss.NewStyle(). + Foreground(m.FirstColumnColors.Foreground). + Background(m.FirstColumnColors.Background). + Padding(0, 1). + Height(Height). + Render(truncate.StringWithTail(m.FirstColumn, 30, "...")) + + thirdColumn := lipgloss.NewStyle(). + Foreground(m.ThirdColumnColors.Foreground). + Background(m.ThirdColumnColors.Background). + Align(lipgloss.Right). + Padding(0, 1). + Height(Height). + Render(m.ThirdColumn) + + fourthColumn := lipgloss.NewStyle(). + Foreground(m.FourthColumnColors.Foreground). + Background(m.FourthColumnColors.Background). + Padding(0, 1). + Height(Height). + Render(m.FourthColumn) + + secondColumn := lipgloss.NewStyle(). + Foreground(m.SecondColumnColors.Foreground). + Background(m.SecondColumnColors.Background). + Padding(0, 1). + Height(Height). + Width(m.Width - width(firstColumn) - width(thirdColumn) - width(fourthColumn)). + Render(truncate.StringWithTail( + m.SecondColumn, + uint(m.Width-width(firstColumn)-width(thirdColumn)-width(fourthColumn)-3), + "..."), + ) + + return lipgloss.JoinHorizontal(lipgloss.Top, + firstColumn, + secondColumn, + thirdColumn, + fourthColumn, + ) +} From 5211c5cc7b5cf8c0173eac7459602bd8c784b2a5 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sat, 9 Mar 2024 19:04:48 -0500 Subject: [PATCH 02/31] feat: render file tree --- filetree/commands.go | 12 +++--- filetree/methods.go | 20 ++++++++++ filetree/model.go | 16 ++++---- filetree/update.go | 16 ++++---- filetree/view.go | 6 +-- go.mod | 3 -- go.sum | 42 +++++++++++++++++-- image/image.go | 8 +--- internal/tui/model.go | 36 +++++------------ internal/tui/update.go | 91 ++++++++---------------------------------- internal/tui/view.go | 2 - 11 files changed, 110 insertions(+), 142 deletions(-) diff --git a/filetree/commands.go b/filetree/commands.go index 0ff9c64..e4922a1 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -61,12 +61,12 @@ func getDirectoryListingCmd(directoryName string, showHidden bool) tea.Cmd { ConvertBytesToSizeString(fileInfo.Size())) directoryItems = append(directoryItems, DirectoryItem{ - name: file.Name(), - details: status, - path: filepath.Join(workingDirectory, file.Name()), - extension: filepath.Ext(fileInfo.Name()), - isDirectory: fileInfo.IsDir(), - currentDirectory: workingDirectory, + Name: file.Name(), + Details: status, + Path: filepath.Join(workingDirectory, file.Name()), + Extension: filepath.Ext(fileInfo.Name()), + IsDirectory: fileInfo.IsDir(), + CurrentDirectory: workingDirectory, }) } diff --git a/filetree/methods.go b/filetree/methods.go index 17fb8fc..0a72597 100644 --- a/filetree/methods.go +++ b/filetree/methods.go @@ -42,3 +42,23 @@ func ConvertBytesToSizeString(size int64) string { func (m *Model) SetIsActive(active bool) { m.active = active } + +// GetSelectedItem returns the currently selected file/dir +func (m Model) GetSelectedItem() DirectoryItem { + if len(m.files) > 0 { + return m.files[m.Cursor] + } + + return DirectoryItem{} +} + +// GetTotalItems returns total number of tree items. +func (m Model) GetTotalItems() int { + return len(m.files) +} + +// SetSize Sets the size of the filetree. +func (m *Model) SetSize(width, height int) { + m.width = width + m.height = height +} diff --git a/filetree/model.go b/filetree/model.go index fd73fc9..879b5bc 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -1,16 +1,16 @@ package filetree type DirectoryItem struct { - name string - details string - path string - extension string - isDirectory bool - currentDirectory string + Name string + Details string + Path string + Extension string + IsDirectory bool + CurrentDirectory string } type Model struct { - cursor int + Cursor int files []DirectoryItem active bool keyMap KeyMap @@ -22,7 +22,7 @@ type Model struct { func New() Model { return Model{ - cursor: 0, + Cursor: 0, active: true, keyMap: DefaultKeyMap(), min: 0, diff --git a/filetree/update.go b/filetree/update.go index ef18c4e..e719375 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -23,22 +23,22 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, m.keyMap.Down): - m.cursor++ - if m.cursor >= len(m.files) { - m.cursor = len(m.files) - 1 + m.Cursor++ + if m.Cursor >= len(m.files) { + m.Cursor = len(m.files) - 1 } - if m.cursor > m.max { + if m.Cursor > m.max { m.min++ m.max++ } case key.Matches(msg, m.keyMap.Up): - m.cursor-- - if m.cursor < 0 { - m.cursor = 0 + m.Cursor-- + if m.Cursor < 0 { + m.Cursor = 0 } - if m.cursor < m.min { + if m.Cursor < m.min { m.min-- m.max-- } diff --git a/filetree/view.go b/filetree/view.go index 50775f7..4e538ff 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -14,11 +14,11 @@ func (m Model) View() string { continue } - if i == m.cursor { - fileList.WriteString(selectedItemStyle.Render(file.name) + "\n") + if i == m.Cursor { + fileList.WriteString(selectedItemStyle.Render(file.Name) + "\n") // fileList.WriteString(selectedItemStyle.Render(file.details) + "\n\n") } else { - fileList.WriteString(file.name + "\n") + fileList.WriteString(file.Name + "\n") // fileList.WriteString(file.details + "\n\n") } } diff --git a/go.mod b/go.mod index a5b10b6..b8bec4b 100644 --- a/go.mod +++ b/go.mod @@ -11,14 +11,12 @@ require ( github.com/disintegration/imaging v1.6.2 github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06 github.com/lucasb-eyer/go-colorful v1.2.0 - github.com/mistakenelf/teacup v0.4.1 github.com/muesli/reflow v0.3.0 github.com/spf13/cobra v1.8.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/containerd/console v1.0.4 // indirect @@ -36,7 +34,6 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/sahilm/fuzzy v0.1.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/yuin/goldmark v1.7.0 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect diff --git a/go.sum b/go.sum index 63ee9f9..ede8492 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,10 @@ +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= +github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= @@ -39,8 +44,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06 h1:kacRlPN7EN++tVpGUorNGPn/4DnB7/DfTY82AOn6ccU= github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -58,8 +61,6 @@ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= -github.com/mistakenelf/teacup v0.4.1 h1:QPNyIqrNKeizeGZc9cE6n+nAsIBu52oUf3bCkfGyBwk= -github.com/mistakenelf/teacup v0.4.1/go.mod h1:8v/aIRCfrae6Uit1WFPHv0xzwi1XELZkAHiTybNSZTk= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -93,35 +94,68 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/image/image.go b/image/image.go index 5274c07..e3c2395 100644 --- a/image/image.go +++ b/image/image.go @@ -69,13 +69,12 @@ func convertImageToStringCmd(width int, filename string) tea.Cmd { type Model struct { Viewport viewport.Model Active bool - Borderless bool FileName string ImageString string } // New creates a new instance of code. -func New(active, borderless bool, borderColor lipgloss.AdaptiveColor) Model { +func New(active bool) Model { viewPort := viewport.New(0, 0) return Model{ @@ -119,11 +118,6 @@ func (m *Model) GotoTop() { m.Viewport.GotoTop() } -// SetBorderless sets weather or not to show the border. -func (m *Model) SetBorderless(borderless bool) { - m.Borderless = borderless -} - // Update handles updating the UI of a code bubble. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var ( diff --git a/internal/tui/model.go b/internal/tui/model.go index d097429..d193568 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -7,14 +7,13 @@ import ( "github.com/mistakenelf/fm/internal/theme" "github.com/charmbracelet/lipgloss" + "github.com/mistakenelf/fm/code" + "github.com/mistakenelf/fm/filetree" "github.com/mistakenelf/fm/help" + "github.com/mistakenelf/fm/image" + "github.com/mistakenelf/fm/markdown" + "github.com/mistakenelf/fm/pdf" "github.com/mistakenelf/fm/statusbar" - "github.com/mistakenelf/teacup/code" - "github.com/mistakenelf/teacup/csv" - "github.com/mistakenelf/teacup/filetree" - "github.com/mistakenelf/teacup/image" - "github.com/mistakenelf/teacup/markdown" - "github.com/mistakenelf/teacup/pdf" ) type sessionState int @@ -25,7 +24,6 @@ const ( showImageState showMarkdownState showPdfState - showCsvState ) // model represents the properties of the UI. @@ -36,7 +34,6 @@ type model struct { image image.Model markdown markdown.Model pdf pdf.Model - csv csv.Model statusbar statusbar.Model state sessionState theme theme.Theme @@ -59,24 +56,14 @@ func New(startDir, selectionPath string) model { syntaxTheme = cfg.Theme.SyntaxTheme.Dark } - filetreeModel := filetree.New( - true, - cfg.Settings.Borderless, - startDir, - selectionPath, - theme.ActiveBoxBorderColor, - theme.SelectedTreeItemColor, - theme.TitleBackgroundColor, - theme.TitleForegroundColor, - ) - filetreeModel.ToggleHelp(false) + filetreeModel := filetree.New() - codeModel := code.New(false, cfg.Settings.Borderless, theme.InactiveBoxBorderColor) + codeModel := code.New(false) codeModel.SetSyntaxTheme(syntaxTheme) - imageModel := image.New(false, cfg.Settings.Borderless, theme.InactiveBoxBorderColor) - markdownModel := markdown.New(false, cfg.Settings.Borderless, theme.InactiveBoxBorderColor) - pdfModel := pdf.New(false, cfg.Settings.Borderless, theme.InactiveBoxBorderColor) + imageModel := image.New(false) + markdownModel := markdown.New(false) + pdfModel := pdf.New(false) statusbarModel := statusbar.New( statusbar.ColorConfig{ Foreground: theme.StatusBarSelectedFileForegroundColor, @@ -130,8 +117,6 @@ func New(startDir, selectionPath string) model { }, ) - csv := csv.New(false) - return model{ filetree: filetreeModel, help: helpModel, @@ -139,7 +124,6 @@ func New(startDir, selectionPath string) model { image: imageModel, markdown: markdownModel, pdf: pdfModel, - csv: csv, statusbar: statusbarModel, theme: theme, config: cfg, diff --git a/internal/tui/update.go b/internal/tui/update.go index a7f3aa9..faf37e9 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -38,7 +38,6 @@ func (m *model) resetViewports() { m.markdown.GotoTop() m.help.GotoTop() m.image.GotoTop() - m.csv.Table.GotoTop() } // deactivateALlBubbles sets all bubbles to inactive. @@ -49,16 +48,6 @@ func (m *model) deactivateAllBubbles() { m.image.SetIsActive(false) m.pdf.SetIsActive(false) m.help.SetIsActive(false) - m.csv.SetIsActive(false) -} - -// resetBorderColors resets all bubble border colors to default. -func (m *model) resetBorderColors() { - m.filetree.SetBorderColor(m.theme.InactiveBoxBorderColor) - m.code.SetBorderColor(m.theme.InactiveBoxBorderColor) - m.image.SetBorderColor(m.theme.InactiveBoxBorderColor) - m.markdown.SetBorderColor(m.theme.InactiveBoxBorderColor) - m.pdf.SetBorderColor(m.theme.InactiveBoxBorderColor) } // reloadConfig reloads the config file and updates the UI. @@ -106,47 +95,26 @@ func (m *model) reloadConfig() []tea.Cmd { }, ) - m.filetree.SetTitleColors(theme.TitleForegroundColor, theme.TitleBackgroundColor) - m.filetree.SetSelectedItemColors(theme.SelectedTreeItemColor) - cmds = append(cmds, m.filetree.ToggleShowIcons(cfg.Settings.ShowIcons)) - - m.filetree.SetBorderless(cfg.Settings.Borderless) - m.code.SetBorderless(cfg.Settings.Borderless) - m.markdown.SetBorderless(cfg.Settings.Borderless) - m.pdf.SetBorderless(cfg.Settings.Borderless) - m.image.SetBorderless(cfg.Settings.Borderless) - if m.activeBox == 0 { m.deactivateAllBubbles() m.filetree.SetIsActive(true) - m.resetBorderColors() - m.filetree.SetBorderColor(theme.ActiveBoxBorderColor) } else { switch m.state { case idleState: m.deactivateAllBubbles() m.help.SetIsActive(true) - m.resetBorderColors() case showCodeState: m.deactivateAllBubbles() m.code.SetIsActive(true) - m.resetBorderColors() - m.code.SetBorderColor(theme.ActiveBoxBorderColor) case showImageState: m.deactivateAllBubbles() m.image.SetIsActive(true) - m.resetBorderColors() - m.image.SetBorderColor(theme.ActiveBoxBorderColor) case showMarkdownState: m.deactivateAllBubbles() m.markdown.SetIsActive(true) - m.resetBorderColors() - m.markdown.SetBorderColor(theme.ActiveBoxBorderColor) case showPdfState: m.deactivateAllBubbles() m.markdown.SetIsActive(true) - m.resetBorderColors() - m.pdf.SetBorderColor(theme.ActiveBoxBorderColor) } } @@ -158,31 +126,27 @@ func (m *model) openFile() []tea.Cmd { var cmds []tea.Cmd selectedFile := m.filetree.GetSelectedItem() - if !selectedFile.IsDirectory() { + if !selectedFile.IsDirectory { m.resetViewports() switch { - case selectedFile.FileExtension() == ".png" || selectedFile.FileExtension() == ".jpg" || selectedFile.FileExtension() == ".jpeg": + case selectedFile.Extension == ".png" || selectedFile.Extension == ".jpg" || selectedFile.Extension == ".jpeg": m.state = showImageState - readFileCmd := m.image.SetFileName(selectedFile.FileName()) + readFileCmd := m.image.SetFileName(selectedFile.Name) cmds = append(cmds, readFileCmd) - case selectedFile.FileExtension() == ".md" && m.config.Settings.PrettyMarkdown: + case selectedFile.Extension == ".md" && m.config.Settings.PrettyMarkdown: m.state = showMarkdownState - markdownCmd := m.markdown.SetFileName(selectedFile.FileName()) + markdownCmd := m.markdown.SetFileName(selectedFile.Name) cmds = append(cmds, markdownCmd) - case selectedFile.FileExtension() == ".pdf": + case selectedFile.Extension == ".pdf": m.state = showPdfState - pdfCmd := m.pdf.SetFileName(selectedFile.FileName()) + pdfCmd := m.pdf.SetFileName(selectedFile.Name) cmds = append(cmds, pdfCmd) - case selectedFile.FileExtension() == ".csv": - m.state = showCsvState - csvCmd := m.csv.SetFileName(selectedFile.FileName()) - cmds = append(cmds, csvCmd) - case contains(forbiddenExtensions, selectedFile.FileExtension()): + case contains(forbiddenExtensions, selectedFile.Extension): return nil default: m.state = showCodeState - readFileCmd := m.code.SetFileName(selectedFile.FileName()) + readFileCmd := m.code.SetFileName(selectedFile.Name) cmds = append(cmds, readFileCmd) } } @@ -196,37 +160,23 @@ func (m *model) toggleBox() { if m.activeBox == 0 { m.deactivateAllBubbles() m.filetree.SetIsActive(true) - m.resetBorderColors() - m.filetree.SetBorderColor(m.theme.ActiveBoxBorderColor) } else { switch m.state { case idleState: m.deactivateAllBubbles() m.help.SetIsActive(true) - m.resetBorderColors() case showCodeState: m.deactivateAllBubbles() m.code.SetIsActive(true) - m.resetBorderColors() - m.code.SetBorderColor(m.theme.ActiveBoxBorderColor) case showImageState: m.deactivateAllBubbles() m.image.SetIsActive(true) - m.resetBorderColors() - m.image.SetBorderColor(m.theme.ActiveBoxBorderColor) case showMarkdownState: m.deactivateAllBubbles() m.markdown.SetIsActive(true) - m.resetBorderColors() - m.markdown.SetBorderColor(m.theme.ActiveBoxBorderColor) case showPdfState: m.deactivateAllBubbles() m.markdown.SetIsActive(true) - m.resetBorderColors() - m.pdf.SetBorderColor(m.theme.ActiveBoxBorderColor) - case showCsvState: - m.deactivateAllBubbles() - m.help.SetIsActive(true) } } } @@ -239,9 +189,9 @@ func (m *model) updateStatusbar() { } m.statusbar.SetContent( - m.filetree.GetSelectedItem().ShortName(), - m.filetree.GetSelectedItem().CurrentDirectory(), - fmt.Sprintf("%d/%d", m.filetree.Cursor(), m.filetree.TotalItems()), + m.filetree.GetSelectedItem().Name, + m.filetree.GetSelectedItem().CurrentDirectory, + fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), logoText, ) } @@ -279,22 +229,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.code.SetSize(halfSize, bubbleHeight) m.pdf.SetSize(halfSize, bubbleHeight) m.statusbar.SetSize(msg.Width) - m.csv.SetSize(msg.Width, bubbleHeight) - cmds = append(cmds, m.filetree.ToggleShowIcons(m.config.Settings.ShowIcons)) cmds = append(cmds, resizeImgCmd, markdownCmd) case tea.KeyMsg: switch { case key.Matches(msg, m.keys.Quit): return m, tea.Quit case key.Matches(msg, m.keys.Exit): - if !m.filetree.IsFiltering() { - return m, tea.Quit - } - case key.Matches(msg, m.keys.ReloadConfig): - if !m.filetree.IsFiltering() { - cmds = append(cmds, tea.Batch(m.reloadConfig()...)) - } + return m, tea.Quit case key.Matches(msg, m.keys.OpenFile): cmds = append(cmds, tea.Batch(m.openFile()...)) case key.Matches(msg, m.keys.ToggleBox): @@ -302,7 +244,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } - m.updateStatusbar() + if m.filetree.GetSelectedItem().Name != "" { + m.updateStatusbar() + } m.code, cmd = m.code.Update(msg) cmds = append(cmds, cmd) @@ -319,8 +263,5 @@ 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) - return m, tea.Batch(cmds...) } diff --git a/internal/tui/view.go b/internal/tui/view.go index 649c76e..f8a5a01 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -20,8 +20,6 @@ func (m model) View() string { rightBox = m.pdf.View() case showMarkdownState: rightBox = m.markdown.View() - case showCsvState: - rightBox = m.csv.View() } return lipgloss.JoinVertical(lipgloss.Top, From 27863828c6d7be7a55fd375455ade734e6df20a3 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sat, 9 Mar 2024 19:09:32 -0500 Subject: [PATCH 03/31] chore: cleanup --- internal/tui/helpers.go | 12 ++++++ internal/tui/keys.go | 14 +++---- internal/tui/update.go | 90 +---------------------------------------- 3 files changed, 19 insertions(+), 97 deletions(-) create mode 100644 internal/tui/helpers.go diff --git a/internal/tui/helpers.go b/internal/tui/helpers.go new file mode 100644 index 0000000..d5252a6 --- /dev/null +++ b/internal/tui/helpers.go @@ -0,0 +1,12 @@ +package tui + +// contains returns true if the slice contains the string. +func contains(s []string, str string) bool { + for _, v := range s { + if v == str { + return true + } + } + + return false +} diff --git a/internal/tui/keys.go b/internal/tui/keys.go index 9118e62..2d35e16 100644 --- a/internal/tui/keys.go +++ b/internal/tui/keys.go @@ -4,11 +4,10 @@ import "github.com/charmbracelet/bubbles/key" // KeyMap defines the keybindings for the app. type KeyMap struct { - Quit key.Binding - Exit key.Binding - ToggleBox key.Binding - OpenFile key.Binding - ReloadConfig key.Binding + Quit key.Binding + Exit key.Binding + TogglePane key.Binding + OpenFile key.Binding } // DefaultKeyMap returns a set of default keybindings. @@ -20,14 +19,11 @@ func DefaultKeyMap() KeyMap { Exit: key.NewBinding( key.WithKeys("q"), ), - ToggleBox: key.NewBinding( + TogglePane: key.NewBinding( key.WithKeys("tab"), ), OpenFile: key.NewBinding( key.WithKeys(" "), ), - ReloadConfig: key.NewBinding( - key.WithKeys("ctrl+r"), - ), } } diff --git a/internal/tui/update.go b/internal/tui/update.go index faf37e9..c98470d 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -3,13 +3,8 @@ package tui import ( "fmt" - "github.com/mistakenelf/fm/internal/config" - "github.com/mistakenelf/fm/internal/theme" - "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/mistakenelf/fm/help" "github.com/mistakenelf/fm/icons" "github.com/mistakenelf/fm/statusbar" ) @@ -50,77 +45,6 @@ func (m *model) deactivateAllBubbles() { m.help.SetIsActive(false) } -// reloadConfig reloads the config file and updates the UI. -func (m *model) reloadConfig() []tea.Cmd { - var cmds []tea.Cmd - - cfg, err := config.ParseConfig() - if err != nil { - return nil - } - - m.config = cfg - syntaxTheme := cfg.Theme.SyntaxTheme.Light - if lipgloss.HasDarkBackground() { - syntaxTheme = cfg.Theme.SyntaxTheme.Dark - } - - m.code.SetSyntaxTheme(syntaxTheme) - - theme := theme.GetTheme(cfg.Theme.AppTheme) - m.theme = theme - m.statusbar.SetColors( - statusbar.ColorConfig{ - Foreground: theme.StatusBarSelectedFileForegroundColor, - Background: theme.StatusBarSelectedFileBackgroundColor, - }, - statusbar.ColorConfig{ - Foreground: theme.StatusBarBarForegroundColor, - Background: theme.StatusBarBarBackgroundColor, - }, - statusbar.ColorConfig{ - Foreground: theme.StatusBarTotalFilesForegroundColor, - Background: theme.StatusBarTotalFilesBackgroundColor, - }, - statusbar.ColorConfig{ - Foreground: theme.StatusBarLogoForegroundColor, - Background: theme.StatusBarLogoBackgroundColor, - }, - ) - - m.help.SetTitleColor( - help.TitleColor{ - Background: theme.TitleBackgroundColor, - Foreground: theme.TitleForegroundColor, - }, - ) - - if m.activeBox == 0 { - m.deactivateAllBubbles() - m.filetree.SetIsActive(true) - } else { - switch m.state { - case idleState: - m.deactivateAllBubbles() - m.help.SetIsActive(true) - case showCodeState: - m.deactivateAllBubbles() - m.code.SetIsActive(true) - case showImageState: - m.deactivateAllBubbles() - m.image.SetIsActive(true) - case showMarkdownState: - m.deactivateAllBubbles() - m.markdown.SetIsActive(true) - case showPdfState: - m.deactivateAllBubbles() - m.markdown.SetIsActive(true) - } - } - - return cmds -} - // openFile opens the currently selected file. func (m *model) openFile() []tea.Cmd { var cmds []tea.Cmd @@ -157,6 +81,7 @@ func (m *model) openFile() []tea.Cmd { // toggleBox toggles between the two boxes. func (m *model) toggleBox() { m.activeBox = (m.activeBox + 1) % 2 + if m.activeBox == 0 { m.deactivateAllBubbles() m.filetree.SetIsActive(true) @@ -196,17 +121,6 @@ func (m *model) updateStatusbar() { ) } -// contains returns true if the slice contains the string. -func contains(s []string, str string) bool { - for _, v := range s { - if v == str { - return true - } - } - - return false -} - // Update handles all UI interactions. func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var ( @@ -239,7 +153,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit case key.Matches(msg, m.keys.OpenFile): cmds = append(cmds, tea.Batch(m.openFile()...)) - case key.Matches(msg, m.keys.ToggleBox): + case key.Matches(msg, m.keys.TogglePane): m.toggleBox() } } From 0d4a608debf140ffa6ad238e6de6d9c25809d8d6 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sat, 9 Mar 2024 19:13:44 -0500 Subject: [PATCH 04/31] chore: rename --- internal/tui/update.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/tui/update.go b/internal/tui/update.go index c98470d..44d0caf 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -78,8 +78,8 @@ func (m *model) openFile() []tea.Cmd { return cmds } -// toggleBox toggles between the two boxes. -func (m *model) toggleBox() { +// togglePane toggles between the two boxes. +func (m *model) togglePane() { m.activeBox = (m.activeBox + 1) % 2 if m.activeBox == 0 { @@ -154,7 +154,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, m.keys.OpenFile): cmds = append(cmds, tea.Batch(m.openFile()...)) case key.Matches(msg, m.keys.TogglePane): - m.toggleBox() + m.togglePane() } } From 662c6ed4017df5d4384cc31b17a6bdab6ebb97d3 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sun, 10 Mar 2024 12:40:14 -0400 Subject: [PATCH 05/31] chore: filetree scrolling and resizing --- filetree/update.go | 2 +- filetree/view.go | 16 +++++++++++++--- go.sum | 42 ------------------------------------------ internal/tui/update.go | 9 ++------- 4 files changed, 16 insertions(+), 53 deletions(-) diff --git a/filetree/update.go b/filetree/update.go index e719375..205a4d8 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -12,7 +12,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: - m.height = msg.Height + m.height = msg.Height - 2 m.width = msg.Width m.max = m.height - 1 case getDirectoryListingMsg: diff --git a/filetree/view.go b/filetree/view.go index 4e538ff..c06d58e 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -15,11 +15,21 @@ func (m Model) View() string { } if i == m.Cursor { + if file.IsDirectory { + fileList.WriteString("🗀 ") + } else { + fileList.WriteString("🗎 ") + } + fileList.WriteString(selectedItemStyle.Render(file.Name) + "\n") - // fileList.WriteString(selectedItemStyle.Render(file.details) + "\n\n") } else { + if file.IsDirectory { + fileList.WriteString("🗀 ") + } else { + fileList.WriteString("🗎 ") + } + fileList.WriteString(file.Name + "\n") - // fileList.WriteString(file.details + "\n\n") } } @@ -27,5 +37,5 @@ func (m Model) View() string { fileList.WriteRune('\n') } - return fileList.String() + return lipgloss.NewStyle().Width(m.width).Height(m.height).Render(fileList.String()) } diff --git a/go.sum b/go.sum index ede8492..3163aaf 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,5 @@ -github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= -github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= -github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= -github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= -github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= -github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= -github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= @@ -83,8 +76,6 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= -github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -94,68 +85,35 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/tui/update.go b/internal/tui/update.go index 44d0caf..21e941d 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -5,7 +5,6 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" - "github.com/mistakenelf/fm/icons" "github.com/mistakenelf/fm/statusbar" ) @@ -108,11 +107,7 @@ func (m *model) togglePane() { // updateStatusbar updates the content of the statusbar. func (m *model) updateStatusbar() { - logoText := fmt.Sprintf("%s %s", icons.IconDef["dir"].GetGlyph(), "FM") - if !m.config.Settings.ShowIcons { - logoText = "FM" - } - + logoText := fmt.Sprintf("%s %s", "🗀", "FM") m.statusbar.SetContent( m.filetree.GetSelectedItem().Name, m.filetree.GetSelectedItem().CurrentDirectory, @@ -134,7 +129,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: halfSize := msg.Width / 2 - bubbleHeight := msg.Height - statusbar.Height + bubbleHeight := msg.Height - statusbar.Height - 2 resizeImgCmd := m.image.SetSize(halfSize, bubbleHeight) markdownCmd := m.markdown.SetSize(halfSize, bubbleHeight) From f62ef7a19ad917ce1f3c8f7cfaa61adcf834ff71 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sun, 10 Mar 2024 13:55:21 -0400 Subject: [PATCH 06/31] chore: cleanup --- .golangci.yml | 1 - filetree/commands.go | 7 +++---- filetree/keys.go | 10 ++++++---- filetree/methods.go | 5 +++-- filetree/model.go | 4 ++-- filetree/styles.go | 3 ++- filetree/update.go | 6 ++---- filetree/view.go | 10 +++++----- internal/config/config.go | 4 ---- internal/tui/model.go | 2 +- internal/tui/update.go | 25 +++++++++---------------- 11 files changed, 33 insertions(+), 44 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 1619382..b0479c0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -6,7 +6,6 @@ linters: enable: - bodyclose - deadcode - - depguard - dogsled - errcheck - exportloopref diff --git a/filetree/commands.go b/filetree/commands.go index e4922a1..4dbf1d1 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -55,10 +55,9 @@ func getDirectoryListingCmd(directoryName string, showHidden bool) tea.Cmd { continue } - status := fmt.Sprintf("%s %s %s", - fileInfo.ModTime().Format("2006-01-02 15:04:05"), - fileInfo.Mode().String(), - ConvertBytesToSizeString(fileInfo.Size())) + status := fmt.Sprintf("%s %s", + ConvertBytesToSizeString(fileInfo.Size()), + fileInfo.Mode().String()) directoryItems = append(directoryItems, DirectoryItem{ Name: file.Name(), diff --git a/filetree/keys.go b/filetree/keys.go index beb0d23..a4dc2cf 100644 --- a/filetree/keys.go +++ b/filetree/keys.go @@ -3,13 +3,15 @@ package filetree import "github.com/charmbracelet/bubbles/key" type KeyMap struct { - Down key.Binding - Up key.Binding + Down key.Binding + Up key.Binding + GoToTop key.Binding } func DefaultKeyMap() KeyMap { return KeyMap{ - Down: key.NewBinding(key.WithKeys("j", "down", "ctrl+n"), key.WithHelp("j", "down")), - Up: key.NewBinding(key.WithKeys("k", "up", "ctrl+p"), key.WithHelp("k", "up")), + Down: key.NewBinding(key.WithKeys("j", "down", "ctrl+n"), key.WithHelp("j", "down")), + Up: key.NewBinding(key.WithKeys("k", "up", "ctrl+p"), key.WithHelp("k", "up")), + GoToTop: key.NewBinding(key.WithKeys("g+g"), key.WithHelp("g+g", "top")), } } diff --git a/filetree/methods.go b/filetree/methods.go index 0a72597..0b4364c 100644 --- a/filetree/methods.go +++ b/filetree/methods.go @@ -43,7 +43,7 @@ func (m *Model) SetIsActive(active bool) { m.active = active } -// GetSelectedItem returns the currently selected file/dir +// GetSelectedItem returns the currently selected file/dir. func (m Model) GetSelectedItem() DirectoryItem { if len(m.files) > 0 { return m.files[m.Cursor] @@ -59,6 +59,7 @@ func (m Model) GetTotalItems() int { // SetSize Sets the size of the filetree. func (m *Model) SetSize(width, height int) { + m.height = height - 2 m.width = width - m.height = height + m.max = m.height - 1 } diff --git a/filetree/model.go b/filetree/model.go index 879b5bc..eb232c7 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -20,10 +20,10 @@ type Model struct { width int } -func New() Model { +func New(active bool) Model { return Model{ Cursor: 0, - active: true, + active: active, keyMap: DefaultKeyMap(), min: 0, max: 0, diff --git a/filetree/styles.go b/filetree/styles.go index a723e69..6d0d6bf 100644 --- a/filetree/styles.go +++ b/filetree/styles.go @@ -3,5 +3,6 @@ package filetree import "github.com/charmbracelet/lipgloss" var ( - selectedItemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true) + selectedItemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true) + unselectedItemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ffffff")) ) diff --git a/filetree/update.go b/filetree/update.go index 205a4d8..6fb32c5 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -11,10 +11,6 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { ) switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.height = msg.Height - 2 - m.width = msg.Width - m.max = m.height - 1 case getDirectoryListingMsg: if msg != nil { m.files = msg @@ -42,6 +38,8 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.min-- m.max-- } + case key.Matches(msg, m.keyMap.GoToTop): + m.Cursor = 0 } } diff --git a/filetree/view.go b/filetree/view.go index c06d58e..d4ca607 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -16,20 +16,20 @@ func (m Model) View() string { if i == m.Cursor { if file.IsDirectory { - fileList.WriteString("🗀 ") + fileList.WriteString("📂 ") } else { - fileList.WriteString("🗎 ") + fileList.WriteString("📄 ") } fileList.WriteString(selectedItemStyle.Render(file.Name) + "\n") } else { if file.IsDirectory { - fileList.WriteString("🗀 ") + fileList.WriteString("📂 ") } else { - fileList.WriteString("🗎 ") + fileList.WriteString("📄 ") } - fileList.WriteString(file.Name + "\n") + fileList.WriteString(unselectedItemStyle.Render(file.Name) + "\n") } } diff --git a/internal/config/config.go b/internal/config/config.go index 04a9452..6792635 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,10 +25,8 @@ type SyntaxThemeConfig struct { // SettingsConfig struct represents the config for the settings. type SettingsConfig struct { StartDir string `yaml:"start_dir"` - ShowIcons bool `yaml:"show_icons"` EnableLogging bool `yaml:"enable_logging"` PrettyMarkdown bool `yaml:"pretty_markdown"` - Borderless bool `yaml:"borderless"` } // ThemeConfig represents the config for themes. @@ -58,10 +56,8 @@ func (parser ConfigParser) getDefaultConfig() Config { return Config{ Settings: SettingsConfig{ StartDir: ".", - ShowIcons: true, EnableLogging: false, PrettyMarkdown: true, - Borderless: false, }, Theme: ThemeConfig{ AppTheme: "default", diff --git a/internal/tui/model.go b/internal/tui/model.go index d193568..a3d519a 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -56,7 +56,7 @@ func New(startDir, selectionPath string) model { syntaxTheme = cfg.Theme.SyntaxTheme.Dark } - filetreeModel := filetree.New() + filetreeModel := filetree.New(true) codeModel := code.New(false) codeModel.SetSyntaxTheme(syntaxTheme) diff --git a/internal/tui/update.go b/internal/tui/update.go index 21e941d..ce46619 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -105,17 +105,6 @@ func (m *model) togglePane() { } } -// updateStatusbar updates the content of the statusbar. -func (m *model) updateStatusbar() { - logoText := fmt.Sprintf("%s %s", "🗀", "FM") - m.statusbar.SetContent( - m.filetree.GetSelectedItem().Name, - m.filetree.GetSelectedItem().CurrentDirectory, - fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), - logoText, - ) -} - // Update handles all UI interactions. func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var ( @@ -131,15 +120,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { halfSize := msg.Width / 2 bubbleHeight := msg.Height - statusbar.Height - 2 - resizeImgCmd := m.image.SetSize(halfSize, bubbleHeight) - markdownCmd := m.markdown.SetSize(halfSize, bubbleHeight) + cmds = append(cmds, m.image.SetSize(halfSize, bubbleHeight)) + cmds = append(cmds, m.markdown.SetSize(halfSize, bubbleHeight)) + m.filetree.SetSize(halfSize, bubbleHeight) m.help.SetSize(halfSize, bubbleHeight) m.code.SetSize(halfSize, bubbleHeight) m.pdf.SetSize(halfSize, bubbleHeight) m.statusbar.SetSize(msg.Width) - - cmds = append(cmds, resizeImgCmd, markdownCmd) case tea.KeyMsg: switch { case key.Matches(msg, m.keys.Quit): @@ -154,7 +142,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } if m.filetree.GetSelectedItem().Name != "" { - m.updateStatusbar() + m.statusbar.SetContent( + m.filetree.GetSelectedItem().Name, + m.filetree.GetSelectedItem().CurrentDirectory, + fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), + fmt.Sprintf("%s %s", "🗀", "FM"), + ) } m.code, cmd = m.code.Update(msg) From 33c3f44842132eb7a1fde23f95f336b7df7d20ff Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sun, 10 Mar 2024 20:53:33 -0400 Subject: [PATCH 07/31] feat: use flags for config --- cmd/root.go | 46 +++++---- go.mod | 4 - go.sum | 14 --- internal/config/config.go | 194 -------------------------------------- internal/tui/model.go | 56 +++++------ internal/tui/update.go | 4 +- 6 files changed, 54 insertions(+), 264 deletions(-) delete mode 100644 internal/config/config.go diff --git a/cmd/root.go b/cmd/root.go index e226a4f..d85bfa8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,17 +5,17 @@ import ( "log" "os" - "github.com/mistakenelf/fm/internal/config" - "github.com/mistakenelf/fm/internal/tui" - tea "github.com/charmbracelet/bubbletea" + "github.com/mistakenelf/fm/filesystem" + "github.com/mistakenelf/fm/internal/theme" + "github.com/mistakenelf/fm/internal/tui" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ Use: "fm", Short: "FM is a simple, configurable, and fun to use file manager", - Version: "0.16.0", + Version: "1.0.0", Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { startDir, err := cmd.Flags().GetString("start-dir") @@ -28,13 +28,23 @@ var rootCmd = &cobra.Command{ log.Fatal(err) } - cfg, err := config.ParseConfig() + enableLogging, err := cmd.Flags().GetBool("enable-logging") + if err != nil { + log.Fatal(err) + } + + prettyMarkdown, err := cmd.Flags().GetBool("pretty-markdown") + if err != nil { + log.Fatal(err) + } + + applicationTheme, err := cmd.Flags().GetString("theme") if err != nil { log.Fatal(err) } // If logging is enabled, logs will be output to debug.log. - if cfg.Settings.EnableLogging { + if enableLogging { f, err := tea.LogToFile("debug.log", "debug") if err != nil { log.Fatal(err) @@ -47,18 +57,19 @@ var rootCmd = &cobra.Command{ }() } - if startDir == "" { - startDir = cfg.Settings.StartDir - } + appTheme := theme.GetTheme(applicationTheme) - m := tui.New(startDir, selectionPath) - var opts []tea.ProgramOption + cfg := tui.Config{ + StartDir: startDir, + SelectionPath: selectionPath, + EnableLogging: enableLogging, + PrettyMarkdown: prettyMarkdown, + Theme: appTheme, + } - // Always append alt screen program option. - opts = append(opts, tea.WithAltScreen()) + m := tui.New(cfg) - // Initialize and start app. - p := tea.NewProgram(m, opts...) + p := tea.NewProgram(m, tea.WithAltScreen()) if _, err := p.Run(); err != nil { log.Fatal("Failed to start fm", err) os.Exit(1) @@ -70,7 +81,10 @@ var rootCmd = &cobra.Command{ func Execute() { rootCmd.AddCommand(updateCmd) rootCmd.PersistentFlags().String("selection-path", "", "Path to write to file on open.") - rootCmd.PersistentFlags().String("start-dir", "", "Starting directory for FM") + rootCmd.PersistentFlags().String("start-dir", filesystem.CurrentDirectory, "Starting directory for FM") + rootCmd.PersistentFlags().Bool("enable-logging", false, "Enable logging for FM") + rootCmd.PersistentFlags().Bool("pretty-markdown", true, "Render markdown to look nice") + rootCmd.PersistentFlags().String("theme", "default", "Application theme") if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) diff --git a/go.mod b/go.mod index b8bec4b..8842f8f 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 github.com/muesli/reflow v0.3.0 github.com/spf13/cobra v1.8.0 - gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -23,7 +22,6 @@ require ( github.com/dlclark/regexp2 v1.11.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect @@ -33,7 +31,6 @@ require ( github.com/muesli/termenv v0.15.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/yuin/goldmark v1.7.0 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect @@ -43,5 +40,4 @@ require ( golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index 3163aaf..5dcc59d 100644 --- a/go.sum +++ b/go.sum @@ -16,7 +16,6 @@ github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy12 github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -30,13 +29,6 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06 h1:kacRlPN7EN++tVpGUorNGPn/4DnB7/DfTY82AOn6ccU= github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -65,16 +57,12 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= @@ -115,8 +103,6 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 6792635..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,194 +0,0 @@ -package config - -import ( - "errors" - "fmt" - "os" - "path" - "path/filepath" - - "gopkg.in/yaml.v3" -) - -// AppDir is the name of the directory where the config file is stored. -const AppDir = "fm" - -// ConfigFileName is the name of the config file that gets created. -const ConfigFileName = "config.yml" - -// SyntaxThemeConfig represents light and dark syntax themes. -type SyntaxThemeConfig struct { - Light string `yaml:"light"` - Dark string `yaml:"dark"` -} - -// SettingsConfig struct represents the config for the settings. -type SettingsConfig struct { - StartDir string `yaml:"start_dir"` - EnableLogging bool `yaml:"enable_logging"` - PrettyMarkdown bool `yaml:"pretty_markdown"` -} - -// ThemeConfig represents the config for themes. -type ThemeConfig struct { - AppTheme string `yaml:"app_theme"` - SyntaxTheme SyntaxThemeConfig `yaml:"syntax_theme"` -} - -// Config represents the main config for the application. -type Config struct { - Settings SettingsConfig `yaml:"settings"` - Theme ThemeConfig `yaml:"theme"` -} - -// configError represents an error that occurred while parsing the config file. -type configError struct { - configDir string - parser ConfigParser - err error -} - -// ConfigParser is the parser for the config file. -type ConfigParser struct{} - -// getDefaultConfig returns the default config for the application. -func (parser ConfigParser) getDefaultConfig() Config { - return Config{ - Settings: SettingsConfig{ - StartDir: ".", - EnableLogging: false, - PrettyMarkdown: true, - }, - Theme: ThemeConfig{ - AppTheme: "default", - SyntaxTheme: SyntaxThemeConfig{ - Dark: "dracula", - Light: "pygments", - }, - }, - } -} - -// getDefaultConfigYamlContents returns the default config file contents. -func (parser ConfigParser) getDefaultConfigYamlContents() string { - defaultConfig := parser.getDefaultConfig() - yaml, _ := yaml.Marshal(defaultConfig) - - return string(yaml) -} - -// Error returns the error message for when a config file is not found. -func (e configError) Error() string { - return fmt.Sprintf( - `Couldn't find a config.yml configuration file. -Create one under: %s -Example of a config.yml file: -%s -For more info, go to https://github.com/mistakenelf/fm -press q to exit. -Original error: %v`, - path.Join(e.configDir, AppDir, ConfigFileName), - e.parser.getDefaultConfigYamlContents(), - e.err, - ) -} - -// writeDefaultConfigContents writes the default config file contents to the given file. -func (parser ConfigParser) writeDefaultConfigContents(newConfigFile *os.File) error { - _, err := newConfigFile.WriteString(parser.getDefaultConfigYamlContents()) - - if err != nil { - return err - } - - return nil -} - -// createConfigFileIfMissing creates the config file if it doesn't exist. -func (parser ConfigParser) createConfigFileIfMissing(configFilePath string) error { - if _, err := os.Stat(configFilePath); errors.Is(err, os.ErrNotExist) { - newConfigFile, err := os.OpenFile(configFilePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) - if err != nil { - return err - } - - defer newConfigFile.Close() - return parser.writeDefaultConfigContents(newConfigFile) - } - - return nil -} - -// getConfigFileOrCreateIfMissing returns the config file path or creates the config file if it doesn't exist. -func (parser ConfigParser) getConfigFileOrCreateIfMissing() (*string, error) { - var err error - configDir := os.Getenv("XDG_CONFIG_HOME") - - if configDir == "" { - configDir, err = os.UserConfigDir() - if err != nil { - return nil, configError{parser: parser, configDir: configDir, err: err} - } - } - - prsConfigDir := filepath.Join(configDir, AppDir) - err = os.MkdirAll(prsConfigDir, os.ModePerm) - if err != nil { - return nil, configError{parser: parser, configDir: configDir, err: err} - } - - configFilePath := filepath.Join(prsConfigDir, ConfigFileName) - err = parser.createConfigFileIfMissing(configFilePath) - if err != nil { - return nil, configError{parser: parser, configDir: configDir, err: err} - } - - return &configFilePath, nil -} - -// parsingError represents an error that occurred while parsing the config file. -type parsingError struct { - err error -} - -// Error represents an error that occurred while parsing the config file. -func (e parsingError) Error() string { - return fmt.Sprintf("failed parsing config.yml: %v", e.err) -} - -// readConfigFile reads the config file and returns the config. -func (parser ConfigParser) readConfigFile(path string) (Config, error) { - config := parser.getDefaultConfig() - data, err := os.ReadFile(path) - if err != nil { - return config, configError{parser: parser, configDir: path, err: err} - } - - err = yaml.Unmarshal((data), &config) - return config, err -} - -// initParser initializes the parser. -func initParser() ConfigParser { - return ConfigParser{} -} - -// ParseConfig parses the config file and returns the config. -func ParseConfig() (Config, error) { - var config Config - var err error - - parser := initParser() - - configFilePath, err := parser.getConfigFileOrCreateIfMissing() - if err != nil { - return config, parsingError{err: err} - } - - config, err = parser.readConfigFile(*configFilePath) - if err != nil { - return config, parsingError{err: err} - } - - return config, nil -} diff --git a/internal/tui/model.go b/internal/tui/model.go index a3d519a..7cd9d45 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -1,16 +1,11 @@ package tui import ( - "log" - - "github.com/mistakenelf/fm/internal/config" - "github.com/mistakenelf/fm/internal/theme" - - "github.com/charmbracelet/lipgloss" "github.com/mistakenelf/fm/code" "github.com/mistakenelf/fm/filetree" "github.com/mistakenelf/fm/help" "github.com/mistakenelf/fm/image" + "github.com/mistakenelf/fm/internal/theme" "github.com/mistakenelf/fm/markdown" "github.com/mistakenelf/fm/pdf" "github.com/mistakenelf/fm/statusbar" @@ -26,7 +21,14 @@ const ( showPdfState ) -// model represents the properties of the UI. +type Config struct { + StartDir string + SelectionPath string + EnableLogging bool + PrettyMarkdown bool + Theme theme.Theme +} + type model struct { filetree filetree.Model help help.Model @@ -36,50 +38,37 @@ type model struct { pdf pdf.Model statusbar statusbar.Model state sessionState - theme theme.Theme - config config.Config keys KeyMap activeBox int + config Config } // New creates a new instance of the UI. -func New(startDir, selectionPath string) model { - cfg, err := config.ParseConfig() - if err != nil { - log.Fatal(err) - } - - theme := theme.GetTheme(cfg.Theme.AppTheme) - - syntaxTheme := cfg.Theme.SyntaxTheme.Light - if lipgloss.HasDarkBackground() { - syntaxTheme = cfg.Theme.SyntaxTheme.Dark - } - +func New(cfg Config) model { filetreeModel := filetree.New(true) codeModel := code.New(false) - codeModel.SetSyntaxTheme(syntaxTheme) + codeModel.SetSyntaxTheme("pygments") imageModel := image.New(false) markdownModel := markdown.New(false) pdfModel := pdf.New(false) statusbarModel := statusbar.New( statusbar.ColorConfig{ - Foreground: theme.StatusBarSelectedFileForegroundColor, - Background: theme.StatusBarSelectedFileBackgroundColor, + Foreground: cfg.Theme.StatusBarSelectedFileForegroundColor, + Background: cfg.Theme.StatusBarSelectedFileBackgroundColor, }, statusbar.ColorConfig{ - Foreground: theme.StatusBarBarForegroundColor, - Background: theme.StatusBarBarBackgroundColor, + Foreground: cfg.Theme.StatusBarBarForegroundColor, + Background: cfg.Theme.StatusBarBarBackgroundColor, }, statusbar.ColorConfig{ - Foreground: theme.StatusBarTotalFilesForegroundColor, - Background: theme.StatusBarTotalFilesBackgroundColor, + Foreground: cfg.Theme.StatusBarTotalFilesForegroundColor, + Background: cfg.Theme.StatusBarTotalFilesBackgroundColor, }, statusbar.ColorConfig{ - Foreground: theme.StatusBarLogoForegroundColor, - Background: theme.StatusBarLogoBackgroundColor, + Foreground: cfg.Theme.StatusBarLogoForegroundColor, + Background: cfg.Theme.StatusBarLogoBackgroundColor, }, ) @@ -87,8 +76,8 @@ func New(startDir, selectionPath string) model { false, "Help", help.TitleColor{ - Background: theme.TitleBackgroundColor, - Foreground: theme.TitleForegroundColor, + Background: cfg.Theme.TitleBackgroundColor, + Foreground: cfg.Theme.TitleForegroundColor, }, []help.Entry{ {Key: "ctrl+c, q", Description: "Exit FM"}, @@ -125,7 +114,6 @@ func New(startDir, selectionPath string) model { markdown: markdownModel, pdf: pdfModel, statusbar: statusbarModel, - theme: theme, config: cfg, keys: DefaultKeyMap(), } diff --git a/internal/tui/update.go b/internal/tui/update.go index ce46619..a8cc540 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -57,7 +57,7 @@ func (m *model) openFile() []tea.Cmd { m.state = showImageState readFileCmd := m.image.SetFileName(selectedFile.Name) cmds = append(cmds, readFileCmd) - case selectedFile.Extension == ".md" && m.config.Settings.PrettyMarkdown: + case selectedFile.Extension == ".md" && m.config.PrettyMarkdown: m.state = showMarkdownState markdownCmd := m.markdown.SetFileName(selectedFile.Name) cmds = append(cmds, markdownCmd) @@ -118,7 +118,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: halfSize := msg.Width / 2 - bubbleHeight := msg.Height - statusbar.Height - 2 + bubbleHeight := msg.Height - statusbar.Height cmds = append(cmds, m.image.SetSize(halfSize, bubbleHeight)) cmds = append(cmds, m.markdown.SetSize(halfSize, bubbleHeight)) From e9b3f55c1d43a6f105b02f42a3a8c0da84e03be6 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sun, 10 Mar 2024 21:00:04 -0400 Subject: [PATCH 08/31] feat: set start dir --- filetree/init.go | 3 +-- filetree/model.go | 38 ++++++++++++++++++++++++-------------- internal/tui/model.go | 2 +- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/filetree/init.go b/filetree/init.go index 2a34147..54d64c0 100644 --- a/filetree/init.go +++ b/filetree/init.go @@ -2,9 +2,8 @@ package filetree import ( tea "github.com/charmbracelet/bubbletea" - "github.com/mistakenelf/fm/filesystem" ) func (m Model) Init() tea.Cmd { - return getDirectoryListingCmd(filesystem.CurrentDirectory, true) + return getDirectoryListingCmd(m.startDir, true) } diff --git a/filetree/model.go b/filetree/model.go index eb232c7..0da20ee 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -1,5 +1,7 @@ package filetree +import "github.com/mistakenelf/fm/filesystem" + type DirectoryItem struct { Name string Details string @@ -10,22 +12,30 @@ type DirectoryItem struct { } type Model struct { - Cursor int - files []DirectoryItem - active bool - keyMap KeyMap - min int - max int - height int - width int + Cursor int + files []DirectoryItem + active bool + keyMap KeyMap + min int + max int + height int + width int + startDir string } -func New(active bool) Model { +func New(active bool, startDir string) Model { + startingDirectory := filesystem.CurrentDirectory + + if startDir != "" { + startingDirectory = startDir + } + return Model{ - Cursor: 0, - active: active, - keyMap: DefaultKeyMap(), - min: 0, - max: 0, + Cursor: 0, + active: active, + keyMap: DefaultKeyMap(), + min: 0, + max: 0, + startDir: startingDirectory, } } diff --git a/internal/tui/model.go b/internal/tui/model.go index 7cd9d45..49b51f0 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -45,7 +45,7 @@ type model struct { // New creates a new instance of the UI. func New(cfg Config) model { - filetreeModel := filetree.New(true) + filetreeModel := filetree.New(true, cfg.StartDir) codeModel := code.New(false) codeModel.SetSyntaxTheme("pygments") From a8584e7ec94fb8a11f15f9b1d5eca45a09342048 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Mon, 11 Mar 2024 13:20:22 -0400 Subject: [PATCH 09/31] feat: page up and down --- filetree/keys.go | 18 ++++++++++++------ filetree/update.go | 32 +++++++++++++++++++++++++++++++- filetree/view.go | 12 ++++-------- internal/tui/update.go | 3 +++ 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/filetree/keys.go b/filetree/keys.go index a4dc2cf..4e55d96 100644 --- a/filetree/keys.go +++ b/filetree/keys.go @@ -3,15 +3,21 @@ package filetree import "github.com/charmbracelet/bubbles/key" type KeyMap struct { - Down key.Binding - Up key.Binding - GoToTop key.Binding + Down key.Binding + Up key.Binding + GoToTop key.Binding + GoToBottom key.Binding + PageUp key.Binding + PageDown key.Binding } func DefaultKeyMap() KeyMap { return KeyMap{ - Down: key.NewBinding(key.WithKeys("j", "down", "ctrl+n"), key.WithHelp("j", "down")), - Up: key.NewBinding(key.WithKeys("k", "up", "ctrl+p"), key.WithHelp("k", "up")), - GoToTop: key.NewBinding(key.WithKeys("g+g"), key.WithHelp("g+g", "top")), + Down: key.NewBinding(key.WithKeys("j", "down", "ctrl+n"), key.WithHelp("j", "down")), + Up: key.NewBinding(key.WithKeys("k", "up", "ctrl+p"), key.WithHelp("k", "up")), + GoToTop: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "go to top")), + GoToBottom: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "go to bottom")), + PageUp: key.NewBinding(key.WithKeys("K", "pgup"), key.WithHelp("pgup", "page up")), + PageDown: key.NewBinding(key.WithKeys("J", "pgdown"), key.WithHelp("pgdown", "page down")), } } diff --git a/filetree/update.go b/filetree/update.go index 6fb32c5..2d921f1 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -14,7 +14,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case getDirectoryListingMsg: if msg != nil { m.files = msg - m.max = max(m.max, m.height-1) + m.max = max(m.max, m.height) } case tea.KeyMsg: switch { @@ -40,6 +40,36 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } case key.Matches(msg, m.keyMap.GoToTop): m.Cursor = 0 + m.min = 0 + m.max = m.height + case key.Matches(msg, m.keyMap.GoToBottom): + m.Cursor = len(m.files) - 1 + m.min = len(m.files) - m.height + m.max = len(m.files) - 1 + case key.Matches(msg, m.keyMap.PageDown): + m.Cursor += m.height + if m.Cursor >= len(m.files) { + m.Cursor = len(m.files) - 1 + } + m.min += m.height + m.max += m.height + + if m.max >= len(m.files) { + m.max = len(m.files) - 1 + m.min = m.max - m.height + } + case key.Matches(msg, m.keyMap.PageUp): + m.Cursor -= m.height + if m.Cursor < 0 { + m.Cursor = 0 + } + m.min -= m.height + m.max -= m.height + + if m.min < 0 { + m.min = 0 + m.max = m.min + m.height + } } } diff --git a/filetree/view.go b/filetree/view.go index d4ca607..50db49e 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -16,26 +16,22 @@ func (m Model) View() string { if i == m.Cursor { if file.IsDirectory { - fileList.WriteString("📂 ") + fileList.WriteString(selectedItemStyle.Render("🗀 ")) } else { - fileList.WriteString("📄 ") + fileList.WriteString(selectedItemStyle.Render("🗎 ")) } fileList.WriteString(selectedItemStyle.Render(file.Name) + "\n") } else { if file.IsDirectory { - fileList.WriteString("📂 ") + fileList.WriteString(unselectedItemStyle.Render("🗀 ")) } else { - fileList.WriteString("📄 ") + fileList.WriteString(unselectedItemStyle.Render("🗎 ")) } fileList.WriteString(unselectedItemStyle.Render(file.Name) + "\n") } } - for i := lipgloss.Height(fileList.String()); i <= m.height; i++ { - fileList.WriteRune('\n') - } - return lipgloss.NewStyle().Width(m.width).Height(m.height).Render(fileList.String()) } diff --git a/internal/tui/update.go b/internal/tui/update.go index a8cc540..8211506 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -128,6 +128,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.code.SetSize(halfSize, bubbleHeight) m.pdf.SetSize(halfSize, bubbleHeight) m.statusbar.SetSize(msg.Width) + + m.filetree, cmd = m.filetree.Update(msg) + cmds = append(cmds, cmd) case tea.KeyMsg: switch { case key.Matches(msg, m.keys.Quit): From 299502c782e975d53d8a95a140c4de3b826c7744 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Mon, 11 Mar 2024 20:50:21 -0400 Subject: [PATCH 10/31] feat: more file navigation --- filetree/commands.go | 4 ++-- filetree/keys.go | 34 ++++++++++++++++++++++------------ filetree/methods.go | 2 +- filetree/model.go | 32 +++++++++++++++++--------------- filetree/styles.go | 1 + filetree/update.go | 24 ++++++++++++++++++++++++ filetree/view.go | 13 +++++++++++-- 7 files changed, 78 insertions(+), 32 deletions(-) diff --git a/filetree/commands.go b/filetree/commands.go index 4dbf1d1..26295df 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -34,12 +34,12 @@ func getDirectoryListingCmd(directoryName string, showHidden bool) tea.Cmd { return nil } - files, err := filesystem.GetDirectoryListing(directoryName, showHidden) + err = os.Chdir(directoryName) if err != nil { return errorMsg(err) } - err = os.Chdir(directoryName) + files, err := filesystem.GetDirectoryListing(directoryName, showHidden) if err != nil { return errorMsg(err) } diff --git a/filetree/keys.go b/filetree/keys.go index 4e55d96..f33ac28 100644 --- a/filetree/keys.go +++ b/filetree/keys.go @@ -3,21 +3,31 @@ package filetree import "github.com/charmbracelet/bubbles/key" type KeyMap struct { - Down key.Binding - Up key.Binding - GoToTop key.Binding - GoToBottom key.Binding - PageUp key.Binding - PageDown key.Binding + Down key.Binding + Up key.Binding + GoToTop key.Binding + GoToBottom key.Binding + PageUp key.Binding + PageDown key.Binding + GoToHomeDirectory key.Binding + GoToRootDirectory key.Binding + ToggleHidden key.Binding + OpenDirectory key.Binding + PreviousDirectory key.Binding } func DefaultKeyMap() KeyMap { return KeyMap{ - Down: key.NewBinding(key.WithKeys("j", "down", "ctrl+n"), key.WithHelp("j", "down")), - Up: key.NewBinding(key.WithKeys("k", "up", "ctrl+p"), key.WithHelp("k", "up")), - GoToTop: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "go to top")), - GoToBottom: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "go to bottom")), - PageUp: key.NewBinding(key.WithKeys("K", "pgup"), key.WithHelp("pgup", "page up")), - PageDown: key.NewBinding(key.WithKeys("J", "pgdown"), key.WithHelp("pgdown", "page down")), + Down: key.NewBinding(key.WithKeys("j", "down", "ctrl+n"), key.WithHelp("j", "down")), + Up: key.NewBinding(key.WithKeys("k", "up", "ctrl+p"), key.WithHelp("k", "up")), + GoToTop: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "go to top")), + GoToBottom: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "go to bottom")), + PageUp: key.NewBinding(key.WithKeys("K", "pgup"), key.WithHelp("pgup", "page up")), + PageDown: key.NewBinding(key.WithKeys("J", "pgdown"), key.WithHelp("pgdown", "page down")), + GoToHomeDirectory: key.NewBinding(key.WithKeys("~"), key.WithHelp("~", "go to home directory")), + GoToRootDirectory: key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "go to root directory")), + ToggleHidden: key.NewBinding(key.WithKeys("."), key.WithHelp(".", "toggle hidden")), + OpenDirectory: key.NewBinding(key.WithKeys("l", "right"), key.WithHelp("l", "open directory")), + PreviousDirectory: key.NewBinding(key.WithKeys("h", "left"), key.WithHelp("h", "previous directory")), } } diff --git a/filetree/methods.go b/filetree/methods.go index 0b4364c..6d631d7 100644 --- a/filetree/methods.go +++ b/filetree/methods.go @@ -40,7 +40,7 @@ func ConvertBytesToSizeString(size int64) string { // SetIsActive sets if the bubble is currently active. func (m *Model) SetIsActive(active bool) { - m.active = active + m.Active = active } // GetSelectedItem returns the currently selected file/dir. diff --git a/filetree/model.go b/filetree/model.go index 0da20ee..0c52b50 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -12,15 +12,16 @@ type DirectoryItem struct { } type Model struct { - Cursor int - files []DirectoryItem - active bool - keyMap KeyMap - min int - max int - height int - width int - startDir string + Cursor int + files []DirectoryItem + Active bool + keyMap KeyMap + min int + max int + height int + width int + startDir string + showHidden bool } func New(active bool, startDir string) Model { @@ -31,11 +32,12 @@ func New(active bool, startDir string) Model { } return Model{ - Cursor: 0, - active: active, - keyMap: DefaultKeyMap(), - min: 0, - max: 0, - startDir: startingDirectory, + Cursor: 0, + Active: active, + keyMap: DefaultKeyMap(), + min: 0, + max: 0, + startDir: startingDirectory, + showHidden: true, } } diff --git a/filetree/styles.go b/filetree/styles.go index 6d0d6bf..975077d 100644 --- a/filetree/styles.go +++ b/filetree/styles.go @@ -5,4 +5,5 @@ import "github.com/charmbracelet/lipgloss" var ( selectedItemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true) unselectedItemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ffffff")) + inactiveStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("243")) ) diff --git a/filetree/update.go b/filetree/update.go index 2d921f1..af2b4cf 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -1,8 +1,11 @@ package filetree import ( + "path/filepath" + "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" + "github.com/mistakenelf/fm/filesystem" ) func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { @@ -10,10 +13,17 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { cmds []tea.Cmd ) + if !m.Active { + return m, nil + } + switch msg := msg.(type) { case getDirectoryListingMsg: if msg != nil { m.files = msg + m.Cursor = 0 + m.min = 0 + m.max = m.height m.max = max(m.max, m.height) } case tea.KeyMsg: @@ -70,6 +80,20 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.min = 0 m.max = m.min + m.height } + case key.Matches(msg, m.keyMap.GoToHomeDirectory): + return m, getDirectoryListingCmd(filesystem.HomeDirectory, m.showHidden) + case key.Matches(msg, m.keyMap.GoToRootDirectory): + return m, getDirectoryListingCmd(filesystem.RootDirectory, m.showHidden) + case key.Matches(msg, m.keyMap.ToggleHidden): + m.showHidden = !m.showHidden + + return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden) + case key.Matches(msg, m.keyMap.OpenDirectory): + if m.files[m.Cursor].IsDirectory { + return m, getDirectoryListingCmd(m.files[m.Cursor].Path, m.showHidden) + } + case key.Matches(msg, m.keyMap.PreviousDirectory): + return m, getDirectoryListingCmd(filepath.Dir(m.files[m.Cursor].CurrentDirectory), m.showHidden) } } diff --git a/filetree/view.go b/filetree/view.go index 50db49e..f6ecd7f 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -14,7 +14,16 @@ func (m Model) View() string { continue } - if i == m.Cursor { + switch { + case !m.Active: + if file.IsDirectory { + fileList.WriteString(inactiveStyle.Render("🗀 ")) + } else { + fileList.WriteString(inactiveStyle.Render("🗎 ")) + } + + fileList.WriteString(inactiveStyle.Render(file.Name) + "\n") + case i == m.Cursor && m.Active: if file.IsDirectory { fileList.WriteString(selectedItemStyle.Render("🗀 ")) } else { @@ -22,7 +31,7 @@ func (m Model) View() string { } fileList.WriteString(selectedItemStyle.Render(file.Name) + "\n") - } else { + case i != m.Cursor && m.Active: if file.IsDirectory { fileList.WriteString(unselectedItemStyle.Render("🗀 ")) } else { From 94bac41370edb50678a4c97730a651a2969b5973 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Tue, 12 Mar 2024 11:44:20 -0400 Subject: [PATCH 11/31] feat: more tree commands and status messages --- .golangci.yml | 3 - filetree/commands.go | 140 ++++++++++++++++++++++++++++++++++++++-- filetree/keys.go | 58 ++++++++++------- filetree/methods.go | 4 +- filetree/model.go | 45 +++++++------ filetree/update.go | 29 +++++++++ go.mod | 1 + go.sum | 2 + internal/tui/helpers.go | 123 +++++++++++++++++++++++++++++++++++ internal/tui/update.go | 108 +------------------------------ statusbar/statusbar.go | 1 - 11 files changed, 356 insertions(+), 158 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b0479c0..8518516 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,6 @@ linters: disable-all: true enable: - bodyclose - - deadcode - dogsled - errcheck - exportloopref @@ -23,13 +22,11 @@ linters: - nolintlint - rowserrcheck - staticcheck - - structcheck - stylecheck - typecheck - unconvert - unparam - unused - - varcheck - whitespace - wastedassign - nilerr diff --git a/filetree/commands.go b/filetree/commands.go index 26295df..8f9ed26 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -4,13 +4,17 @@ import ( "fmt" "os" "path/filepath" + "time" + "github.com/atotto/clipboard" tea "github.com/charmbracelet/bubbletea" "github.com/mistakenelf/fm/filesystem" ) type getDirectoryListingMsg []DirectoryItem -type errorMsg error +type errorMsg string +type copyToClipboardMsg string +type statusMessageTimeoutMsg struct{} // getDirectoryListingCmd updates the directory listing based on the name of the directory provided. func getDirectoryListingCmd(directoryName string, showHidden bool) tea.Cmd { @@ -21,13 +25,13 @@ func getDirectoryListingCmd(directoryName string, showHidden bool) tea.Cmd { if directoryName == filesystem.HomeDirectory { directoryName, err = filesystem.GetHomeDirectory() if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } } directoryInfo, err := os.Stat(directoryName) if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } if !directoryInfo.IsDir() { @@ -36,17 +40,17 @@ func getDirectoryListingCmd(directoryName string, showHidden bool) tea.Cmd { err = os.Chdir(directoryName) if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } files, err := filesystem.GetDirectoryListing(directoryName, showHidden) if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } workingDirectory, err := filesystem.GetWorkingDirectory() if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } for _, file := range files { @@ -72,3 +76,127 @@ func getDirectoryListingCmd(directoryName string, showHidden bool) tea.Cmd { return getDirectoryListingMsg(directoryItems) } } + +// deleteDirectoryItemCmd deletes a directory based on the name provided. +func deleteDirectoryItemCmd(name string, isDirectory bool) tea.Cmd { + return func() tea.Msg { + if isDirectory { + if err := filesystem.DeleteDirectory(name); err != nil { + return errorMsg(err.Error()) + } + } else { + if err := filesystem.DeleteFile(name); err != nil { + return errorMsg(err.Error()) + } + } + + return nil + } +} + +// createDirectoryCmd creates a directory based on the name provided. +func createDirectoryCmd(name string) tea.Cmd { + return func() tea.Msg { + if err := filesystem.CreateDirectory(name); err != nil { + return errorMsg(err.Error()) + } + + return nil + } +} + +// createFileCmd creates a file based on the name provided. +func createFileCmd(name string) tea.Cmd { + return func() tea.Msg { + if err := filesystem.CreateFile(name); err != nil { + return errorMsg(err.Error()) + } + + return nil + } +} + +// zipDirectoryCmd zips a directory based on the name provided. +func zipDirectoryCmd(name string) tea.Cmd { + return func() tea.Msg { + if err := filesystem.Zip(name); err != nil { + return errorMsg(err.Error()) + } + + return nil + } +} + +// unzipDirectoryCmd unzips a directory based on the name provided. +func unzipDirectoryCmd(name string) tea.Cmd { + return func() tea.Msg { + if err := filesystem.Unzip(name); err != nil { + return errorMsg(err.Error()) + } + + return nil + } +} + +// copyDirectoryItemCmd copies a directory based on the name provided. +func copyDirectoryItemCmd(name string, isDirectory bool) tea.Cmd { + return func() tea.Msg { + if isDirectory { + if err := filesystem.CopyDirectory(name); err != nil { + return errorMsg(err.Error()) + } + } else { + if err := filesystem.CopyFile(name); err != nil { + return errorMsg(err.Error()) + } + } + + return nil + } +} + +// copyToClipboardCmd copies the provided string to the clipboard. +func copyToClipboardCmd(name string) tea.Cmd { + return func() tea.Msg { + workingDir, err := filesystem.GetWorkingDirectory() + if err != nil { + return errorMsg(err.Error()) + } + + filePath := filepath.Join(workingDir, name) + err = clipboard.WriteAll(filePath) + if err != nil { + return errorMsg(err.Error()) + } + + return copyToClipboardMsg(fmt.Sprintf("%s %s %s", "Successfully copied", filePath, "to clipboard")) + } +} + +// writeSelectionPathCmd writes content to the file specified. +func writeSelectionPathCmd(selectionPath, filePath string) tea.Cmd { + return func() tea.Msg { + if err := filesystem.WriteToFile(selectionPath, filePath); err != nil { + return errorMsg(err.Error()) + } + + return nil + } +} + +// NewStatusMessage sets a new status message, which will show for a limited +// amount of time. Note that this also returns a command. +func (m *Model) NewStatusMessage(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{} + } +} diff --git a/filetree/keys.go b/filetree/keys.go index f33ac28..e7abc3f 100644 --- a/filetree/keys.go +++ b/filetree/keys.go @@ -3,31 +3,45 @@ package filetree import "github.com/charmbracelet/bubbles/key" type KeyMap struct { - Down key.Binding - Up key.Binding - GoToTop key.Binding - GoToBottom key.Binding - PageUp key.Binding - PageDown key.Binding - GoToHomeDirectory key.Binding - GoToRootDirectory key.Binding - ToggleHidden key.Binding - OpenDirectory key.Binding - PreviousDirectory key.Binding + Down key.Binding + Up key.Binding + GoToTop key.Binding + GoToBottom key.Binding + PageUp key.Binding + PageDown key.Binding + GoToHomeDirectory key.Binding + GoToRootDirectory key.Binding + ToggleHidden key.Binding + OpenDirectory key.Binding + PreviousDirectory key.Binding + CopyPathToClipboard key.Binding + CopyDirectoryItem key.Binding + DeleteDirectoryItem key.Binding + ZipDirectoryItem key.Binding + UnzipDirectoryItem key.Binding + ShowDirectoriesOnly key.Binding + ShowFilesOnly key.Binding } func DefaultKeyMap() KeyMap { return KeyMap{ - Down: key.NewBinding(key.WithKeys("j", "down", "ctrl+n"), key.WithHelp("j", "down")), - Up: key.NewBinding(key.WithKeys("k", "up", "ctrl+p"), key.WithHelp("k", "up")), - GoToTop: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "go to top")), - GoToBottom: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "go to bottom")), - PageUp: key.NewBinding(key.WithKeys("K", "pgup"), key.WithHelp("pgup", "page up")), - PageDown: key.NewBinding(key.WithKeys("J", "pgdown"), key.WithHelp("pgdown", "page down")), - GoToHomeDirectory: key.NewBinding(key.WithKeys("~"), key.WithHelp("~", "go to home directory")), - GoToRootDirectory: key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "go to root directory")), - ToggleHidden: key.NewBinding(key.WithKeys("."), key.WithHelp(".", "toggle hidden")), - OpenDirectory: key.NewBinding(key.WithKeys("l", "right"), key.WithHelp("l", "open directory")), - PreviousDirectory: key.NewBinding(key.WithKeys("h", "left"), key.WithHelp("h", "previous directory")), + Down: key.NewBinding(key.WithKeys("j", "down", "ctrl+n"), key.WithHelp("j", "down")), + Up: key.NewBinding(key.WithKeys("k", "up", "ctrl+p"), key.WithHelp("k", "up")), + GoToTop: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "go to top")), + GoToBottom: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "go to bottom")), + PageUp: key.NewBinding(key.WithKeys("K", "pgup"), key.WithHelp("pgup", "page up")), + PageDown: key.NewBinding(key.WithKeys("J", "pgdown"), key.WithHelp("pgdown", "page down")), + GoToHomeDirectory: key.NewBinding(key.WithKeys("~"), key.WithHelp("~", "go to home directory")), + GoToRootDirectory: key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "go to root directory")), + ToggleHidden: key.NewBinding(key.WithKeys("."), key.WithHelp(".", "toggle hidden")), + OpenDirectory: key.NewBinding(key.WithKeys("l", "right"), key.WithHelp("l", "open directory")), + PreviousDirectory: key.NewBinding(key.WithKeys("h", "left"), key.WithHelp("h", "previous directory")), + CopyPathToClipboard: key.NewBinding(key.WithKeys("y"), key.WithHelp("y", "copy to clipboard")), + CopyDirectoryItem: key.NewBinding(key.WithKeys("C"), key.WithHelp("C", "copy directory item")), + DeleteDirectoryItem: key.NewBinding(key.WithKeys("X"), key.WithHelp("X", "delete directory item")), + ZipDirectoryItem: key.NewBinding(key.WithKeys("Z"), key.WithHelp("Z", "zip directory item")), + UnzipDirectoryItem: key.NewBinding(key.WithKeys("U"), key.WithHelp("U", "unzip directory item")), + ShowDirectoriesOnly: key.NewBinding(key.WithKeys("D"), key.WithHelp("D", "show directories only")), + ShowFilesOnly: key.NewBinding(key.WithKeys("F"), key.WithHelp("F", "show files only")), } } diff --git a/filetree/methods.go b/filetree/methods.go index 6d631d7..6d95246 100644 --- a/filetree/methods.go +++ b/filetree/methods.go @@ -1,6 +1,8 @@ package filetree -import "fmt" +import ( + "fmt" +) const ( thousand = 1000 diff --git a/filetree/model.go b/filetree/model.go index 0c52b50..090ac7c 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -1,6 +1,10 @@ package filetree -import "github.com/mistakenelf/fm/filesystem" +import ( + "time" + + "github.com/mistakenelf/fm/filesystem" +) type DirectoryItem struct { Name string @@ -12,16 +16,19 @@ type DirectoryItem struct { } type Model struct { - Cursor int - files []DirectoryItem - Active bool - keyMap KeyMap - min int - max int - height int - width int - startDir string - showHidden bool + Cursor int + files []DirectoryItem + Active bool + keyMap KeyMap + min int + max int + height int + width int + startDir string + showHidden bool + StatusMessage string + StatusMessageLifetime time.Duration + statusMessageTimer *time.Timer } func New(active bool, startDir string) Model { @@ -32,12 +39,14 @@ func New(active bool, startDir string) Model { } return Model{ - Cursor: 0, - Active: active, - keyMap: DefaultKeyMap(), - min: 0, - max: 0, - startDir: startingDirectory, - showHidden: true, + Cursor: 0, + Active: active, + keyMap: DefaultKeyMap(), + min: 0, + max: 0, + startDir: startingDirectory, + showHidden: true, + StatusMessage: "", + StatusMessageLifetime: time.Second, } } diff --git a/filetree/update.go b/filetree/update.go index af2b4cf..3cbb9cc 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -5,6 +5,7 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/mistakenelf/fm/filesystem" ) @@ -18,6 +19,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } switch msg := msg.(type) { + case errorMsg: + cmds = append(cmds, m.NewStatusMessage(lipgloss.NewStyle().Foreground(lipgloss.Color("#cc241d")).Bold(true).Render(string(msg)))) + case statusMessageTimeoutMsg: + m.StatusMessage = "" + case copyToClipboardMsg: + m.StatusMessage = string(msg) case getDirectoryListingMsg: if msg != nil { m.files = msg @@ -94,6 +101,28 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } case key.Matches(msg, m.keyMap.PreviousDirectory): return m, getDirectoryListingCmd(filepath.Dir(m.files[m.Cursor].CurrentDirectory), m.showHidden) + case key.Matches(msg, m.keyMap.CopyPathToClipboard): + return m, copyToClipboardCmd(m.files[m.Cursor].Name) + case key.Matches(msg, m.keyMap.CopyDirectoryItem): + return m, tea.Sequence( + copyDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden), + ) + case key.Matches(msg, m.keyMap.DeleteDirectoryItem): + return m, tea.Sequence( + deleteDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden), + ) + case key.Matches(msg, m.keyMap.ZipDirectoryItem): + return m, tea.Sequence( + zipDirectoryCmd(m.files[m.Cursor].Name), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden), + ) + case key.Matches(msg, m.keyMap.UnzipDirectoryItem): + return m, tea.Sequence( + unzipDirectoryCmd(m.files[m.Cursor].Name), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden), + ) } } diff --git a/go.mod b/go.mod index 8842f8f..375dfb1 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22 require ( github.com/alecthomas/chroma v0.10.0 + github.com/atotto/clipboard v0.1.4 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/glamour v0.6.0 diff --git a/go.sum b/go.sum index 5dcc59d..df52659 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= diff --git a/internal/tui/helpers.go b/internal/tui/helpers.go index d5252a6..9720db1 100644 --- a/internal/tui/helpers.go +++ b/internal/tui/helpers.go @@ -1,5 +1,28 @@ package tui +import ( + "fmt" + + tea "github.com/charmbracelet/bubbletea" +) + +var forbiddenExtensions = []string{ + ".FCStd", + ".gif", + ".zip", + ".rar", + ".webm", + ".sqlite", + ".sqlite-shm", + ".sqlite-wal", + ".DS_Store", + ".db", + ".data", + ".plist", + ".webp", + ".img", +} + // contains returns true if the slice contains the string. func contains(s []string, str string) bool { for _, v := range s { @@ -10,3 +33,103 @@ func contains(s []string, str string) bool { return false } + +func (m *model) updateStatusbarContent() { + if m.filetree.GetSelectedItem().Name != "" { + if m.filetree.StatusMessage != "" { + m.statusbar.SetContent( + m.filetree.GetSelectedItem().Name, + m.filetree.StatusMessage, + fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), + fmt.Sprintf("%s %s", "🗀", "FM"), + ) + } else { + m.statusbar.SetContent( + m.filetree.GetSelectedItem().Name, + m.filetree.GetSelectedItem().CurrentDirectory, + fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), + fmt.Sprintf("%s %s", "🗀", "FM"), + ) + } + } +} + +// togglePane toggles between the two panes. +func (m *model) togglePane() { + m.activeBox = (m.activeBox + 1) % 2 + + if m.activeBox == 0 { + m.deactivateAllBubbles() + m.filetree.SetIsActive(true) + } else { + switch m.state { + case idleState: + m.deactivateAllBubbles() + m.help.SetIsActive(true) + case showCodeState: + m.deactivateAllBubbles() + m.code.SetIsActive(true) + case showImageState: + m.deactivateAllBubbles() + m.image.SetIsActive(true) + case showMarkdownState: + m.deactivateAllBubbles() + m.markdown.SetIsActive(true) + case showPdfState: + m.deactivateAllBubbles() + m.markdown.SetIsActive(true) + } + } +} + +// openFile opens the currently selected file. +func (m *model) openFile() []tea.Cmd { + var cmds []tea.Cmd + + selectedFile := m.filetree.GetSelectedItem() + if !selectedFile.IsDirectory { + m.resetViewports() + + switch { + case selectedFile.Extension == ".png" || selectedFile.Extension == ".jpg" || selectedFile.Extension == ".jpeg": + m.state = showImageState + readFileCmd := m.image.SetFileName(selectedFile.Name) + cmds = append(cmds, readFileCmd) + case selectedFile.Extension == ".md" && m.config.PrettyMarkdown: + m.state = showMarkdownState + markdownCmd := m.markdown.SetFileName(selectedFile.Name) + cmds = append(cmds, markdownCmd) + case selectedFile.Extension == ".pdf": + m.state = showPdfState + pdfCmd := m.pdf.SetFileName(selectedFile.Name) + cmds = append(cmds, pdfCmd) + case contains(forbiddenExtensions, selectedFile.Extension): + return nil + default: + m.state = showCodeState + readFileCmd := m.code.SetFileName(selectedFile.Name) + cmds = append(cmds, readFileCmd) + } + } + + return cmds +} + +// resetViewports goes to the top of all bubbles viewports. +func (m *model) resetViewports() { + m.code.GotoTop() + m.pdf.GotoTop() + m.markdown.GotoTop() + m.help.GotoTop() + m.image.GotoTop() +} + +// deactivateAllBubbles sets all bubbles to inactive. +func (m *model) deactivateAllBubbles() { + m.filetree.SetIsActive(false) + m.code.SetIsActive(false) + m.markdown.SetIsActive(false) + m.image.SetIsActive(false) + m.pdf.SetIsActive(false) + m.help.SetIsActive(false) +} diff --git a/internal/tui/update.go b/internal/tui/update.go index 8211506..4f04abc 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -1,110 +1,11 @@ package tui import ( - "fmt" - "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/mistakenelf/fm/statusbar" ) -var forbiddenExtensions = []string{ - ".FCStd", - ".gif", - ".zip", - ".rar", - ".webm", - ".sqlite", - ".sqlite-shm", - ".sqlite-wal", - ".DS_Store", - ".db", - ".data", - ".plist", - ".webp", - ".img", -} - -// resetViewports goes to the top of all bubbles viewports. -func (m *model) resetViewports() { - m.code.GotoTop() - m.pdf.GotoTop() - m.markdown.GotoTop() - m.help.GotoTop() - m.image.GotoTop() -} - -// deactivateALlBubbles sets all bubbles to inactive. -func (m *model) deactivateAllBubbles() { - m.filetree.SetIsActive(false) - m.code.SetIsActive(false) - m.markdown.SetIsActive(false) - m.image.SetIsActive(false) - m.pdf.SetIsActive(false) - m.help.SetIsActive(false) -} - -// openFile opens the currently selected file. -func (m *model) openFile() []tea.Cmd { - var cmds []tea.Cmd - - selectedFile := m.filetree.GetSelectedItem() - if !selectedFile.IsDirectory { - m.resetViewports() - - switch { - case selectedFile.Extension == ".png" || selectedFile.Extension == ".jpg" || selectedFile.Extension == ".jpeg": - m.state = showImageState - readFileCmd := m.image.SetFileName(selectedFile.Name) - cmds = append(cmds, readFileCmd) - case selectedFile.Extension == ".md" && m.config.PrettyMarkdown: - m.state = showMarkdownState - markdownCmd := m.markdown.SetFileName(selectedFile.Name) - cmds = append(cmds, markdownCmd) - case selectedFile.Extension == ".pdf": - m.state = showPdfState - pdfCmd := m.pdf.SetFileName(selectedFile.Name) - cmds = append(cmds, pdfCmd) - case contains(forbiddenExtensions, selectedFile.Extension): - return nil - default: - m.state = showCodeState - readFileCmd := m.code.SetFileName(selectedFile.Name) - cmds = append(cmds, readFileCmd) - } - } - - return cmds -} - -// togglePane toggles between the two boxes. -func (m *model) togglePane() { - m.activeBox = (m.activeBox + 1) % 2 - - if m.activeBox == 0 { - m.deactivateAllBubbles() - m.filetree.SetIsActive(true) - } else { - switch m.state { - case idleState: - m.deactivateAllBubbles() - m.help.SetIsActive(true) - case showCodeState: - m.deactivateAllBubbles() - m.code.SetIsActive(true) - case showImageState: - m.deactivateAllBubbles() - m.image.SetIsActive(true) - case showMarkdownState: - m.deactivateAllBubbles() - m.markdown.SetIsActive(true) - case showPdfState: - m.deactivateAllBubbles() - m.markdown.SetIsActive(true) - } - } -} - // Update handles all UI interactions. func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var ( @@ -144,14 +45,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } - if m.filetree.GetSelectedItem().Name != "" { - m.statusbar.SetContent( - m.filetree.GetSelectedItem().Name, - m.filetree.GetSelectedItem().CurrentDirectory, - fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), - fmt.Sprintf("%s %s", "🗀", "FM"), - ) - } + m.updateStatusbarContent() m.code, cmd = m.code.Update(msg) cmds = append(cmds, cmd) diff --git a/statusbar/statusbar.go b/statusbar/statusbar.go index b5fe452..2510f4c 100644 --- a/statusbar/statusbar.go +++ b/statusbar/statusbar.go @@ -11,7 +11,6 @@ import ( // Height represents the height of the statusbar. const Height = 1 -// ColorConfig type ColorConfig struct { Foreground lipgloss.AdaptiveColor Background lipgloss.AdaptiveColor From 358868d514e04dcd4847ca8a89d4ef5cac7d1e88 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Wed, 13 Mar 2024 15:22:27 -0400 Subject: [PATCH 12/31] feat: more updates --- code/code.go | 81 +++++++++++++++++++++++++---------------- filetree/commands.go | 23 ++++++++++-- filetree/init.go | 2 +- filetree/methods.go | 6 +-- filetree/model.go | 10 +++-- filetree/update.go | 32 ++++++++++------ filetree/view.go | 6 +-- help/help.go | 1 - internal/tui/helpers.go | 80 ++++++++++------------------------------ internal/tui/model.go | 27 +++++++------- internal/tui/update.go | 25 +++++++++---- 11 files changed, 154 insertions(+), 139 deletions(-) diff --git a/code/code.go b/code/code.go index 9e65b3a..3d1d1d9 100644 --- a/code/code.go +++ b/code/code.go @@ -6,16 +6,31 @@ import ( "bytes" "fmt" "path/filepath" + "time" "github.com/alecthomas/chroma/quick" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/mistakenelf/fm/filesystem" ) type syntaxMsg string -type errorMsg error +type statusMessageTimeoutMsg struct{} +type errorMsg string + +// Model represents the properties of a code bubble. +type Model struct { + Viewport viewport.Model + Filename string + Content string + SyntaxTheme string + StatusMessage string + StatusMessageLifetime time.Duration + statusMessageTimer *time.Timer + ViewportDisabled bool +} // Highlight returns a syntax highlighted string of text. func Highlight(content, extension, syntaxTheme string) (string, error) { @@ -27,40 +42,48 @@ func Highlight(content, extension, syntaxTheme string) (string, error) { return buf.String(), nil } -// readFileContentCmd reads the content of the file. func readFileContentCmd(fileName, syntaxTheme string) tea.Cmd { return func() tea.Msg { content, err := filesystem.ReadFileContent(fileName) if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } highlightedContent, err := Highlight(content, filepath.Ext(fileName), syntaxTheme) if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } return syntaxMsg(highlightedContent) } } -// Model represents the properties of a code bubble. -type Model struct { - Viewport viewport.Model - Active bool - Filename string - HighlightedContent string - SyntaxTheme string +// NewStatusMessage sets a new status message, which will show for a limited +// amount of time. Note that this also returns a command. +func (m *Model) NewStatusMessage(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{} + } } // New creates a new instance of code. -func New(active bool) Model { +func New() Model { viewPort := viewport.New(0, 0) return Model{ - Viewport: viewPort, - Active: active, - SyntaxTheme: "dracula", + Viewport: viewPort, + SyntaxTheme: "dracula", + StatusMessage: "", + StatusMessageLifetime: time.Second, } } @@ -76,11 +99,6 @@ func (m *Model) SetFileName(filename string) tea.Cmd { return readFileContentCmd(filename, m.SyntaxTheme) } -// SetIsActive sets if the bubble is currently active. -func (m *Model) SetIsActive(active bool) { - m.Active = active -} - // SetSyntaxTheme sets the syntax theme of the rendered code. func (m *Model) SetSyntaxTheme(theme string) { m.SyntaxTheme = theme @@ -97,6 +115,11 @@ func (m *Model) GotoTop() { m.Viewport.GotoTop() } +// SetViewportDisabled toggles the state of the viewport. +func (m *Model) SetViewportDisabled(disabled bool) { + m.ViewportDisabled = disabled +} + // Update handles updating the UI of a code bubble. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var ( @@ -107,27 +130,21 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { switch msg := msg.(type) { case syntaxMsg: m.Filename = "" - m.HighlightedContent = lipgloss.NewStyle(). + m.Content = lipgloss.NewStyle(). Width(m.Viewport.Width). Height(m.Viewport.Height). Render(string(msg)) - m.Viewport.SetContent(m.HighlightedContent) + m.Viewport.SetContent(m.Content) - return m, nil + case statusMessageTimeoutMsg: + m.StatusMessage = "" case errorMsg: m.Filename = "" - m.HighlightedContent = lipgloss.NewStyle(). - Width(m.Viewport.Width). - Height(m.Viewport.Height). - Render("Error: " + msg.Error()) - - m.Viewport.SetContent(m.HighlightedContent) - - return m, nil + cmds = append(cmds, m.NewStatusMessage(lipgloss.NewStyle().Foreground(lipgloss.Color("#cc241d")).Bold(true).Render(string(msg)))) } - if m.Active { + if !m.ViewportDisabled { m.Viewport, cmd = m.Viewport.Update(msg) cmds = append(cmds, cmd) } diff --git a/filetree/commands.go b/filetree/commands.go index 8f9ed26..633020b 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -2,6 +2,7 @@ package filetree import ( "fmt" + "io/fs" "os" "path/filepath" "time" @@ -17,10 +18,11 @@ type copyToClipboardMsg string type statusMessageTimeoutMsg struct{} // getDirectoryListingCmd updates the directory listing based on the name of the directory provided. -func getDirectoryListingCmd(directoryName string, showHidden bool) tea.Cmd { +func getDirectoryListingCmd(directoryName string, showHidden, directoriesOnly, filesOnly bool) tea.Cmd { return func() tea.Msg { var err error var directoryItems []DirectoryItem + var files []fs.DirEntry if directoryName == filesystem.HomeDirectory { directoryName, err = filesystem.GetHomeDirectory() @@ -43,9 +45,22 @@ func getDirectoryListingCmd(directoryName string, showHidden bool) tea.Cmd { return errorMsg(err.Error()) } - files, err := filesystem.GetDirectoryListing(directoryName, showHidden) - if err != nil { - return errorMsg(err.Error()) + if !directoriesOnly && !filesOnly { + files, err = filesystem.GetDirectoryListing(directoryName, showHidden) + if err != nil { + return errorMsg(err.Error()) + } + } else { + listingType := filesystem.DirectoriesListingType + + if filesOnly { + listingType = filesystem.FilesListingType + } + + files, err = filesystem.GetDirectoryListingByType(directoryName, listingType, showHidden) + if err != nil { + return errorMsg(err.Error()) + } } workingDirectory, err := filesystem.GetWorkingDirectory() diff --git a/filetree/init.go b/filetree/init.go index 54d64c0..da8a50d 100644 --- a/filetree/init.go +++ b/filetree/init.go @@ -5,5 +5,5 @@ import ( ) func (m Model) Init() tea.Cmd { - return getDirectoryListingCmd(m.startDir, true) + return getDirectoryListingCmd(m.startDir, true, false, false) } diff --git a/filetree/methods.go b/filetree/methods.go index 6d95246..bcc841b 100644 --- a/filetree/methods.go +++ b/filetree/methods.go @@ -40,9 +40,9 @@ func ConvertBytesToSizeString(size int64) string { return "" } -// SetIsActive sets if the bubble is currently active. -func (m *Model) SetIsActive(active bool) { - m.Active = active +// SetDisabled sets if the bubble is currently active. +func (m *Model) SetDisabled(disabled bool) { + m.Disabled = disabled } // GetSelectedItem returns the currently selected file/dir. diff --git a/filetree/model.go b/filetree/model.go index 090ac7c..1363bf8 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -18,7 +18,7 @@ type DirectoryItem struct { type Model struct { Cursor int files []DirectoryItem - Active bool + Disabled bool keyMap KeyMap min int max int @@ -26,12 +26,14 @@ type Model struct { width int startDir string showHidden bool + showDirectoriesOnly bool + showFilesOnly bool StatusMessage string StatusMessageLifetime time.Duration statusMessageTimer *time.Timer } -func New(active bool, startDir string) Model { +func New(startDir string) Model { startingDirectory := filesystem.CurrentDirectory if startDir != "" { @@ -40,7 +42,7 @@ func New(active bool, startDir string) Model { return Model{ Cursor: 0, - Active: active, + Disabled: false, keyMap: DefaultKeyMap(), min: 0, max: 0, @@ -48,5 +50,7 @@ func New(active bool, startDir string) Model { showHidden: true, StatusMessage: "", StatusMessageLifetime: time.Second, + showFilesOnly: false, + showDirectoriesOnly: false, } } diff --git a/filetree/update.go b/filetree/update.go index 3cbb9cc..7c788d0 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -14,7 +14,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { cmds []tea.Cmd ) - if !m.Active { + if m.Disabled { return m, nil } @@ -24,7 +24,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case statusMessageTimeoutMsg: m.StatusMessage = "" case copyToClipboardMsg: - m.StatusMessage = string(msg) + cmds = append(cmds, m.NewStatusMessage(lipgloss.NewStyle().Bold(true).Render(string(msg)))) case getDirectoryListingMsg: if msg != nil { m.files = msg @@ -88,41 +88,51 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.max = m.min + m.height } case key.Matches(msg, m.keyMap.GoToHomeDirectory): - return m, getDirectoryListingCmd(filesystem.HomeDirectory, m.showHidden) + return m, getDirectoryListingCmd(filesystem.HomeDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) case key.Matches(msg, m.keyMap.GoToRootDirectory): - return m, getDirectoryListingCmd(filesystem.RootDirectory, m.showHidden) + return m, getDirectoryListingCmd(filesystem.RootDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) case key.Matches(msg, m.keyMap.ToggleHidden): m.showHidden = !m.showHidden - return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden) + return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) case key.Matches(msg, m.keyMap.OpenDirectory): if m.files[m.Cursor].IsDirectory { - return m, getDirectoryListingCmd(m.files[m.Cursor].Path, m.showHidden) + return m, getDirectoryListingCmd(m.files[m.Cursor].Path, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) } case key.Matches(msg, m.keyMap.PreviousDirectory): - return m, getDirectoryListingCmd(filepath.Dir(m.files[m.Cursor].CurrentDirectory), m.showHidden) + return m, getDirectoryListingCmd(filepath.Dir(m.files[m.Cursor].CurrentDirectory), m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) case key.Matches(msg, m.keyMap.CopyPathToClipboard): return m, copyToClipboardCmd(m.files[m.Cursor].Name) case key.Matches(msg, m.keyMap.CopyDirectoryItem): return m, tea.Sequence( copyDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), ) case key.Matches(msg, m.keyMap.DeleteDirectoryItem): return m, tea.Sequence( deleteDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), ) case key.Matches(msg, m.keyMap.ZipDirectoryItem): return m, tea.Sequence( zipDirectoryCmd(m.files[m.Cursor].Name), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), ) case key.Matches(msg, m.keyMap.UnzipDirectoryItem): return m, tea.Sequence( unzipDirectoryCmd(m.files[m.Cursor].Name), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), ) + case key.Matches(msg, m.keyMap.ShowDirectoriesOnly): + m.showDirectoriesOnly = !m.showDirectoriesOnly + m.showFilesOnly = false + + return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) + case key.Matches(msg, m.keyMap.ShowFilesOnly): + m.showFilesOnly = !m.showFilesOnly + m.showDirectoriesOnly = false + + return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) } } diff --git a/filetree/view.go b/filetree/view.go index f6ecd7f..de3e11c 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -15,7 +15,7 @@ func (m Model) View() string { } switch { - case !m.Active: + case m.Disabled: if file.IsDirectory { fileList.WriteString(inactiveStyle.Render("🗀 ")) } else { @@ -23,7 +23,7 @@ func (m Model) View() string { } fileList.WriteString(inactiveStyle.Render(file.Name) + "\n") - case i == m.Cursor && m.Active: + case i == m.Cursor && !m.Disabled: if file.IsDirectory { fileList.WriteString(selectedItemStyle.Render("🗀 ")) } else { @@ -31,7 +31,7 @@ func (m Model) View() string { } fileList.WriteString(selectedItemStyle.Render(file.Name) + "\n") - case i != m.Cursor && m.Active: + case i != m.Cursor && !m.Disabled: if file.IsDirectory { fileList.WriteString(unselectedItemStyle.Render("🗀 ")) } else { diff --git a/help/help.go b/help/help.go index 88d74ab..941a239 100644 --- a/help/help.go +++ b/help/help.go @@ -34,7 +34,6 @@ type Model struct { Active bool } -// generateHelpScreen generates the help text based on the title and entries. func generateHelpScreen(title string, titleColor TitleColor, entries []Entry, width, height int) string { helpScreen := "" diff --git a/internal/tui/helpers.go b/internal/tui/helpers.go index 9720db1..810c90c 100644 --- a/internal/tui/helpers.go +++ b/internal/tui/helpers.go @@ -36,83 +36,51 @@ func contains(s []string, str string) bool { func (m *model) updateStatusbarContent() { if m.filetree.GetSelectedItem().Name != "" { + statusMessage := m.filetree.GetSelectedItem().CurrentDirectory + if m.filetree.StatusMessage != "" { - m.statusbar.SetContent( - m.filetree.GetSelectedItem().Name, - m.filetree.StatusMessage, - fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), - fmt.Sprintf("%s %s", "🗀", "FM"), - ) - } else { - m.statusbar.SetContent( - m.filetree.GetSelectedItem().Name, - m.filetree.GetSelectedItem().CurrentDirectory, - fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), - fmt.Sprintf("%s %s", "🗀", "FM"), - ) + statusMessage = m.filetree.StatusMessage } - } -} -// togglePane toggles between the two panes. -func (m *model) togglePane() { - m.activeBox = (m.activeBox + 1) % 2 - - if m.activeBox == 0 { - m.deactivateAllBubbles() - m.filetree.SetIsActive(true) - } else { - switch m.state { - case idleState: - m.deactivateAllBubbles() - m.help.SetIsActive(true) - case showCodeState: - m.deactivateAllBubbles() - m.code.SetIsActive(true) - case showImageState: - m.deactivateAllBubbles() - m.image.SetIsActive(true) - case showMarkdownState: - m.deactivateAllBubbles() - m.markdown.SetIsActive(true) - case showPdfState: - m.deactivateAllBubbles() - m.markdown.SetIsActive(true) + if m.code.StatusMessage != "" { + statusMessage = m.code.StatusMessage } + + m.statusbar.SetContent( + m.filetree.GetSelectedItem().Name, + statusMessage, + fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), + fmt.Sprintf("%s %s", "🗀", "FM"), + ) } } // openFile opens the currently selected file. -func (m *model) openFile() []tea.Cmd { - var cmds []tea.Cmd - +func (m *model) openFile() tea.Cmd { selectedFile := m.filetree.GetSelectedItem() + if !selectedFile.IsDirectory { m.resetViewports() switch { case selectedFile.Extension == ".png" || selectedFile.Extension == ".jpg" || selectedFile.Extension == ".jpeg": m.state = showImageState - readFileCmd := m.image.SetFileName(selectedFile.Name) - cmds = append(cmds, readFileCmd) + return m.image.SetFileName(selectedFile.Name) case selectedFile.Extension == ".md" && m.config.PrettyMarkdown: m.state = showMarkdownState - markdownCmd := m.markdown.SetFileName(selectedFile.Name) - cmds = append(cmds, markdownCmd) + return m.markdown.SetFileName(selectedFile.Name) case selectedFile.Extension == ".pdf": m.state = showPdfState - pdfCmd := m.pdf.SetFileName(selectedFile.Name) - cmds = append(cmds, pdfCmd) + return m.pdf.SetFileName(selectedFile.Name) case contains(forbiddenExtensions, selectedFile.Extension): return nil default: m.state = showCodeState - readFileCmd := m.code.SetFileName(selectedFile.Name) - cmds = append(cmds, readFileCmd) + return m.code.SetFileName(selectedFile.Name) } } - return cmds + return nil } // resetViewports goes to the top of all bubbles viewports. @@ -123,13 +91,3 @@ func (m *model) resetViewports() { m.help.GotoTop() m.image.GotoTop() } - -// deactivateAllBubbles sets all bubbles to inactive. -func (m *model) deactivateAllBubbles() { - m.filetree.SetIsActive(false) - m.code.SetIsActive(false) - m.markdown.SetIsActive(false) - m.image.SetIsActive(false) - m.pdf.SetIsActive(false) - m.help.SetIsActive(false) -} diff --git a/internal/tui/model.go b/internal/tui/model.go index 49b51f0..31df20a 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -19,6 +19,7 @@ const ( showImageState showMarkdownState showPdfState + showHelpState ) type Config struct { @@ -30,24 +31,24 @@ type Config struct { } type model struct { - filetree filetree.Model - help help.Model - code code.Model - image image.Model - markdown markdown.Model - pdf pdf.Model - statusbar statusbar.Model - state sessionState - keys KeyMap - activeBox int - config Config + filetree filetree.Model + help help.Model + code code.Model + image image.Model + markdown markdown.Model + pdf pdf.Model + statusbar statusbar.Model + state sessionState + keys KeyMap + activePane int + config Config } // New creates a new instance of the UI. func New(cfg Config) model { - filetreeModel := filetree.New(true, cfg.StartDir) + filetreeModel := filetree.New(cfg.StartDir) - codeModel := code.New(false) + codeModel := code.New() codeModel.SetSyntaxTheme("pygments") imageModel := image.New(false) diff --git a/internal/tui/update.go b/internal/tui/update.go index 4f04abc..29ea8f4 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -3,6 +3,7 @@ package tui import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" + "github.com/mistakenelf/fm/statusbar" ) @@ -13,9 +14,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds []tea.Cmd ) - m.filetree, cmd = m.filetree.Update(msg) - cmds = append(cmds, cmd) - switch msg := msg.(type) { case tea.WindowSizeMsg: halfSize := msg.Width / 2 @@ -38,14 +36,25 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit case key.Matches(msg, m.keys.Exit): return m, tea.Quit - case key.Matches(msg, m.keys.OpenFile): - cmds = append(cmds, tea.Batch(m.openFile()...)) case key.Matches(msg, m.keys.TogglePane): - m.togglePane() + m.activePane = (m.activePane + 1) % 2 + + if m.activePane == 0 { + m.filetree.SetDisabled(false) + } else { + m.filetree.SetDisabled(true) + } + + return m, nil } } - m.updateStatusbarContent() + if m.filetree.GetSelectedItem().Name != "" { + cmds = append(cmds, m.openFile()) + } + + m.filetree, cmd = m.filetree.Update(msg) + cmds = append(cmds, cmd) m.code, cmd = m.code.Update(msg) cmds = append(cmds, cmd) @@ -62,5 +71,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.help, cmd = m.help.Update(msg) cmds = append(cmds, cmd) + m.updateStatusbarContent() + return m, tea.Batch(cmds...) } From 6c129662b124cda3c4836959c6d04de5e94d8624 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Wed, 13 Mar 2024 20:46:14 -0400 Subject: [PATCH 13/31] feat: viewport update fix --- Makefile | 2 +- cmd/root.go | 3 ++- code/code.go | 4 ++-- go.mod | 3 ++- go.sum | 8 ++++++++ help/help.go | 29 ++++++++++++++--------------- image/image.go | 36 +++++++++++++++++------------------- internal/tui/helpers.go | 15 ++++++++++++--- internal/tui/keys.go | 26 ++++++++------------------ internal/tui/model.go | 19 +++++++++++++------ internal/tui/update.go | 37 +++++++++++++++++++++++++++---------- markdown/markdown.go | 26 +++++++++++++------------- pdf/pdf.go | 29 ++++++++++++++--------------- statusbar/statusbar.go | 6 ++++-- 14 files changed, 137 insertions(+), 106 deletions(-) diff --git a/Makefile b/Makefile index 727334e..23d88a4 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ make: - go run main.go + go run main.go --theme=nord test: go test ./... -short diff --git a/cmd/root.go b/cmd/root.go index d85bfa8..16bbee7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,10 +6,11 @@ import ( "os" tea "github.com/charmbracelet/bubbletea" + "github.com/spf13/cobra" + "github.com/mistakenelf/fm/filesystem" "github.com/mistakenelf/fm/internal/theme" "github.com/mistakenelf/fm/internal/tui" - "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ diff --git a/code/code.go b/code/code.go index 3d1d1d9..2d7d841 100644 --- a/code/code.go +++ b/code/code.go @@ -8,7 +8,7 @@ import ( "path/filepath" "time" - "github.com/alecthomas/chroma/quick" + "github.com/alecthomas/chroma/v2/quick" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -120,7 +120,7 @@ func (m *Model) SetViewportDisabled(disabled bool) { m.ViewportDisabled = disabled } -// Update handles updating the UI of a code bubble. +// Update handles updating the UI of the code bubble. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var ( cmd tea.Cmd diff --git a/go.mod b/go.mod index 375dfb1..0b69bc3 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/mistakenelf/fm go 1.22 require ( - github.com/alecthomas/chroma v0.10.0 + github.com/alecthomas/chroma/v2 v2.13.0 github.com/atotto/clipboard v0.1.4 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 @@ -17,6 +17,7 @@ require ( ) require ( + github.com/alecthomas/chroma v0.10.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/containerd/console v1.0.4 // indirect diff --git a/go.sum b/go.sum index df52659..352296d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,11 @@ +github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= +github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= +github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= @@ -29,6 +35,8 @@ github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06 h1:kacRlPN7EN++tVpGUorNGPn/4DnB7/DfTY82AOn6ccU= diff --git a/help/help.go b/help/help.go index 941a239..9021064 100644 --- a/help/help.go +++ b/help/help.go @@ -27,11 +27,11 @@ type Entry struct { // Model represents the properties of a help bubble. type Model struct { - Viewport viewport.Model - Entries []Entry - Title string - TitleColor TitleColor - Active bool + Viewport viewport.Model + Entries []Entry + Title string + TitleColor TitleColor + ViewportDisabled bool } func generateHelpScreen(title string, titleColor TitleColor, entries []Entry, width, height int) string { @@ -71,7 +71,6 @@ func generateHelpScreen(title string, titleColor TitleColor, entries []Entry, wi // New creates a new instance of a help bubble. func New( - active bool, title string, titleColor TitleColor, entries []Entry, @@ -80,11 +79,11 @@ func New( viewPort.SetContent(generateHelpScreen(title, titleColor, entries, 0, 0)) return Model{ - Viewport: viewPort, - Entries: entries, - Title: title, - Active: active, - TitleColor: titleColor, + Viewport: viewPort, + Entries: entries, + Title: title, + ViewportDisabled: false, + TitleColor: titleColor, } } @@ -96,9 +95,9 @@ func (m *Model) SetSize(w, h int) { m.Viewport.SetContent(generateHelpScreen(m.Title, m.TitleColor, m.Entries, m.Viewport.Width, m.Viewport.Height)) } -// SetIsActive sets if the bubble is currently active. -func (m *Model) SetIsActive(active bool) { - m.Active = active +// SetViewportDisabled toggles the state of the viewport. +func (m *Model) SetViewportDisabled(disabled bool) { + m.ViewportDisabled = disabled } // GotoTop jumps to the top of the viewport. @@ -120,7 +119,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { cmds []tea.Cmd ) - if m.Active { + if !m.ViewportDisabled { m.Viewport, cmd = m.Viewport.Update(msg) cmds = append(cmds, cmd) } diff --git a/image/image.go b/image/image.go index e3c2395..0736c11 100644 --- a/image/image.go +++ b/image/image.go @@ -46,7 +46,6 @@ func ToString(width int, img image.Image) string { return str.String() } -// convertImageToStringCmd redraws the image based on the width provided. func convertImageToStringCmd(width int, filename string) tea.Cmd { return func() tea.Msg { imageContent, err := os.Open(filepath.Clean(filename)) @@ -65,31 +64,30 @@ func convertImageToStringCmd(width int, filename string) tea.Cmd { } } -// Model represents the properties of a code bubble. +// Model represents the properties of a image bubble. type Model struct { - Viewport viewport.Model - Active bool - FileName string - ImageString string + Viewport viewport.Model + ViewportDisabled bool + FileName string + ImageString string } -// New creates a new instance of code. -func New(active bool) Model { +// New creates a new instance of an image. +func New() Model { viewPort := viewport.New(0, 0) return Model{ - Viewport: viewPort, - Active: active, + Viewport: viewPort, + ViewportDisabled: false, } } -// Init initializes the code bubble. +// Init initializes the image bubble. func (m Model) Init() tea.Cmd { return nil } -// SetFileName sets current file to highlight, this -// returns a cmd which will highlight the text. +// SetFileName sets the image file and convers it to a string. func (m *Model) SetFileName(filename string) tea.Cmd { m.FileName = filename @@ -108,9 +106,9 @@ func (m *Model) SetSize(w, h int) tea.Cmd { return nil } -// SetIsActive sets if the bubble is currently active -func (m *Model) SetIsActive(active bool) { - m.Active = active +// SetViewportDisabled toggles the state of the viewport. +func (m *Model) SetViewportDisabled(disabled bool) { + m.ViewportDisabled = disabled } // GotoTop jumps to the top of the viewport. @@ -118,7 +116,7 @@ func (m *Model) GotoTop() { m.Viewport.GotoTop() } -// Update handles updating the UI of a code bubble. +// Update handles updating the UI of the image bubble. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var ( cmd tea.Cmd @@ -147,7 +145,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, nil } - if m.Active { + if !m.ViewportDisabled { m.Viewport, cmd = m.Viewport.Update(msg) cmds = append(cmds, cmd) } @@ -155,7 +153,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, tea.Batch(cmds...) } -// View returns a string representation of the code bubble. +// View returns a string representation of the image bubble. func (m Model) View() string { return m.Viewport.View() } diff --git a/internal/tui/helpers.go b/internal/tui/helpers.go index 810c90c..254bdd3 100644 --- a/internal/tui/helpers.go +++ b/internal/tui/helpers.go @@ -23,7 +23,6 @@ var forbiddenExtensions = []string{ ".img", } -// contains returns true if the slice contains the string. func contains(s []string, str string) bool { for _, v := range s { if v == str { @@ -55,7 +54,6 @@ func (m *model) updateStatusbarContent() { } } -// openFile opens the currently selected file. func (m *model) openFile() tea.Cmd { selectedFile := m.filetree.GetSelectedItem() @@ -65,17 +63,21 @@ func (m *model) openFile() tea.Cmd { switch { case selectedFile.Extension == ".png" || selectedFile.Extension == ".jpg" || selectedFile.Extension == ".jpeg": m.state = showImageState + return m.image.SetFileName(selectedFile.Name) case selectedFile.Extension == ".md" && m.config.PrettyMarkdown: m.state = showMarkdownState + return m.markdown.SetFileName(selectedFile.Name) case selectedFile.Extension == ".pdf": m.state = showPdfState + return m.pdf.SetFileName(selectedFile.Name) case contains(forbiddenExtensions, selectedFile.Extension): return nil default: m.state = showCodeState + return m.code.SetFileName(selectedFile.Name) } } @@ -83,7 +85,14 @@ func (m *model) openFile() tea.Cmd { return nil } -// resetViewports goes to the top of all bubbles viewports. +func (m *model) disableAllViewports() { + m.code.SetViewportDisabled(true) + m.pdf.SetViewportDisabled(true) + m.markdown.SetViewportDisabled(true) + m.help.SetViewportDisabled(true) + m.image.SetViewportDisabled(true) +} + func (m *model) resetViewports() { m.code.GotoTop() m.pdf.GotoTop() diff --git a/internal/tui/keys.go b/internal/tui/keys.go index 2d35e16..1cc130c 100644 --- a/internal/tui/keys.go +++ b/internal/tui/keys.go @@ -2,28 +2,18 @@ package tui import "github.com/charmbracelet/bubbles/key" -// KeyMap defines the keybindings for the app. -type KeyMap struct { +type keyMap struct { Quit key.Binding - Exit key.Binding TogglePane key.Binding OpenFile key.Binding + ResetState key.Binding } -// DefaultKeyMap returns a set of default keybindings. -func DefaultKeyMap() KeyMap { - return KeyMap{ - Quit: key.NewBinding( - key.WithKeys("ctrl+c"), - ), - Exit: key.NewBinding( - key.WithKeys("q"), - ), - TogglePane: key.NewBinding( - key.WithKeys("tab"), - ), - OpenFile: key.NewBinding( - key.WithKeys(" "), - ), +func defaultKeyMap() keyMap { + return keyMap{ + Quit: key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "quit")), + TogglePane: key.NewBinding(key.WithKeys("tab"), key.WithHelp("tab", "toggle pane")), + OpenFile: key.NewBinding(key.WithKeys("l"), key.WithHelp("l", "open file")), + ResetState: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "reset state")), } } diff --git a/internal/tui/model.go b/internal/tui/model.go index 31df20a..24116f9 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -39,7 +39,7 @@ type model struct { pdf pdf.Model statusbar statusbar.Model state sessionState - keys KeyMap + keyMap keyMap activePane int config Config } @@ -50,10 +50,17 @@ func New(cfg Config) model { codeModel := code.New() codeModel.SetSyntaxTheme("pygments") + codeModel.SetViewportDisabled(true) + + imageModel := image.New() + imageModel.SetViewportDisabled(true) + + markdownModel := markdown.New() + markdownModel.SetViewportDisabled(true) + + pdfModel := pdf.New() + pdfModel.SetViewportDisabled(true) - imageModel := image.New(false) - markdownModel := markdown.New(false) - pdfModel := pdf.New(false) statusbarModel := statusbar.New( statusbar.ColorConfig{ Foreground: cfg.Theme.StatusBarSelectedFileForegroundColor, @@ -74,7 +81,6 @@ func New(cfg Config) model { ) helpModel := help.New( - false, "Help", help.TitleColor{ Background: cfg.Theme.TitleBackgroundColor, @@ -106,6 +112,7 @@ func New(cfg Config) model { {Key: "tab", Description: "Toggle between boxes"}, }, ) + helpModel.SetViewportDisabled(true) return model{ filetree: filetreeModel, @@ -116,6 +123,6 @@ func New(cfg Config) model { pdf: pdfModel, statusbar: statusbarModel, config: cfg, - keys: DefaultKeyMap(), + keyMap: defaultKeyMap(), } } diff --git a/internal/tui/update.go b/internal/tui/update.go index 29ea8f4..64ee84b 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -32,27 +32,44 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) case tea.KeyMsg: switch { - case key.Matches(msg, m.keys.Quit): + case key.Matches(msg, m.keyMap.Quit): return m, tea.Quit - case key.Matches(msg, m.keys.Exit): - return m, tea.Quit - case key.Matches(msg, m.keys.TogglePane): + case key.Matches(msg, m.keyMap.OpenFile): + cmds = append(cmds, m.openFile()) + case key.Matches(msg, m.keyMap.ResetState): + m.state = idleState + m.disableAllViewports() + m.resetViewports() + case key.Matches(msg, m.keyMap.TogglePane): m.activePane = (m.activePane + 1) % 2 if m.activePane == 0 { m.filetree.SetDisabled(false) + m.disableAllViewports() } else { m.filetree.SetDisabled(true) - } - return m, nil + switch m.state { + case idleState: + m.disableAllViewports() + m.help.SetViewportDisabled(false) + case showCodeState: + m.disableAllViewports() + m.code.SetViewportDisabled(false) + case showImageState: + m.disableAllViewports() + m.image.SetViewportDisabled(false) + case showPdfState: + m.disableAllViewports() + m.pdf.SetViewportDisabled(false) + case showMarkdownState: + m.disableAllViewports() + m.markdown.SetViewportDisabled(false) + } + } } } - if m.filetree.GetSelectedItem().Name != "" { - cmds = append(cmds, m.openFile()) - } - m.filetree, cmd = m.filetree.Update(msg) cmds = append(cmds, cmd) diff --git a/markdown/markdown.go b/markdown/markdown.go index 7fe8539..c8d2f34 100644 --- a/markdown/markdown.go +++ b/markdown/markdown.go @@ -9,17 +9,18 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" "github.com/charmbracelet/lipgloss" + "github.com/mistakenelf/fm/filesystem" ) type renderMarkdownMsg string type errorMsg error -// Model represents the properties of a code bubble. +// Model represents the properties of a markdown bubble. type Model struct { - Viewport viewport.Model - Active bool - FileName string + Viewport viewport.Model + ViewportDisabled bool + FileName string } // RenderMarkdown renders the markdown content with glamour. @@ -43,7 +44,6 @@ func RenderMarkdown(width int, content string) (string, error) { return out, nil } -// renderMarkdownCmd renders text as pretty markdown. func renderMarkdownCmd(width int, filename string) tea.Cmd { return func() tea.Msg { content, err := filesystem.ReadFileContent(filename) @@ -61,12 +61,12 @@ func renderMarkdownCmd(width int, filename string) tea.Cmd { } // New creates a new instance of markdown. -func New(active bool) Model { +func New() Model { viewPort := viewport.New(0, 0) return Model{ - Viewport: viewPort, - Active: active, + Viewport: viewPort, + ViewportDisabled: false, } } @@ -100,12 +100,12 @@ func (m *Model) GotoTop() { m.Viewport.GotoTop() } -// SetIsActive sets if the bubble is currently active. -func (m *Model) SetIsActive(active bool) { - m.Active = active +// SetViewportDisabled toggles the state of the viewport. +func (m *Model) SetViewportDisabled(disabled bool) { + m.ViewportDisabled = disabled } -// Update handles updating the UI of a code bubble. +// Update handles updating the UI of a markdown bubble. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var ( cmd tea.Cmd @@ -129,7 +129,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, nil } - if m.Active { + if !m.ViewportDisabled { m.Viewport, cmd = m.Viewport.Update(msg) cmds = append(cmds, cmd) } diff --git a/pdf/pdf.go b/pdf/pdf.go index ae4693f..146a85e 100644 --- a/pdf/pdf.go +++ b/pdf/pdf.go @@ -17,13 +17,13 @@ type errorMsg error // Model represents the properties of a pdf bubble. type Model struct { - Viewport viewport.Model - Active bool - FileName string + Viewport viewport.Model + ViewportDisabled bool + FileName string } -// readPdf reads a PDF file given a name. -func readPdf(name string) (string, error) { +// ReadPDF reads the content of a PDF and returns it as a string. +func ReadPDF(name string) (string, error) { file, reader, err := pdf.Open(name) if err != nil { return "", errors.Unwrap(err) @@ -50,10 +50,9 @@ func readPdf(name string) (string, error) { return buf.String(), nil } -// renderPDFCmd reads the content of a PDF and returns its content as a string. func renderPDFCmd(filename string) tea.Cmd { return func() tea.Msg { - pdfContent, err := readPdf(filename) + pdfContent, err := ReadPDF(filename) if err != nil { return errorMsg(err) } @@ -63,12 +62,12 @@ func renderPDFCmd(filename string) tea.Cmd { } // New creates a new instance of a PDF. -func New(active bool) Model { +func New() Model { viewPort := viewport.New(0, 0) return Model{ - Viewport: viewPort, - Active: active, + Viewport: viewPort, + ViewportDisabled: false, } } @@ -91,9 +90,9 @@ func (m *Model) SetSize(w, h int) { m.Viewport.Height = h } -// SetIsActive sets if the bubble is currently active. -func (m *Model) SetIsActive(active bool) { - m.Active = active +// SetViewportDisabled toggles the state of the viewport. +func (m *Model) SetViewportDisabled(disabled bool) { + m.ViewportDisabled = disabled } // GotoTop jumps to the top of the viewport. @@ -101,7 +100,7 @@ func (m *Model) GotoTop() { m.Viewport.GotoTop() } -// Update handles updating the UI of a code bubble. +// Update handles updating the UI of the pdf bubble. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var ( cmd tea.Cmd @@ -125,7 +124,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, nil } - if m.Active { + if !m.ViewportDisabled { m.Viewport, cmd = m.Viewport.Update(msg) cmds = append(cmds, cmd) } diff --git a/statusbar/statusbar.go b/statusbar/statusbar.go index 2510f4c..92c7b4f 100644 --- a/statusbar/statusbar.go +++ b/statusbar/statusbar.go @@ -11,6 +11,8 @@ import ( // Height represents the height of the statusbar. const Height = 1 +// ColorConfig represents the the foreground and background +// color configuration. type ColorConfig struct { Foreground lipgloss.AdaptiveColor Background lipgloss.AdaptiveColor @@ -45,7 +47,7 @@ func (m *Model) SetSize(width int) { m.Width = width } -// Update updates the size of the statusbar. +// Update updates the UI of the statusbar. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: @@ -71,7 +73,7 @@ func (m *Model) SetColors(firstColumnColors, secondColumnColors, thirdColumnColo m.FourthColumnColors = fourthColumnColors } -// View returns a string representation of a statusbar. +// View returns a string representation of the statusbar. func (m Model) View() string { width := lipgloss.Width From 23e6e5f2e97880089001318556cc89acae4e9398 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Thu, 14 Mar 2024 13:07:17 -0400 Subject: [PATCH 14/31] fix: ensure flags are all working --- Makefile | 2 +- cmd/root.go | 7 +++++++ filetree/commands.go | 2 +- filetree/keys.go | 2 ++ filetree/methods.go | 18 ++++++++++++++++++ filetree/model.go | 10 ++++++++++ filetree/styles.go | 9 --------- filetree/update.go | 5 +++++ filetree/view.go | 36 +++++++++++++++++++++--------------- internal/theme/theme.go | 11 +---------- internal/tui/model.go | 4 ++++ 11 files changed, 70 insertions(+), 36 deletions(-) delete mode 100644 filetree/styles.go diff --git a/Makefile b/Makefile index 23d88a4..727334e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ make: - go run main.go --theme=nord + go run main.go test: go test ./... -short diff --git a/cmd/root.go b/cmd/root.go index 16bbee7..00194c7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -44,6 +44,11 @@ var rootCmd = &cobra.Command{ log.Fatal(err) } + showIcons, err := cmd.Flags().GetBool("show-icons") + if err != nil { + log.Fatal(err) + } + // If logging is enabled, logs will be output to debug.log. if enableLogging { f, err := tea.LogToFile("debug.log", "debug") @@ -66,6 +71,7 @@ var rootCmd = &cobra.Command{ EnableLogging: enableLogging, PrettyMarkdown: prettyMarkdown, Theme: appTheme, + ShowIcons: showIcons, } m := tui.New(cfg) @@ -86,6 +92,7 @@ func Execute() { rootCmd.PersistentFlags().Bool("enable-logging", false, "Enable logging for FM") rootCmd.PersistentFlags().Bool("pretty-markdown", true, "Render markdown to look nice") rootCmd.PersistentFlags().String("theme", "default", "Application theme") + rootCmd.PersistentFlags().Bool("show-icons", true, "Show icons") if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) diff --git a/filetree/commands.go b/filetree/commands.go index 633020b..29083a0 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -203,13 +203,13 @@ func writeSelectionPathCmd(selectionPath, filePath string) tea.Cmd { // amount of time. Note that this also returns a command. func (m *Model) NewStatusMessage(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{} diff --git a/filetree/keys.go b/filetree/keys.go index e7abc3f..9f077e3 100644 --- a/filetree/keys.go +++ b/filetree/keys.go @@ -21,6 +21,7 @@ type KeyMap struct { UnzipDirectoryItem key.Binding ShowDirectoriesOnly key.Binding ShowFilesOnly key.Binding + WriteSelectionPath key.Binding } func DefaultKeyMap() KeyMap { @@ -43,5 +44,6 @@ func DefaultKeyMap() KeyMap { UnzipDirectoryItem: key.NewBinding(key.WithKeys("U"), key.WithHelp("U", "unzip directory item")), ShowDirectoriesOnly: key.NewBinding(key.WithKeys("D"), key.WithHelp("D", "show directories only")), ShowFilesOnly: key.NewBinding(key.WithKeys("F"), key.WithHelp("F", "show files only")), + WriteSelectionPath: key.NewBinding(key.WithKeys("S"), key.WithHelp("S", "write selection path")), } } diff --git a/filetree/methods.go b/filetree/methods.go index bcc841b..43d5cb9 100644 --- a/filetree/methods.go +++ b/filetree/methods.go @@ -2,6 +2,8 @@ package filetree import ( "fmt" + + "github.com/charmbracelet/lipgloss" ) const ( @@ -65,3 +67,19 @@ func (m *Model) SetSize(width, height int) { m.width = width m.max = m.height - 1 } + +// SetTheme sets the theme of the tree. +func (m *Model) SetTheme(selectedItemColor, unselectedItemColor lipgloss.AdaptiveColor) { + m.selectedItemColor = selectedItemColor + m.unselectedItemColor = unselectedItemColor +} + +// SetSelectionPath sets the selection path to be written. +func (m *Model) SetSelectionPath(path string) { + m.selectionPath = path +} + +// SetShowIcons sets whether icons will show or not. +func (m *Model) SetShowIcons(show bool) { + m.showIcons = show +} diff --git a/filetree/model.go b/filetree/model.go index 1363bf8..d73b062 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -3,6 +3,7 @@ package filetree import ( "time" + "github.com/charmbracelet/lipgloss" "github.com/mistakenelf/fm/filesystem" ) @@ -31,6 +32,11 @@ type Model struct { StatusMessage string StatusMessageLifetime time.Duration statusMessageTimer *time.Timer + selectedItemColor lipgloss.AdaptiveColor + unselectedItemColor lipgloss.AdaptiveColor + inactiveItemColor lipgloss.AdaptiveColor + selectionPath string + showIcons bool } func New(startDir string) Model { @@ -52,5 +58,9 @@ func New(startDir string) Model { StatusMessageLifetime: time.Second, showFilesOnly: false, showDirectoriesOnly: false, + selectedItemColor: lipgloss.AdaptiveColor{Light: "212", Dark: "212"}, + unselectedItemColor: lipgloss.AdaptiveColor{Light: "ffffff", Dark: "#000000"}, + inactiveItemColor: lipgloss.AdaptiveColor{Light: "243", Dark: "243"}, + showIcons: true, } } diff --git a/filetree/styles.go b/filetree/styles.go deleted file mode 100644 index 975077d..0000000 --- a/filetree/styles.go +++ /dev/null @@ -1,9 +0,0 @@ -package filetree - -import "github.com/charmbracelet/lipgloss" - -var ( - selectedItemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true) - unselectedItemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ffffff")) - inactiveStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("243")) -) diff --git a/filetree/update.go b/filetree/update.go index 7c788d0..01ecd73 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -133,6 +133,11 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.showDirectoriesOnly = false return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) + case key.Matches(msg, m.keyMap.WriteSelectionPath): + return m, tea.Sequence( + writeSelectionPathCmd(m.selectionPath, m.files[m.Cursor].Name), + tea.Quit, + ) } } diff --git a/filetree/view.go b/filetree/view.go index de3e11c..57b8a86 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -16,29 +16,35 @@ func (m Model) View() string { switch { case m.Disabled: - if file.IsDirectory { - fileList.WriteString(inactiveStyle.Render("🗀 ")) - } else { - fileList.WriteString(inactiveStyle.Render("🗎 ")) + if m.showIcons { + if file.IsDirectory { + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.inactiveItemColor).Render("🗀 ")) + } else { + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.inactiveItemColor).Render("🗎 ")) + } } - fileList.WriteString(inactiveStyle.Render(file.Name) + "\n") + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.inactiveItemColor).Render(file.Name) + "\n") case i == m.Cursor && !m.Disabled: - if file.IsDirectory { - fileList.WriteString(selectedItemStyle.Render("🗀 ")) - } else { - fileList.WriteString(selectedItemStyle.Render("🗎 ")) + if m.showIcons { + if file.IsDirectory { + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.selectedItemColor).Render("🗀 ")) + } else { + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.selectedItemColor).Render("🗎 ")) + } } - fileList.WriteString(selectedItemStyle.Render(file.Name) + "\n") + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.selectedItemColor).Render(file.Name) + "\n") case i != m.Cursor && !m.Disabled: - if file.IsDirectory { - fileList.WriteString(unselectedItemStyle.Render("🗀 ")) - } else { - fileList.WriteString(unselectedItemStyle.Render("🗎 ")) + if m.showIcons { + if file.IsDirectory { + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.unselectedItemColor).Render("🗀 ")) + } else { + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.unselectedItemColor).Render("🗎 ")) + } } - fileList.WriteString(unselectedItemStyle.Render(file.Name) + "\n") + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.unselectedItemColor).Render(file.Name) + "\n") } } diff --git a/internal/theme/theme.go b/internal/theme/theme.go index 2b6c24c..b318598 100644 --- a/internal/theme/theme.go +++ b/internal/theme/theme.go @@ -6,8 +6,6 @@ import "github.com/charmbracelet/lipgloss" type Theme struct { SelectedTreeItemColor lipgloss.AdaptiveColor UnselectedTreeItemColor lipgloss.AdaptiveColor - ActiveBoxBorderColor lipgloss.AdaptiveColor - InactiveBoxBorderColor lipgloss.AdaptiveColor StatusBarSelectedFileForegroundColor lipgloss.AdaptiveColor StatusBarSelectedFileBackgroundColor lipgloss.AdaptiveColor StatusBarBarForegroundColor lipgloss.AdaptiveColor @@ -20,13 +18,10 @@ type Theme struct { TitleForegroundColor lipgloss.AdaptiveColor } -// themeMap represents the mapping of different themes. var themeMap = map[string]Theme{ "default": { - SelectedTreeItemColor: lipgloss.AdaptiveColor{Dark: "63", Light: "63"}, + SelectedTreeItemColor: lipgloss.AdaptiveColor{Dark: "212", Light: "212"}, UnselectedTreeItemColor: lipgloss.AdaptiveColor{Dark: "#ffffff", Light: "#000000"}, - ActiveBoxBorderColor: lipgloss.AdaptiveColor{Dark: "#F25D94", Light: "#F25D94"}, - InactiveBoxBorderColor: lipgloss.AdaptiveColor{Dark: "#ffffff", Light: "#000000"}, StatusBarSelectedFileForegroundColor: lipgloss.AdaptiveColor{Dark: "#ffffff", Light: "#ffffff"}, StatusBarSelectedFileBackgroundColor: lipgloss.AdaptiveColor{Dark: "#F25D94", Light: "#F25D94"}, StatusBarBarForegroundColor: lipgloss.AdaptiveColor{Dark: "#ffffff", Light: "#ffffff"}, @@ -41,8 +36,6 @@ var themeMap = map[string]Theme{ "gruvbox": { SelectedTreeItemColor: lipgloss.AdaptiveColor{Dark: "#d65d0e", Light: "#d65d0e"}, UnselectedTreeItemColor: lipgloss.AdaptiveColor{Dark: "#ffffff", Light: "#000000"}, - ActiveBoxBorderColor: lipgloss.AdaptiveColor{Dark: "#b8bb26", Light: "#b8bb26"}, - InactiveBoxBorderColor: lipgloss.AdaptiveColor{Dark: "#ffffff", Light: "#000000"}, StatusBarSelectedFileForegroundColor: lipgloss.AdaptiveColor{Dark: "#ffffff", Light: "#ffffff"}, StatusBarSelectedFileBackgroundColor: lipgloss.AdaptiveColor{Dark: "#cc241d", Light: "#cc241d"}, StatusBarBarForegroundColor: lipgloss.AdaptiveColor{Dark: "#ffffff", Light: "#ffffff"}, @@ -57,8 +50,6 @@ var themeMap = map[string]Theme{ "nord": { SelectedTreeItemColor: lipgloss.AdaptiveColor{Dark: "#d08770", Light: "#d08770"}, UnselectedTreeItemColor: lipgloss.AdaptiveColor{Dark: "#e5e9f0", Light: "#3b4252"}, - ActiveBoxBorderColor: lipgloss.AdaptiveColor{Dark: "#a3be8c", Light: "#a3be8c"}, - InactiveBoxBorderColor: lipgloss.AdaptiveColor{Dark: "#e5e9f0", Light: "#3b4252"}, StatusBarSelectedFileForegroundColor: lipgloss.AdaptiveColor{Dark: "#e5e9f0", Light: "#e5e9f0"}, StatusBarSelectedFileBackgroundColor: lipgloss.AdaptiveColor{Dark: "#bf616a", Light: "#bf616a"}, StatusBarBarForegroundColor: lipgloss.AdaptiveColor{Dark: "#e5e9f0", Light: "#e5e9f0"}, diff --git a/internal/tui/model.go b/internal/tui/model.go index 24116f9..3cea1df 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -28,6 +28,7 @@ type Config struct { EnableLogging bool PrettyMarkdown bool Theme theme.Theme + ShowIcons bool } type model struct { @@ -47,6 +48,9 @@ type model struct { // New creates a new instance of the UI. func New(cfg Config) model { filetreeModel := filetree.New(cfg.StartDir) + filetreeModel.SetTheme(cfg.Theme.SelectedTreeItemColor, cfg.Theme.UnselectedTreeItemColor) + filetreeModel.SetSelectionPath(cfg.SelectionPath) + filetreeModel.SetShowIcons(cfg.ShowIcons) codeModel := code.New() codeModel.SetSyntaxTheme("pygments") From c702b4f1248eaf1ee0bdd17981c89ffca0d106a0 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Thu, 14 Mar 2024 19:47:38 -0400 Subject: [PATCH 15/31] feat: set syntax theme --- cmd/root.go | 7 +++++++ internal/tui/model.go | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 00194c7..c836129 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -49,6 +49,11 @@ var rootCmd = &cobra.Command{ log.Fatal(err) } + syntaxTheme, err := cmd.Flags().GetString("syntax-theme") + if err != nil { + log.Fatal(err) + } + // If logging is enabled, logs will be output to debug.log. if enableLogging { f, err := tea.LogToFile("debug.log", "debug") @@ -72,6 +77,7 @@ var rootCmd = &cobra.Command{ PrettyMarkdown: prettyMarkdown, Theme: appTheme, ShowIcons: showIcons, + SyntaxTheme: syntaxTheme, } m := tui.New(cfg) @@ -93,6 +99,7 @@ func Execute() { rootCmd.PersistentFlags().Bool("pretty-markdown", true, "Render markdown to look nice") rootCmd.PersistentFlags().String("theme", "default", "Application theme") rootCmd.PersistentFlags().Bool("show-icons", true, "Show icons") + rootCmd.PersistentFlags().String("syntax-theme", "dracula", "Set syntax theme for file output") if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) diff --git a/internal/tui/model.go b/internal/tui/model.go index 3cea1df..224652f 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -29,6 +29,7 @@ type Config struct { PrettyMarkdown bool Theme theme.Theme ShowIcons bool + SyntaxTheme string } type model struct { @@ -53,7 +54,7 @@ func New(cfg Config) model { filetreeModel.SetShowIcons(cfg.ShowIcons) codeModel := code.New() - codeModel.SetSyntaxTheme("pygments") + codeModel.SetSyntaxTheme(cfg.SyntaxTheme) codeModel.SetViewportDisabled(true) imageModel := image.New() From 43c36379448b24d3897edf6b8d23d293d2e231f3 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Thu, 14 Mar 2024 19:57:47 -0400 Subject: [PATCH 16/31] feat: open in editor --- filetree/commands.go | 14 ++++++++++++++ filetree/keys.go | 2 ++ filetree/model.go | 1 + filetree/update.go | 7 +++++++ filetree/view.go | 4 ++++ 5 files changed, 28 insertions(+) diff --git a/filetree/commands.go b/filetree/commands.go index 29083a0..e6e93b5 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -4,6 +4,7 @@ import ( "fmt" "io/fs" "os" + "os/exec" "path/filepath" "time" @@ -16,6 +17,7 @@ type getDirectoryListingMsg []DirectoryItem type errorMsg string type copyToClipboardMsg string type statusMessageTimeoutMsg struct{} +type editorFinishedMsg struct{ err error } // getDirectoryListingCmd updates the directory listing based on the name of the directory provided. func getDirectoryListingCmd(directoryName string, showHidden, directoriesOnly, filesOnly bool) tea.Cmd { @@ -215,3 +217,15 @@ func (m *Model) NewStatusMessage(s string) tea.Cmd { return statusMessageTimeoutMsg{} } } + +func openEditorCmd(file string) tea.Cmd { + editor := os.Getenv("EDITOR") + if editor == "" { + editor = "vim" + } + + c := exec.Command(editor, file) + return tea.ExecProcess(c, func(err error) tea.Msg { + return editorFinishedMsg{err} + }) +} diff --git a/filetree/keys.go b/filetree/keys.go index 9f077e3..48d3520 100644 --- a/filetree/keys.go +++ b/filetree/keys.go @@ -22,6 +22,7 @@ type KeyMap struct { ShowDirectoriesOnly key.Binding ShowFilesOnly key.Binding WriteSelectionPath key.Binding + OpenInEditor key.Binding } func DefaultKeyMap() KeyMap { @@ -45,5 +46,6 @@ func DefaultKeyMap() KeyMap { ShowDirectoriesOnly: key.NewBinding(key.WithKeys("D"), key.WithHelp("D", "show directories only")), ShowFilesOnly: key.NewBinding(key.WithKeys("F"), key.WithHelp("F", "show files only")), WriteSelectionPath: key.NewBinding(key.WithKeys("S"), key.WithHelp("S", "write selection path")), + OpenInEditor: key.NewBinding(key.WithKeys("e"), key.WithHelp("e", "open in editor")), } } diff --git a/filetree/model.go b/filetree/model.go index d73b062..dab9bb6 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -37,6 +37,7 @@ type Model struct { inactiveItemColor lipgloss.AdaptiveColor selectionPath string showIcons bool + err error } func New(startDir string) Model { diff --git a/filetree/update.go b/filetree/update.go index 01ecd73..0facbf6 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -19,6 +19,11 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } switch msg := msg.(type) { + case editorFinishedMsg: + if msg.err != nil { + m.err = msg.err + return m, tea.Quit + } case errorMsg: cmds = append(cmds, m.NewStatusMessage(lipgloss.NewStyle().Foreground(lipgloss.Color("#cc241d")).Bold(true).Render(string(msg)))) case statusMessageTimeoutMsg: @@ -138,6 +143,8 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { writeSelectionPathCmd(m.selectionPath, m.files[m.Cursor].Name), tea.Quit, ) + case key.Matches(msg, m.keyMap.OpenInEditor): + return m, openEditorCmd(m.files[m.Cursor].Name) } } diff --git a/filetree/view.go b/filetree/view.go index 57b8a86..bf565c3 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -9,6 +9,10 @@ import ( func (m Model) View() string { var fileList strings.Builder + if m.err != nil { + return "Error: " + m.err.Error() + "\n" + } + for i, file := range m.files { if i < m.min || i > m.max { continue From 767d605e359620d544c68a8bb6ec6b7f57754173 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Thu, 14 Mar 2024 20:01:46 -0400 Subject: [PATCH 17/31] chore: move to view --- internal/tui/helpers.go | 23 ----------------------- internal/tui/update.go | 2 -- internal/tui/view.go | 21 +++++++++++++++++++++ 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/internal/tui/helpers.go b/internal/tui/helpers.go index 254bdd3..0450806 100644 --- a/internal/tui/helpers.go +++ b/internal/tui/helpers.go @@ -1,8 +1,6 @@ package tui import ( - "fmt" - tea "github.com/charmbracelet/bubbletea" ) @@ -33,27 +31,6 @@ func contains(s []string, str string) bool { return false } -func (m *model) updateStatusbarContent() { - if m.filetree.GetSelectedItem().Name != "" { - statusMessage := m.filetree.GetSelectedItem().CurrentDirectory - - if m.filetree.StatusMessage != "" { - statusMessage = m.filetree.StatusMessage - } - - if m.code.StatusMessage != "" { - statusMessage = m.code.StatusMessage - } - - m.statusbar.SetContent( - m.filetree.GetSelectedItem().Name, - statusMessage, - fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), - fmt.Sprintf("%s %s", "🗀", "FM"), - ) - } -} - func (m *model) openFile() tea.Cmd { selectedFile := m.filetree.GetSelectedItem() diff --git a/internal/tui/update.go b/internal/tui/update.go index 64ee84b..7193421 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -88,7 +88,5 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.help, cmd = m.help.Update(msg) cmds = append(cmds, cmd) - m.updateStatusbarContent() - return m, tea.Batch(cmds...) } diff --git a/internal/tui/view.go b/internal/tui/view.go index f8a5a01..e36e053 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -1,6 +1,8 @@ package tui import ( + "fmt" + "github.com/charmbracelet/lipgloss" ) @@ -22,6 +24,25 @@ func (m model) View() string { rightBox = m.markdown.View() } + if m.filetree.GetSelectedItem().Name != "" { + statusMessage := m.filetree.GetSelectedItem().CurrentDirectory + + if m.filetree.StatusMessage != "" { + statusMessage = m.filetree.StatusMessage + } + + if m.code.StatusMessage != "" { + statusMessage = m.code.StatusMessage + } + + m.statusbar.SetContent( + m.filetree.GetSelectedItem().Name, + statusMessage, + fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), + fmt.Sprintf("%s %s", "🗀", "FM"), + ) + } + return lipgloss.JoinVertical(lipgloss.Top, lipgloss.JoinHorizontal(lipgloss.Top, leftBox, rightBox), m.statusbar.View(), From 7e1b95d0249203a655f75de4aa3327305a2f959d Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Thu, 14 Mar 2024 20:44:27 -0400 Subject: [PATCH 18/31] feat: WIP create file --- filetree/commands.go | 5 +- filetree/keys.go | 2 + filetree/model.go | 2 + filetree/update.go | 210 +++++++++++++++++++++-------------------- internal/tui/init.go | 3 +- internal/tui/keys.go | 20 ++-- internal/tui/model.go | 47 +++++---- internal/tui/update.go | 69 +++++++++----- internal/tui/view.go | 4 + 9 files changed, 206 insertions(+), 156 deletions(-) diff --git a/filetree/commands.go b/filetree/commands.go index e6e93b5..fcff0ad 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -18,6 +18,7 @@ type errorMsg string type copyToClipboardMsg string type statusMessageTimeoutMsg struct{} type editorFinishedMsg struct{ err error } +type createFileMsg struct{} // getDirectoryListingCmd updates the directory listing based on the name of the directory provided. func getDirectoryListingCmd(directoryName string, showHidden, directoriesOnly, filesOnly bool) tea.Cmd { @@ -123,13 +124,13 @@ func createDirectoryCmd(name string) tea.Cmd { } // createFileCmd creates a file based on the name provided. -func createFileCmd(name string) tea.Cmd { +func (m *Model) CreateFileCmd(name string) tea.Cmd { return func() tea.Msg { if err := filesystem.CreateFile(name); err != nil { return errorMsg(err.Error()) } - return nil + return createFileMsg{} } } diff --git a/filetree/keys.go b/filetree/keys.go index 48d3520..86e9e3a 100644 --- a/filetree/keys.go +++ b/filetree/keys.go @@ -23,6 +23,7 @@ type KeyMap struct { ShowFilesOnly key.Binding WriteSelectionPath key.Binding OpenInEditor key.Binding + CreateFile key.Binding } func DefaultKeyMap() KeyMap { @@ -47,5 +48,6 @@ func DefaultKeyMap() KeyMap { ShowFilesOnly: key.NewBinding(key.WithKeys("F"), key.WithHelp("F", "show files only")), WriteSelectionPath: key.NewBinding(key.WithKeys("S"), key.WithHelp("S", "write selection path")), OpenInEditor: key.NewBinding(key.WithKeys("e"), key.WithHelp("e", "open in editor")), + CreateFile: key.NewBinding(key.WithKeys("ctrl+f"), key.WithHelp("ctrl+f", "create new file")), } } diff --git a/filetree/model.go b/filetree/model.go index dab9bb6..99aa95f 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -38,6 +38,7 @@ type Model struct { selectionPath string showIcons bool err error + CreatingNewFile bool } func New(startDir string) Model { @@ -63,5 +64,6 @@ func New(startDir string) Model { unselectedItemColor: lipgloss.AdaptiveColor{Light: "ffffff", Dark: "#000000"}, inactiveItemColor: lipgloss.AdaptiveColor{Light: "243", Dark: "243"}, showIcons: true, + CreatingNewFile: false, } } diff --git a/filetree/update.go b/filetree/update.go index 0facbf6..16289d0 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -14,10 +14,6 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { cmds []tea.Cmd ) - if m.Disabled { - return m, nil - } - switch msg := msg.(type) { case editorFinishedMsg: if msg.err != nil { @@ -30,6 +26,9 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.StatusMessage = "" case copyToClipboardMsg: cmds = append(cmds, m.NewStatusMessage(lipgloss.NewStyle().Bold(true).Render(string(msg)))) + case createFileMsg: + m.SetDisabled(false) + return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) case getDirectoryListingMsg: if msg != nil { m.files = msg @@ -39,112 +38,119 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.max = max(m.max, m.height) } case tea.KeyMsg: - switch { - case key.Matches(msg, m.keyMap.Down): - m.Cursor++ - if m.Cursor >= len(m.files) { - m.Cursor = len(m.files) - 1 - } + if !m.CreatingNewFile { + switch { + case key.Matches(msg, m.keyMap.Down): + m.Cursor++ + if m.Cursor >= len(m.files) { + m.Cursor = len(m.files) - 1 + } - if m.Cursor > m.max { - m.min++ - m.max++ - } - case key.Matches(msg, m.keyMap.Up): - m.Cursor-- - if m.Cursor < 0 { - m.Cursor = 0 - } + if m.Cursor > m.max { + m.min++ + m.max++ + } + case key.Matches(msg, m.keyMap.Up): + m.Cursor-- + if m.Cursor < 0 { + m.Cursor = 0 + } - if m.Cursor < m.min { - m.min-- - m.max-- - } - case key.Matches(msg, m.keyMap.GoToTop): - m.Cursor = 0 - m.min = 0 - m.max = m.height - case key.Matches(msg, m.keyMap.GoToBottom): - m.Cursor = len(m.files) - 1 - m.min = len(m.files) - m.height - m.max = len(m.files) - 1 - case key.Matches(msg, m.keyMap.PageDown): - m.Cursor += m.height - if m.Cursor >= len(m.files) { + if m.Cursor < m.min { + m.min-- + m.max-- + } + case key.Matches(msg, m.keyMap.GoToTop): + m.Cursor = 0 + m.min = 0 + m.max = m.height + case key.Matches(msg, m.keyMap.GoToBottom): m.Cursor = len(m.files) - 1 - } - m.min += m.height - m.max += m.height - - if m.max >= len(m.files) { + m.min = len(m.files) - m.height m.max = len(m.files) - 1 - m.min = m.max - m.height - } - case key.Matches(msg, m.keyMap.PageUp): - m.Cursor -= m.height - if m.Cursor < 0 { - m.Cursor = 0 - } - m.min -= m.height - m.max -= m.height + case key.Matches(msg, m.keyMap.PageDown): + m.Cursor += m.height + if m.Cursor >= len(m.files) { + m.Cursor = len(m.files) - 1 + } + m.min += m.height + m.max += m.height - if m.min < 0 { - m.min = 0 - m.max = m.min + m.height - } - case key.Matches(msg, m.keyMap.GoToHomeDirectory): - return m, getDirectoryListingCmd(filesystem.HomeDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.GoToRootDirectory): - return m, getDirectoryListingCmd(filesystem.RootDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.ToggleHidden): - m.showHidden = !m.showHidden + if m.max >= len(m.files) { + m.max = len(m.files) - 1 + m.min = m.max - m.height + } + case key.Matches(msg, m.keyMap.PageUp): + m.Cursor -= m.height + if m.Cursor < 0 { + m.Cursor = 0 + } + m.min -= m.height + m.max -= m.height - return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.OpenDirectory): - if m.files[m.Cursor].IsDirectory { - return m, getDirectoryListingCmd(m.files[m.Cursor].Path, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - } - case key.Matches(msg, m.keyMap.PreviousDirectory): - return m, getDirectoryListingCmd(filepath.Dir(m.files[m.Cursor].CurrentDirectory), m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.CopyPathToClipboard): - return m, copyToClipboardCmd(m.files[m.Cursor].Name) - case key.Matches(msg, m.keyMap.CopyDirectoryItem): - return m, tea.Sequence( - copyDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), - ) - case key.Matches(msg, m.keyMap.DeleteDirectoryItem): - return m, tea.Sequence( - deleteDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), - ) - case key.Matches(msg, m.keyMap.ZipDirectoryItem): - return m, tea.Sequence( - zipDirectoryCmd(m.files[m.Cursor].Name), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), - ) - case key.Matches(msg, m.keyMap.UnzipDirectoryItem): - return m, tea.Sequence( - unzipDirectoryCmd(m.files[m.Cursor].Name), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), - ) - case key.Matches(msg, m.keyMap.ShowDirectoriesOnly): - m.showDirectoriesOnly = !m.showDirectoriesOnly - m.showFilesOnly = false + if m.min < 0 { + m.min = 0 + m.max = m.min + m.height + } + case key.Matches(msg, m.keyMap.GoToHomeDirectory): + return m, getDirectoryListingCmd(filesystem.HomeDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) + case key.Matches(msg, m.keyMap.GoToRootDirectory): + return m, getDirectoryListingCmd(filesystem.RootDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) + case key.Matches(msg, m.keyMap.ToggleHidden): + m.showHidden = !m.showHidden - return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.ShowFilesOnly): - m.showFilesOnly = !m.showFilesOnly - m.showDirectoriesOnly = false + return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) + case key.Matches(msg, m.keyMap.OpenDirectory): + if m.files[m.Cursor].IsDirectory { + return m, getDirectoryListingCmd(m.files[m.Cursor].Path, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) + } + case key.Matches(msg, m.keyMap.PreviousDirectory): + return m, getDirectoryListingCmd(filepath.Dir(m.files[m.Cursor].CurrentDirectory), m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) + case key.Matches(msg, m.keyMap.CopyPathToClipboard): + return m, copyToClipboardCmd(m.files[m.Cursor].Name) + case key.Matches(msg, m.keyMap.CopyDirectoryItem): + return m, tea.Sequence( + copyDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), + ) + case key.Matches(msg, m.keyMap.DeleteDirectoryItem): + return m, tea.Sequence( + deleteDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), + ) + case key.Matches(msg, m.keyMap.ZipDirectoryItem): + return m, tea.Sequence( + zipDirectoryCmd(m.files[m.Cursor].Name), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), + ) + case key.Matches(msg, m.keyMap.UnzipDirectoryItem): + return m, tea.Sequence( + unzipDirectoryCmd(m.files[m.Cursor].Name), + getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), + ) + case key.Matches(msg, m.keyMap.ShowDirectoriesOnly): + m.showDirectoriesOnly = !m.showDirectoriesOnly + m.showFilesOnly = false - return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.WriteSelectionPath): - return m, tea.Sequence( - writeSelectionPathCmd(m.selectionPath, m.files[m.Cursor].Name), - tea.Quit, - ) - case key.Matches(msg, m.keyMap.OpenInEditor): - return m, openEditorCmd(m.files[m.Cursor].Name) + return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) + case key.Matches(msg, m.keyMap.ShowFilesOnly): + m.showFilesOnly = !m.showFilesOnly + m.showDirectoriesOnly = false + + return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) + case key.Matches(msg, m.keyMap.WriteSelectionPath): + return m, tea.Sequence( + writeSelectionPathCmd(m.selectionPath, m.files[m.Cursor].Name), + tea.Quit, + ) + case key.Matches(msg, m.keyMap.OpenInEditor): + return m, openEditorCmd(m.files[m.Cursor].Name) + case key.Matches(msg, m.keyMap.CreateFile): + m.CreatingNewFile = true + m.SetDisabled(true) + + return m, nil + } } } diff --git a/internal/tui/init.go b/internal/tui/init.go index ff3c50d..601db1e 100644 --- a/internal/tui/init.go +++ b/internal/tui/init.go @@ -1,10 +1,11 @@ package tui import ( + "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" ) // Init intializes the UI. func (m model) Init() tea.Cmd { - return m.filetree.Init() + return tea.Batch(m.filetree.Init(), textinput.Blink) } diff --git a/internal/tui/keys.go b/internal/tui/keys.go index 1cc130c..2d8a64d 100644 --- a/internal/tui/keys.go +++ b/internal/tui/keys.go @@ -3,17 +3,21 @@ package tui import "github.com/charmbracelet/bubbles/key" type keyMap struct { - Quit key.Binding - TogglePane key.Binding - OpenFile key.Binding - ResetState key.Binding + Quit key.Binding + TogglePane key.Binding + OpenFile key.Binding + ResetState key.Binding + ShowTextInput key.Binding + SubmitTextInput key.Binding } func defaultKeyMap() keyMap { return keyMap{ - Quit: key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "quit")), - TogglePane: key.NewBinding(key.WithKeys("tab"), key.WithHelp("tab", "toggle pane")), - OpenFile: key.NewBinding(key.WithKeys("l"), key.WithHelp("l", "open file")), - ResetState: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "reset state")), + Quit: key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "quit")), + TogglePane: key.NewBinding(key.WithKeys("tab"), key.WithHelp("tab", "toggle pane")), + OpenFile: key.NewBinding(key.WithKeys("l"), key.WithHelp("l", "open file")), + ResetState: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "reset state")), + ShowTextInput: key.NewBinding(key.WithKeys("ctrl+f"), key.WithHelp("ctrl+f", "show text input")), + SubmitTextInput: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit text input")), } } diff --git a/internal/tui/model.go b/internal/tui/model.go index 224652f..dc63282 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -1,6 +1,7 @@ package tui import ( + "github.com/charmbracelet/bubbles/textinput" "github.com/mistakenelf/fm/code" "github.com/mistakenelf/fm/filetree" "github.com/mistakenelf/fm/help" @@ -33,17 +34,19 @@ type Config struct { } type model struct { - filetree filetree.Model - help help.Model - code code.Model - image image.Model - markdown markdown.Model - pdf pdf.Model - statusbar statusbar.Model - state sessionState - keyMap keyMap - activePane int - config Config + filetree filetree.Model + help help.Model + code code.Model + image image.Model + markdown markdown.Model + pdf pdf.Model + statusbar statusbar.Model + state sessionState + keyMap keyMap + activePane int + config Config + showTextInput bool + textinput textinput.Model } // New creates a new instance of the UI. @@ -119,15 +122,19 @@ func New(cfg Config) model { ) helpModel.SetViewportDisabled(true) + textInput := textinput.New() + return model{ - filetree: filetreeModel, - help: helpModel, - code: codeModel, - image: imageModel, - markdown: markdownModel, - pdf: pdfModel, - statusbar: statusbarModel, - config: cfg, - keyMap: defaultKeyMap(), + filetree: filetreeModel, + help: helpModel, + code: codeModel, + image: imageModel, + markdown: markdownModel, + pdf: pdfModel, + statusbar: statusbarModel, + config: cfg, + keyMap: defaultKeyMap(), + showTextInput: false, + textinput: textInput, } } diff --git a/internal/tui/update.go b/internal/tui/update.go index 7193421..903ce18 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -35,36 +35,56 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, m.keyMap.Quit): return m, tea.Quit case key.Matches(msg, m.keyMap.OpenFile): - cmds = append(cmds, m.openFile()) + if !m.showTextInput { + cmds = append(cmds, m.openFile()) + } case key.Matches(msg, m.keyMap.ResetState): m.state = idleState + m.showTextInput = false m.disableAllViewports() m.resetViewports() - case key.Matches(msg, m.keyMap.TogglePane): - m.activePane = (m.activePane + 1) % 2 - - if m.activePane == 0 { + m.filetree.SetDisabled(false) + m.textinput.Blur() + m.textinput.Reset() + case key.Matches(msg, m.keyMap.ShowTextInput): + m.showTextInput = true + m.textinput.Focus() + m.disableAllViewports() + case key.Matches(msg, m.keyMap.SubmitTextInput): + if m.filetree.CreatingNewFile { + m.resetViewports() + m.textinput.Blur() + m.textinput.Reset() m.filetree.SetDisabled(false) - m.disableAllViewports() - } else { - m.filetree.SetDisabled(true) + cmds = append(cmds, m.filetree.CreateFileCmd(m.textinput.Value())) + } + case key.Matches(msg, m.keyMap.TogglePane): + if !m.showTextInput { + m.activePane = (m.activePane + 1) % 2 - switch m.state { - case idleState: + if m.activePane == 0 { + m.filetree.SetDisabled(false) m.disableAllViewports() - m.help.SetViewportDisabled(false) - case showCodeState: - m.disableAllViewports() - m.code.SetViewportDisabled(false) - case showImageState: - m.disableAllViewports() - m.image.SetViewportDisabled(false) - case showPdfState: - m.disableAllViewports() - m.pdf.SetViewportDisabled(false) - case showMarkdownState: - m.disableAllViewports() - m.markdown.SetViewportDisabled(false) + } else { + m.filetree.SetDisabled(true) + + switch m.state { + case idleState: + m.disableAllViewports() + m.help.SetViewportDisabled(false) + case showCodeState: + m.disableAllViewports() + m.code.SetViewportDisabled(false) + case showImageState: + m.disableAllViewports() + m.image.SetViewportDisabled(false) + case showPdfState: + m.disableAllViewports() + m.pdf.SetViewportDisabled(false) + case showMarkdownState: + m.disableAllViewports() + m.markdown.SetViewportDisabled(false) + } } } } @@ -88,5 +108,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.help, cmd = m.help.Update(msg) cmds = append(cmds, cmd) + m.textinput, cmd = m.textinput.Update(msg) + cmds = append(cmds, cmd) + return m, tea.Batch(cmds...) } diff --git a/internal/tui/view.go b/internal/tui/view.go index e36e053..d13b181 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -35,6 +35,10 @@ func (m model) View() string { statusMessage = m.code.StatusMessage } + if m.showTextInput { + statusMessage = m.textinput.View() + } + m.statusbar.SetContent( m.filetree.GetSelectedItem().Name, statusMessage, From b53992ddd3e81be2fe0898d97918136fc0dae320 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Fri, 15 Mar 2024 15:05:03 -0400 Subject: [PATCH 19/31] feat: create file and directory --- filetree/commands.go | 10 +- filetree/keys.go | 6 +- filetree/model.go | 19 +- filetree/update.go | 387 +++++++++++++++++++++++++++++----------- filetree/view.go | 38 ++-- internal/tui/helpers.go | 1 + internal/tui/keys.go | 2 +- internal/tui/model.go | 4 +- internal/tui/update.go | 27 ++- internal/tui/view.go | 2 +- 10 files changed, 349 insertions(+), 147 deletions(-) diff --git a/filetree/commands.go b/filetree/commands.go index fcff0ad..2358ebc 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -10,6 +10,7 @@ import ( "github.com/atotto/clipboard" tea "github.com/charmbracelet/bubbletea" + "github.com/mistakenelf/fm/filesystem" ) @@ -19,6 +20,7 @@ type copyToClipboardMsg string type statusMessageTimeoutMsg struct{} type editorFinishedMsg struct{ err error } type createFileMsg struct{} +type createDirectoryMsg struct{} // getDirectoryListingCmd updates the directory listing based on the name of the directory provided. func getDirectoryListingCmd(directoryName string, showHidden, directoriesOnly, filesOnly bool) tea.Cmd { @@ -112,18 +114,18 @@ func deleteDirectoryItemCmd(name string, isDirectory bool) tea.Cmd { } } -// createDirectoryCmd creates a directory based on the name provided. -func createDirectoryCmd(name string) tea.Cmd { +// CreateDirectoryCmd creates a directory based on the name provided. +func (m *Model) CreateDirectoryCmd(name string) tea.Cmd { return func() tea.Msg { if err := filesystem.CreateDirectory(name); err != nil { return errorMsg(err.Error()) } - return nil + return createDirectoryMsg{} } } -// createFileCmd creates a file based on the name provided. +// CreateFileCmd creates a file based on the name provided. func (m *Model) CreateFileCmd(name string) tea.Cmd { return func() tea.Msg { if err := filesystem.CreateFile(name); err != nil { diff --git a/filetree/keys.go b/filetree/keys.go index 86e9e3a..725c68f 100644 --- a/filetree/keys.go +++ b/filetree/keys.go @@ -24,6 +24,7 @@ type KeyMap struct { WriteSelectionPath key.Binding OpenInEditor key.Binding CreateFile key.Binding + CreateDirectory key.Binding } func DefaultKeyMap() KeyMap { @@ -46,8 +47,9 @@ func DefaultKeyMap() KeyMap { UnzipDirectoryItem: key.NewBinding(key.WithKeys("U"), key.WithHelp("U", "unzip directory item")), ShowDirectoriesOnly: key.NewBinding(key.WithKeys("D"), key.WithHelp("D", "show directories only")), ShowFilesOnly: key.NewBinding(key.WithKeys("F"), key.WithHelp("F", "show files only")), - WriteSelectionPath: key.NewBinding(key.WithKeys("S"), key.WithHelp("S", "write selection path")), + WriteSelectionPath: key.NewBinding(key.WithKeys("W"), key.WithHelp("W", "write selection path")), OpenInEditor: key.NewBinding(key.WithKeys("e"), key.WithHelp("e", "open in editor")), - CreateFile: key.NewBinding(key.WithKeys("ctrl+f"), key.WithHelp("ctrl+f", "create new file")), + CreateFile: key.NewBinding(key.WithKeys("N"), key.WithHelp("N", "create new file")), + CreateDirectory: key.NewBinding(key.WithKeys("M"), key.WithHelp("M", "create new directory")), } } diff --git a/filetree/model.go b/filetree/model.go index 99aa95f..ccb1eef 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -4,6 +4,7 @@ import ( "time" "github.com/charmbracelet/lipgloss" + "github.com/mistakenelf/fm/filesystem" ) @@ -12,33 +13,34 @@ type DirectoryItem struct { Details string Path string Extension string - IsDirectory bool CurrentDirectory string + IsDirectory bool } type Model struct { - Cursor int files []DirectoryItem - Disabled bool - keyMap KeyMap + Cursor int min int max int height int width int - startDir string + Disabled bool showHidden bool showDirectoriesOnly bool showFilesOnly bool + showIcons bool + CreatingNewFile bool + CreatingNewDirectory bool + keyMap KeyMap + startDir string StatusMessage string + selectionPath string StatusMessageLifetime time.Duration statusMessageTimer *time.Timer selectedItemColor lipgloss.AdaptiveColor unselectedItemColor lipgloss.AdaptiveColor inactiveItemColor lipgloss.AdaptiveColor - selectionPath string - showIcons bool err error - CreatingNewFile bool } func New(startDir string) Model { @@ -65,5 +67,6 @@ func New(startDir string) Model { inactiveItemColor: lipgloss.AdaptiveColor{Light: "243", Dark: "243"}, showIcons: true, CreatingNewFile: false, + CreatingNewDirectory: false, } } diff --git a/filetree/update.go b/filetree/update.go index 16289d0..0f29087 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -6,6 +6,7 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/mistakenelf/fm/filesystem" ) @@ -14,6 +15,10 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { cmds []tea.Cmd ) + if m.Disabled { + return m, nil + } + switch msg := msg.(type) { case editorFinishedMsg: if msg.err != nil { @@ -21,14 +26,36 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, tea.Quit } case errorMsg: - cmds = append(cmds, m.NewStatusMessage(lipgloss.NewStyle().Foreground(lipgloss.Color("#cc241d")).Bold(true).Render(string(msg)))) + cmds = append(cmds, m.NewStatusMessage( + lipgloss.NewStyle(). + Foreground(lipgloss.Color("#cc241d")). + Bold(true). + Render(string(msg)))) case statusMessageTimeoutMsg: m.StatusMessage = "" case copyToClipboardMsg: - cmds = append(cmds, m.NewStatusMessage(lipgloss.NewStyle().Bold(true).Render(string(msg)))) + cmds = append(cmds, m.NewStatusMessage( + lipgloss.NewStyle(). + Bold(true). + Render(string(msg)))) case createFileMsg: - m.SetDisabled(false) - return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) + m.CreatingNewFile = false + + return m, getDirectoryListingCmd( + filesystem.CurrentDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ) + case createDirectoryMsg: + m.CreatingNewDirectory = false + + return m, getDirectoryListingCmd( + filesystem.CurrentDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ) case getDirectoryListingMsg: if msg != nil { m.files = msg @@ -38,119 +65,271 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.max = max(m.max, m.height) } case tea.KeyMsg: - if !m.CreatingNewFile { - switch { - case key.Matches(msg, m.keyMap.Down): - m.Cursor++ - if m.Cursor >= len(m.files) { - m.Cursor = len(m.files) - 1 - } - - if m.Cursor > m.max { - m.min++ - m.max++ - } - case key.Matches(msg, m.keyMap.Up): - m.Cursor-- - if m.Cursor < 0 { - m.Cursor = 0 - } - - if m.Cursor < m.min { - m.min-- - m.max-- - } - case key.Matches(msg, m.keyMap.GoToTop): + switch { + case key.Matches(msg, m.keyMap.Down): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + m.Cursor++ + + if m.Cursor >= len(m.files) { + m.Cursor = len(m.files) - 1 + } + + if m.Cursor > m.max { + m.min++ + m.max++ + } + case key.Matches(msg, m.keyMap.Up): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + m.Cursor-- + + if m.Cursor < 0 { m.Cursor = 0 - m.min = 0 - m.max = m.height - case key.Matches(msg, m.keyMap.GoToBottom): + } + + if m.Cursor < m.min { + m.min-- + m.max-- + } + case key.Matches(msg, m.keyMap.GoToTop): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + m.Cursor = 0 + m.min = 0 + m.max = m.height + case key.Matches(msg, m.keyMap.GoToBottom): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + m.Cursor = len(m.files) - 1 + m.min = len(m.files) - m.height + m.max = len(m.files) - 1 + case key.Matches(msg, m.keyMap.PageDown): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + m.Cursor += m.height + if m.Cursor >= len(m.files) { m.Cursor = len(m.files) - 1 - m.min = len(m.files) - m.height + } + m.min += m.height + m.max += m.height + + if m.max >= len(m.files) { m.max = len(m.files) - 1 - case key.Matches(msg, m.keyMap.PageDown): - m.Cursor += m.height - if m.Cursor >= len(m.files) { - m.Cursor = len(m.files) - 1 - } - m.min += m.height - m.max += m.height - - if m.max >= len(m.files) { - m.max = len(m.files) - 1 - m.min = m.max - m.height - } - case key.Matches(msg, m.keyMap.PageUp): - m.Cursor -= m.height - if m.Cursor < 0 { - m.Cursor = 0 - } - m.min -= m.height - m.max -= m.height - - if m.min < 0 { - m.min = 0 - m.max = m.min + m.height - } - case key.Matches(msg, m.keyMap.GoToHomeDirectory): - return m, getDirectoryListingCmd(filesystem.HomeDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.GoToRootDirectory): - return m, getDirectoryListingCmd(filesystem.RootDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.ToggleHidden): - m.showHidden = !m.showHidden - - return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.OpenDirectory): - if m.files[m.Cursor].IsDirectory { - return m, getDirectoryListingCmd(m.files[m.Cursor].Path, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - } - case key.Matches(msg, m.keyMap.PreviousDirectory): - return m, getDirectoryListingCmd(filepath.Dir(m.files[m.Cursor].CurrentDirectory), m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.CopyPathToClipboard): - return m, copyToClipboardCmd(m.files[m.Cursor].Name) - case key.Matches(msg, m.keyMap.CopyDirectoryItem): - return m, tea.Sequence( - copyDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), - ) - case key.Matches(msg, m.keyMap.DeleteDirectoryItem): - return m, tea.Sequence( - deleteDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), - ) - case key.Matches(msg, m.keyMap.ZipDirectoryItem): - return m, tea.Sequence( - zipDirectoryCmd(m.files[m.Cursor].Name), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), - ) - case key.Matches(msg, m.keyMap.UnzipDirectoryItem): - return m, tea.Sequence( - unzipDirectoryCmd(m.files[m.Cursor].Name), - getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly), + m.min = m.max - m.height + } + case key.Matches(msg, m.keyMap.PageUp): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + m.Cursor -= m.height + + if m.Cursor < 0 { + m.Cursor = 0 + } + + m.min -= m.height + m.max -= m.height + + if m.min < 0 { + m.min = 0 + m.max = m.min + m.height + } + case key.Matches(msg, m.keyMap.GoToHomeDirectory): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + return m, getDirectoryListingCmd( + filesystem.HomeDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ) + case key.Matches(msg, m.keyMap.GoToRootDirectory): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + return m, getDirectoryListingCmd( + filesystem.RootDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ) + case key.Matches(msg, m.keyMap.ToggleHidden): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + m.showHidden = !m.showHidden + + return m, getDirectoryListingCmd( + filesystem.CurrentDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ) + case key.Matches(msg, m.keyMap.OpenDirectory): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + if m.files[m.Cursor].IsDirectory { + return m, getDirectoryListingCmd( + m.files[m.Cursor].Path, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, ) - case key.Matches(msg, m.keyMap.ShowDirectoriesOnly): - m.showDirectoriesOnly = !m.showDirectoriesOnly - m.showFilesOnly = false + } + case key.Matches(msg, m.keyMap.PreviousDirectory): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } - return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.ShowFilesOnly): - m.showFilesOnly = !m.showFilesOnly - m.showDirectoriesOnly = false + return m, getDirectoryListingCmd( + filepath.Dir(m.files[m.Cursor].CurrentDirectory), + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ) + case key.Matches(msg, m.keyMap.CopyPathToClipboard): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } - return m, getDirectoryListingCmd(filesystem.CurrentDirectory, m.showHidden, m.showDirectoriesOnly, m.showFilesOnly) - case key.Matches(msg, m.keyMap.WriteSelectionPath): + return m, copyToClipboardCmd(m.files[m.Cursor].Name) + case key.Matches(msg, m.keyMap.CopyDirectoryItem): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + return m, tea.Sequence( + copyDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), + getDirectoryListingCmd( + filesystem.CurrentDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ), + ) + case key.Matches(msg, m.keyMap.DeleteDirectoryItem): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + return m, tea.Sequence( + deleteDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), + getDirectoryListingCmd( + filesystem.CurrentDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ), + ) + case key.Matches(msg, m.keyMap.ZipDirectoryItem): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + return m, tea.Sequence( + zipDirectoryCmd(m.files[m.Cursor].Name), + getDirectoryListingCmd( + filesystem.CurrentDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ), + ) + case key.Matches(msg, m.keyMap.UnzipDirectoryItem): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + return m, tea.Sequence( + unzipDirectoryCmd(m.files[m.Cursor].Name), + getDirectoryListingCmd( + filesystem.CurrentDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ), + ) + case key.Matches(msg, m.keyMap.ShowDirectoriesOnly): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + m.showDirectoriesOnly = !m.showDirectoriesOnly + m.showFilesOnly = false + + return m, getDirectoryListingCmd( + filesystem.CurrentDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ) + case key.Matches(msg, m.keyMap.ShowFilesOnly): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + m.showFilesOnly = !m.showFilesOnly + m.showDirectoriesOnly = false + + return m, getDirectoryListingCmd( + filesystem.CurrentDirectory, + m.showHidden, + m.showDirectoriesOnly, + m.showFilesOnly, + ) + case key.Matches(msg, m.keyMap.WriteSelectionPath): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + if m.selectionPath != "" { return m, tea.Sequence( writeSelectionPathCmd(m.selectionPath, m.files[m.Cursor].Name), tea.Quit, ) - case key.Matches(msg, m.keyMap.OpenInEditor): - return m, openEditorCmd(m.files[m.Cursor].Name) - case key.Matches(msg, m.keyMap.CreateFile): - m.CreatingNewFile = true - m.SetDisabled(true) + } + case key.Matches(msg, m.keyMap.OpenInEditor): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + return m, openEditorCmd(m.files[m.Cursor].Name) + case key.Matches(msg, m.keyMap.CreateFile): + if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil } + + m.CreatingNewFile = true + m.CreatingNewDirectory = false + + return m, nil + case key.Matches(msg, m.keyMap.CreateDirectory): + if m.CreatingNewFile || m.CreatingNewDirectory { + return m, nil + } + + m.CreatingNewDirectory = true + m.CreatingNewFile = false + + return m, nil } } diff --git a/filetree/view.go b/filetree/view.go index bf565c3..ffb869f 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -20,35 +20,37 @@ func (m Model) View() string { switch { case m.Disabled: - if m.showIcons { - if file.IsDirectory { - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.inactiveItemColor).Render("🗀 ")) - } else { - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.inactiveItemColor).Render("🗎 ")) - } + fallthrough + case i == m.Cursor && !m.Disabled: + iconColor := m.inactiveItemColor + textColor := m.inactiveItemColor + if i == m.Cursor && !m.Disabled { + iconColor = m.selectedItemColor + textColor = m.selectedItemColor } - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.inactiveItemColor).Render(file.Name) + "\n") - case i == m.Cursor && !m.Disabled: if m.showIcons { - if file.IsDirectory { - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.selectedItemColor).Render("🗀 ")) - } else { - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.selectedItemColor).Render("🗎 ")) + icon := "🗀 " + if !file.IsDirectory { + icon = "🗎 " } + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(iconColor).Render(icon)) } - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.selectedItemColor).Render(file.Name) + "\n") + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(textColor).Render(file.Name) + "\n") case i != m.Cursor && !m.Disabled: + iconColor := m.unselectedItemColor + textColor := m.unselectedItemColor + if m.showIcons { - if file.IsDirectory { - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.unselectedItemColor).Render("🗀 ")) - } else { - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.unselectedItemColor).Render("🗎 ")) + icon := "🗀 " + if !file.IsDirectory { + icon = "🗎 " } + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(iconColor).Render(icon)) } - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(m.unselectedItemColor).Render(file.Name) + "\n") + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(textColor).Render(file.Name) + "\n") } } diff --git a/internal/tui/helpers.go b/internal/tui/helpers.go index 0450806..a2005d9 100644 --- a/internal/tui/helpers.go +++ b/internal/tui/helpers.go @@ -51,6 +51,7 @@ func (m *model) openFile() tea.Cmd { return m.pdf.SetFileName(selectedFile.Name) case contains(forbiddenExtensions, selectedFile.Extension): + //TODO: Display an error status message in the statusbar. return nil default: m.state = showCodeState diff --git a/internal/tui/keys.go b/internal/tui/keys.go index 2d8a64d..3ac5562 100644 --- a/internal/tui/keys.go +++ b/internal/tui/keys.go @@ -17,7 +17,7 @@ func defaultKeyMap() keyMap { TogglePane: key.NewBinding(key.WithKeys("tab"), key.WithHelp("tab", "toggle pane")), OpenFile: key.NewBinding(key.WithKeys("l"), key.WithHelp("l", "open file")), ResetState: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "reset state")), - ShowTextInput: key.NewBinding(key.WithKeys("ctrl+f"), key.WithHelp("ctrl+f", "show text input")), + ShowTextInput: key.NewBinding(key.WithKeys("N", "M"), key.WithHelp("N, M", "show text input")), SubmitTextInput: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit text input")), } } diff --git a/internal/tui/model.go b/internal/tui/model.go index dc63282..8e3b255 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -26,11 +26,11 @@ const ( type Config struct { StartDir string SelectionPath string + SyntaxTheme string EnableLogging bool PrettyMarkdown bool - Theme theme.Theme ShowIcons bool - SyntaxTheme string + Theme theme.Theme } type model struct { diff --git a/internal/tui/update.go b/internal/tui/update.go index 903ce18..8f7474b 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -50,14 +50,25 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.showTextInput = true m.textinput.Focus() m.disableAllViewports() + + m.textinput, cmd = m.textinput.Update(msg) + cmds = append(cmds, cmd) + + m.textinput.Reset() case key.Matches(msg, m.keyMap.SubmitTextInput): if m.filetree.CreatingNewFile { - m.resetViewports() - m.textinput.Blur() - m.textinput.Reset() - m.filetree.SetDisabled(false) cmds = append(cmds, m.filetree.CreateFileCmd(m.textinput.Value())) } + + if m.filetree.CreatingNewDirectory { + cmds = append(cmds, m.filetree.CreateDirectoryCmd(m.textinput.Value())) + } + + m.resetViewports() + m.textinput.Blur() + m.textinput.Reset() + m.showTextInput = false + m.activePane = 0 case key.Matches(msg, m.keyMap.TogglePane): if !m.showTextInput { m.activePane = (m.activePane + 1) % 2 @@ -90,6 +101,11 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } + if m.filetree.CreatingNewDirectory || m.filetree.CreatingNewFile { + m.textinput, cmd = m.textinput.Update(msg) + cmds = append(cmds, cmd) + } + m.filetree, cmd = m.filetree.Update(msg) cmds = append(cmds, cmd) @@ -108,8 +124,5 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.help, cmd = m.help.Update(msg) cmds = append(cmds, cmd) - m.textinput, cmd = m.textinput.Update(msg) - cmds = append(cmds, cmd) - return m, tea.Batch(cmds...) } diff --git a/internal/tui/view.go b/internal/tui/view.go index d13b181..c46e069 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -42,7 +42,7 @@ func (m model) View() string { m.statusbar.SetContent( m.filetree.GetSelectedItem().Name, statusMessage, - fmt.Sprintf("%d/%d", m.filetree.Cursor, m.filetree.GetTotalItems()), + fmt.Sprintf("%d/%d", m.filetree.Cursor+1, m.filetree.GetTotalItems()), fmt.Sprintf("%s %s", "🗀", "FM"), ) } From 32bf683bbdd0827d959183fc368fc20bbc4d9401 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Fri, 15 Mar 2024 20:27:56 -0400 Subject: [PATCH 20/31] fix: text input update too soon --- filetree/update.go | 2 ++ internal/tui/update.go | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/filetree/update.go b/filetree/update.go index 0f29087..a0f19a6 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -40,6 +40,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { Render(string(msg)))) case createFileMsg: m.CreatingNewFile = false + m.CreatingNewDirectory = false return m, getDirectoryListingCmd( filesystem.CurrentDirectory, @@ -49,6 +50,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { ) case createDirectoryMsg: m.CreatingNewDirectory = false + m.CreatingNewFile = false return m, getDirectoryListingCmd( filesystem.CurrentDirectory, diff --git a/internal/tui/update.go b/internal/tui/update.go index 8f7474b..7afb1fd 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -28,8 +28,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.pdf.SetSize(halfSize, bubbleHeight) m.statusbar.SetSize(msg.Width) - m.filetree, cmd = m.filetree.Update(msg) - cmds = append(cmds, cmd) + return m, tea.Batch(cmds...) case tea.KeyMsg: switch { case key.Matches(msg, m.keyMap.Quit): @@ -45,16 +44,24 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.resetViewports() m.filetree.SetDisabled(false) m.textinput.Blur() - m.textinput.Reset() - case key.Matches(msg, m.keyMap.ShowTextInput): - m.showTextInput = true - m.textinput.Focus() - m.disableAllViewports() + m.filetree.CreatingNewDirectory = false + m.filetree.CreatingNewFile = false m.textinput, cmd = m.textinput.Update(msg) cmds = append(cmds, cmd) m.textinput.Reset() + case key.Matches(msg, m.keyMap.ShowTextInput): + if m.activePane == 0 { + m.showTextInput = true + m.textinput.Focus() + m.disableAllViewports() + + m.textinput, cmd = m.textinput.Update(msg) + cmds = append(cmds, cmd) + + m.textinput.Reset() + } case key.Matches(msg, m.keyMap.SubmitTextInput): if m.filetree.CreatingNewFile { cmds = append(cmds, m.filetree.CreateFileCmd(m.textinput.Value())) From dd6434ff8ca63245517af5553962c34ae3a63f35 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Fri, 15 Mar 2024 20:28:06 -0400 Subject: [PATCH 21/31] feat: update keybinding help --- internal/tui/model.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/internal/tui/model.go b/internal/tui/model.go index 8e3b255..9a01180 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -98,9 +98,8 @@ func New(cfg Config) model { {Key: "ctrl+c, q", Description: "Exit FM"}, {Key: "j/up", Description: "Move up"}, {Key: "k/down", Description: "Move down"}, - {Key: "h", Description: "Paginate left in current directory"}, - {Key: "l", Description: "Paginate right in current directory"}, - {Key: "space", Description: "Read file or enter directory"}, + {Key: "h", Description: "Go to previous directory"}, + {Key: "l", Description: "Open file or directory"}, {Key: "G", Description: "Jump to bottom"}, {Key: "g", Description: "Jump to top"}, {Key: "~", Description: "Go to home directory"}, @@ -108,16 +107,21 @@ func New(cfg Config) model { {Key: "y", Description: "Copy file path to clipboard"}, {Key: "z", Description: "Zip currently selected tree item"}, {Key: "u", Description: "Unzip currently selected tree item"}, - {Key: "n", Description: "Create new file"}, - {Key: "N", Description: "Create new directory"}, - {Key: "x", Description: "Delete currently selected tree item"}, + {Key: "N", Description: "Create new file"}, + {Key: "M", Description: "Create new directory"}, + {Key: "X", Description: "Delete currently selected tree item"}, {Key: "m", Description: "Move currently selected tree item"}, - {Key: "enter", Description: "Process command"}, - {Key: "e", Description: "Edit currently selected tree item"}, - {Key: "c", Description: "Copy currently selected tree item"}, - {Key: "esc", Description: "Reset input field"}, - {Key: "R", Description: "Go to root directory"}, - {Key: "tab", Description: "Toggle between boxes"}, + {Key: "enter", Description: "Submit text input"}, + {Key: "e", Description: "Open file in $EDITOR"}, + {Key: "C", Description: "Copy currently selected tree item"}, + {Key: "esc", Description: "Reset state and show help"}, + {Key: "/", Description: "Go to root directory"}, + {Key: "tab", Description: "Toggle between panes"}, + {Key: "K", Description: "Page up in filetree"}, + {Key: "J", Description: "Page down in filetree"}, + {Key: "D", Description: "Only show directories in filetree"}, + {Key: "F", Description: "Only show files in filetree"}, + {Key: "W", Description: "Write selection path to file if its set"}, }, ) helpModel.SetViewportDisabled(true) From de082f78079f8d4c5e57540f753491c1728e51c0 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Fri, 15 Mar 2024 21:13:58 -0400 Subject: [PATCH 22/31] feat: file icons --- filetree/commands.go | 1 + filetree/model.go | 2 + filetree/view.go | 17 +- icons/directories.go | 62 ----- icons/extensions.go | 537 ---------------------------------------- icons/filenames.go | 415 ------------------------------- icons/glyphs.go | 362 --------------------------- icons/icons | 332 +++++++++++++++++++++++++ icons/icons.go | 205 ++++++++++----- icons/sub_extensions.go | 48 ---- internal/tui/model.go | 5 + 11 files changed, 495 insertions(+), 1491 deletions(-) delete mode 100644 icons/directories.go delete mode 100644 icons/extensions.go delete mode 100644 icons/filenames.go delete mode 100644 icons/glyphs.go create mode 100644 icons/icons delete mode 100644 icons/sub_extensions.go diff --git a/filetree/commands.go b/filetree/commands.go index 2358ebc..604707a 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -90,6 +90,7 @@ func getDirectoryListingCmd(directoryName string, showHidden, directoriesOnly, f Extension: filepath.Ext(fileInfo.Name()), IsDirectory: fileInfo.IsDir(), CurrentDirectory: workingDirectory, + FileInfo: fileInfo, }) } diff --git a/filetree/model.go b/filetree/model.go index ccb1eef..b759f04 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -1,6 +1,7 @@ package filetree import ( + "os" "time" "github.com/charmbracelet/lipgloss" @@ -15,6 +16,7 @@ type DirectoryItem struct { Extension string CurrentDirectory string IsDirectory bool + FileInfo os.FileInfo } type Model struct { diff --git a/filetree/view.go b/filetree/view.go index ffb869f..d7a6828 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -4,6 +4,8 @@ import ( "strings" "github.com/charmbracelet/lipgloss" + + "github.com/mistakenelf/fm/icons" ) func (m Model) View() string { @@ -24,17 +26,15 @@ func (m Model) View() string { case i == m.Cursor && !m.Disabled: iconColor := m.inactiveItemColor textColor := m.inactiveItemColor + if i == m.Cursor && !m.Disabled { iconColor = m.selectedItemColor textColor = m.selectedItemColor } if m.showIcons { - icon := "🗀 " - if !file.IsDirectory { - icon = "🗎 " - } - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(iconColor).Render(icon)) + icon := icons.Icons.GetIcon(file.FileInfo) + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(iconColor).Render(icon) + " ") } fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(textColor).Render(file.Name) + "\n") @@ -43,11 +43,8 @@ func (m Model) View() string { textColor := m.unselectedItemColor if m.showIcons { - icon := "🗀 " - if !file.IsDirectory { - icon = "🗎 " - } - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(iconColor).Render(icon)) + icon := icons.Icons.GetIcon(file.FileInfo) + fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(iconColor).Render(icon) + " ") } fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(textColor).Render(file.Name) + "\n") diff --git a/icons/directories.go b/icons/directories.go deleted file mode 100644 index 759b883..0000000 --- a/icons/directories.go +++ /dev/null @@ -1,62 +0,0 @@ -package icons - -// IconDir is used to get the icon based on the type of directory. -var IconDir = map[string]*IconInfo{ - "config": IconSet["dir-config"], - ".config": IconSet["dir-config"], - "configs": IconSet["dir-config"], - "configuration": IconSet["dir-config"], - "configurations": IconSet["dir-config"], - "settings": IconSet["dir-config"], - ".settings": IconSet["dir-config"], - "META-INF": IconSet["dir-config"], - "controller": IconSet["dir-controller"], - "controllers": IconSet["dir-controller"], - "service": IconSet["dir-controller"], - "services": IconSet["dir-controller"], - "provider": IconSet["dir-controller"], - "providers": IconSet["dir-controller"], - ".git": IconSet["dir-git"], - "githooks": IconSet["dir-git"], - ".githooks": IconSet["dir-git"], - "submodules": IconSet["dir-git"], - ".submodules": IconSet["dir-git"], - ".github": IconSet["dir-github"], - "node_modules": IconSet["dir-npm"], - "include": IconSet["dir-include"], - "includes": IconSet["dir-include"], - "_includes": IconSet["dir-include"], - "import": IconSet["dir-import"], - "imports": IconSet["dir-import"], - "imported": IconSet["dir-import"], - "uploads": IconSet["dir-upload"], - "upload": IconSet["dir-upload"], - "downloads": IconSet["dir-download"], - "download": IconSet["dir-download"], - "auth": IconSet["dir-secure"], - "authentication": IconSet["dir-secure"], - "secure": IconSet["dir-secure"], - "security": IconSet["dir-secure"], - "cert": IconSet["dir-secure"], - "certs": IconSet["dir-secure"], - "certificate": IconSet["dir-secure"], - "certificates": IconSet["dir-secure"], - "ssl": IconSet["dir-secure"], - "images": IconSet["dir-images"], - "image": IconSet["dir-images"], - "pics": IconSet["dir-images"], - "pic": IconSet["dir-images"], - "pictures": IconSet["dir-images"], - "picture": IconSet["dir-images"], - "img": IconSet["dir-images"], - "icons": IconSet["dir-images"], - "icon": IconSet["dir-images"], - "ico": IconSet["dir-images"], - "screenshot": IconSet["dir-images"], - "screenshots": IconSet["dir-images"], - ".env": IconSet["dir-environment"], - ".environment": IconSet["dir-environment"], - "env": IconSet["dir-environment"], - "environment": IconSet["dir-environment"], - "environments": IconSet["dir-environment"], -} diff --git a/icons/extensions.go b/icons/extensions.go deleted file mode 100644 index c4cd5df..0000000 --- a/icons/extensions.go +++ /dev/null @@ -1,537 +0,0 @@ -package icons - -// IconExt is used to represent an icon with a specific extension. -var IconExt = map[string]*IconInfo{ - "htm": IconSet["html"], - "html": IconSet["html"], - "xhtml": IconSet["html"], - "html_vm": IconSet["html"], - "asp": IconSet["html"], - "jade": IconSet["pug"], - "pug": IconSet["pug"], - "md": IconSet["markdown"], - "markdown": IconSet["markdown"], - "rst": IconSet["markdown"], - "blink": IconSet["blink"], - "css": IconSet["css"], - "scss": IconSet["sass"], - "sass": IconSet["sass"], - "less": IconSet["less"], - "json": IconSet["json"], - "tsbuildinfo": IconSet["json"], - "json5": IconSet["json"], - "jsonl": IconSet["json"], - "ndjson": IconSet["json"], - "jinja": IconSet["jinja"], - "jinja2": IconSet["jinja"], - "j2": IconSet["jinja"], - "jinja-html": IconSet["jinja"], - "sublime-project": IconSet["sublime"], - "sublime-workspace": IconSet["sublime"], - "yaml": IconSet["yaml"], - "yaml-tmlanguage": IconSet["yaml"], - "yml": IconSet["yaml"], - "xml": IconSet["xml"], - "plist": IconSet["xml"], - "xsd": IconSet["xml"], - "dtd": IconSet["xml"], - "xsl": IconSet["xml"], - "xslt": IconSet["xml"], - "resx": IconSet["xml"], - "iml": IconSet["xml"], - "xquery": IconSet["xml"], - "tmLanguage": IconSet["xml"], - "manifest": IconSet["xml"], - "project": IconSet["xml"], - "png": IconSet["image"], - "jpeg": IconSet["image"], - "jpg": IconSet["image"], - "gif": IconSet["image"], - "ico": IconSet["image"], - "tif": IconSet["image"], - "tiff": IconSet["image"], - "psd": IconSet["image"], - "psb": IconSet["image"], - "ami": IconSet["image"], - "apx": IconSet["image"], - "bmp": IconSet["image"], - "bpg": IconSet["image"], - "brk": IconSet["image"], - "cur": IconSet["image"], - "dds": IconSet["image"], - "dng": IconSet["image"], - "exr": IconSet["image"], - "fpx": IconSet["image"], - "gbr": IconSet["image"], - "img": IconSet["image"], - "jbig2": IconSet["image"], - "jb2": IconSet["image"], - "jng": IconSet["image"], - "jxr": IconSet["image"], - "pbm": IconSet["image"], - "pgf": IconSet["image"], - "pic": IconSet["image"], - "raw": IconSet["image"], - "webp": IconSet["image"], - "eps": IconSet["image"], - "afphoto": IconSet["image"], - "ase": IconSet["image"], - "aseprite": IconSet["image"], - "clip": IconSet["image"], - "cpt": IconSet["image"], - "heif": IconSet["image"], - "heic": IconSet["image"], - "kra": IconSet["image"], - "mdp": IconSet["image"], - "ora": IconSet["image"], - "pdn": IconSet["image"], - "reb": IconSet["image"], - "sai": IconSet["image"], - "tga": IconSet["image"], - "xcf": IconSet["image"], - "js": IconSet["javascript"], - "esx": IconSet["javascript"], - "mj": IconSet["javascript"], - "jsx": IconSet["react"], - "tsx": IconSet["react_ts"], - "ini": IconSet["settings"], - "dlc": IconSet["settings"], - "dll": IconSet["settings"], - "config": IconSet["settings"], - "conf": IconSet["settings"], - "properties": IconSet["settings"], - "prop": IconSet["settings"], - "settings": IconSet["settings"], - "option": IconSet["settings"], - "props": IconSet["settings"], - "toml": IconSet["settings"], - "prefs": IconSet["settings"], - "dotsettings": IconSet["settings"], - "cfg": IconSet["settings"], - "ts": IconSet["typescript"], - "marko": IconSet["markojs"], - "pdf": IconSet["pdf"], - "xlsx": IconSet["table"], - "xls": IconSet["table"], - "csv": IconSet["table"], - "tsv": IconSet["table"], - "vscodeignore": IconSet["vscode"], - "vsixmanifest": IconSet["vscode"], - "vsix": IconSet["vscode"], - "code-workplace": IconSet["vscode"], - "csproj": IconSet["visualstudio"], - "ruleset": IconSet["visualstudio"], - "sln": IconSet["visualstudio"], - "suo": IconSet["visualstudio"], - "vb": IconSet["visualstudio"], - "vbs": IconSet["visualstudio"], - "vcxitems": IconSet["visualstudio"], - "vcxproj": IconSet["visualstudio"], - "pdb": IconSet["database"], - "sql": IconSet["mysql"], - "pks": IconSet["database"], - "pkb": IconSet["database"], - "accdb": IconSet["database"], - "mdb": IconSet["database"], - "sqlite": IconSet["sqlite"], - "sqlite3": IconSet["sqlite"], - "pgsql": IconSet["postgresql"], - "postgres": IconSet["postgresql"], - "psql": IconSet["postgresql"], - "cs": IconSet["csharp"], - "csx": IconSet["csharp"], - "qs": IconSet["qsharp"], - "zip": IconSet["zip"], - "tar": IconSet["zip"], - "gz": IconSet["zip"], - "xz": IconSet["zip"], - "br": IconSet["zip"], - "bzip2": IconSet["zip"], - "gzip": IconSet["zip"], - "brotli": IconSet["zip"], - "7z": IconSet["zip"], - "rar": IconSet["zip"], - "tgz": IconSet["zip"], - "vala": IconSet["vala"], - "zig": IconSet["zig"], - "exe": IconSet["exe"], - "msi": IconSet["exe"], - "java": IconSet["java"], - "jar": IconSet["java"], - "jsp": IconSet["java"], - "c": IconSet["c"], - "m": IconSet["c"], - "i": IconSet["c"], - "mi": IconSet["c"], - "h": IconSet["h"], - "cc": IconSet["cpp"], - "cpp": IconSet["cpp"], - "cxx": IconSet["cpp"], - "c++": IconSet["cpp"], - "cp": IconSet["cpp"], - "mm": IconSet["cpp"], - "mii": IconSet["cpp"], - "ii": IconSet["cpp"], - "hh": IconSet["hpp"], - "hpp": IconSet["hpp"], - "hxx": IconSet["hpp"], - "h++": IconSet["hpp"], - "hp": IconSet["hpp"], - "tcc": IconSet["hpp"], - "inl": IconSet["hpp"], - "go": IconSet["go"], - "py": IconSet["python"], - "pyc": IconSet["python-misc"], - "whl": IconSet["python-misc"], - "url": IconSet["url"], - "sh": IconSet["console"], - "ksh": IconSet["console"], - "csh": IconSet["console"], - "tcsh": IconSet["console"], - "zsh": IconSet["console"], - "bash": IconSet["console"], - "bat": IconSet["console"], - "cmd": IconSet["console"], - "awk": IconSet["console"], - "fish": IconSet["console"], - "ps1": IconSet["powershell"], - "psm1": IconSet["powershell"], - "psd1": IconSet["powershell"], - "ps1xml": IconSet["powershell"], - "psc1": IconSet["powershell"], - "pssc": IconSet["powershell"], - "gradle": IconSet["gradle"], - "doc": IconSet["word"], - "docx": IconSet["word"], - "rtf": IconSet["word"], - "cer": IconSet["certificate"], - "cert": IconSet["certificate"], - "crt": IconSet["certificate"], - "pub": IconSet["key"], - "key": IconSet["key"], - "pem": IconSet["key"], - "asc": IconSet["key"], - "gpg": IconSet["key"], - "woff": IconSet["font"], - "woff2": IconSet["font"], - "ttf": IconSet["font"], - "eot": IconSet["font"], - "suit": IconSet["font"], - "otf": IconSet["font"], - "bmap": IconSet["font"], - "fnt": IconSet["font"], - "odttf": IconSet["font"], - "ttc": IconSet["font"], - "font": IconSet["font"], - "fonts": IconSet["font"], - "sui": IconSet["font"], - "ntf": IconSet["font"], - "mrf": IconSet["font"], - "lib": IconSet["lib"], - "bib": IconSet["lib"], - "rb": IconSet["ruby"], - "erb": IconSet["ruby"], - "fs": IconSet["fsharp"], - "fsx": IconSet["fsharp"], - "fsi": IconSet["fsharp"], - "fsproj": IconSet["fsharp"], - "swift": IconSet["swift"], - "ino": IconSet["arduino"], - "dockerignore": IconSet["docker"], - "dockerfile": IconSet["docker"], - "tex": IconSet["tex"], - "sty": IconSet["tex"], - "dtx": IconSet["tex"], - "ltx": IconSet["tex"], - "pptx": IconSet["powerpoint"], - "ppt": IconSet["powerpoint"], - "pptm": IconSet["powerpoint"], - "potx": IconSet["powerpoint"], - "potm": IconSet["powerpoint"], - "ppsx": IconSet["powerpoint"], - "ppsm": IconSet["powerpoint"], - "pps": IconSet["powerpoint"], - "ppam": IconSet["powerpoint"], - "ppa": IconSet["powerpoint"], - "webm": IconSet["video"], - "mkv": IconSet["video"], - "flv": IconSet["video"], - "vob": IconSet["video"], - "ogv": IconSet["video"], - "ogg": IconSet["video"], - "gifv": IconSet["video"], - "avi": IconSet["video"], - "mov": IconSet["video"], - "qt": IconSet["video"], - "wmv": IconSet["video"], - "yuv": IconSet["video"], - "rm": IconSet["video"], - "rmvb": IconSet["video"], - "mp4": IconSet["video"], - "m4v": IconSet["video"], - "mpg": IconSet["video"], - "mp2": IconSet["video"], - "mpeg": IconSet["video"], - "mpe": IconSet["video"], - "mpv": IconSet["video"], - "m2v": IconSet["video"], - "vdi": IconSet["virtual"], - "vbox": IconSet["virtual"], - "vbox-prev": IconSet["virtual"], - "ics": IconSet["email"], - "mp3": IconSet["audio"], - "flac": IconSet["audio"], - "m4a": IconSet["audio"], - "wma": IconSet["audio"], - "aiff": IconSet["audio"], - "coffee": IconSet["coffee"], - "cson": IconSet["coffee"], - "iced": IconSet["coffee"], - "txt": IconSet["document"], - "graphql": IconSet["graphql"], - "gql": IconSet["graphql"], - "rs": IconSet["rust"], - "raml": IconSet["raml"], - "xaml": IconSet["xaml"], - "hs": IconSet["haskell"], - "kt": IconSet["kotlin"], - "kts": IconSet["kotlin"], - "patch": IconSet["git"], - "lua": IconSet["lua"], - "clj": IconSet["clojure"], - "cljs": IconSet["clojure"], - "cljc": IconSet["clojure"], - "groovy": IconSet["groovy"], - "r": IconSet["r"], - "rmd": IconSet["r"], - "dart": IconSet["dart"], - "as": IconSet["actionscript"], - "mxml": IconSet["mxml"], - "ahk": IconSet["autohotkey"], - "swf": IconSet["flash"], - "swc": IconSet["swc"], - "cmake": IconSet["cmake"], - "asm": IconSet["assembly"], - "a51": IconSet["assembly"], - "inc": IconSet["assembly"], - "nasm": IconSet["assembly"], - "s": IconSet["assembly"], - "ms": IconSet["assembly"], - "agc": IconSet["assembly"], - "ags": IconSet["assembly"], - "aea": IconSet["assembly"], - "argus": IconSet["assembly"], - "mitigus": IconSet["assembly"], - "binsource": IconSet["assembly"], - "vue": IconSet["vue"], - "ml": IconSet["ocaml"], - "mli": IconSet["ocaml"], - "cmx": IconSet["ocaml"], - "lock": IconSet["lock"], - "hbs": IconSet["handlebars"], - "mustache": IconSet["handlebars"], - "pm": IconSet["perl"], - "raku": IconSet["perl"], - "hx": IconSet["haxe"], - "pp": IconSet["puppet"], - "ex": IconSet["elixir"], - "exs": IconSet["elixir"], - "eex": IconSet["elixir"], - "leex": IconSet["elixir"], - "erl": IconSet["erlang"], - "twig": IconSet["twig"], - "jl": IconSet["julia"], - "elm": IconSet["elm"], - "pure": IconSet["purescript"], - "purs": IconSet["purescript"], - "tpl": IconSet["smarty"], - "styl": IconSet["stylus"], - "merlin": IconSet["merlin"], - "v": IconSet["verilog"], - "vhd": IconSet["verilog"], - "sv": IconSet["verilog"], - "svh": IconSet["verilog"], - "robot": IconSet["robot"], - "sol": IconSet["solidity"], - "yang": IconSet["yang"], - "mjml": IconSet["mjml"], - "tf": IconSet["terraform"], - "tfvars": IconSet["terraform"], - "tfstate": IconSet["terraform"], - "applescript": IconSet["applescript"], - "ipa": IconSet["applescript"], - "cake": IconSet["cake"], - "nim": IconSet["nim"], - "nimble": IconSet["nim"], - "apib": IconSet["apiblueprint"], - "apiblueprint": IconSet["apiblueprint"], - "pcss": IconSet["postcss"], - "sss": IconSet["postcss"], - "todo": IconSet["todo"], - "nix": IconSet["nix"], - "slim": IconSet["slim"], - "http": IconSet["http"], - "rest": IconSet["http"], - "apk": IconSet["android"], - "env": IconSet["tune"], - "jenkinsfile": IconSet["jenkins"], - "jenkins": IconSet["jenkins"], - "log": IconSet["log"], - "ejs": IconSet["ejs"], - "djt": IconSet["django"], - "pot": IconSet["i18n"], - "po": IconSet["i18n"], - "mo": IconSet["i18n"], - "d": IconSet["d"], - "mdx": IconSet["mdx"], - "gd": IconSet["godot"], - "godot": IconSet["godot-assets"], - "tres": IconSet["godot-assets"], - "tscn": IconSet["godot-assets"], - "azcli": IconSet["azure"], - "vagrantfile": IconSet["vagrant"], - "cshtml": IconSet["razor"], - "vbhtml": IconSet["razor"], - "ad": IconSet["asciidoc"], - "adoc": IconSet["asciidoc"], - "asciidoc": IconSet["asciidoc"], - "edge": IconSet["edge"], - "ss": IconSet["scheme"], - "scm": IconSet["scheme"], - "stl": IconSet["3d"], - "obj": IconSet["3d"], - "ac": IconSet["3d"], - "blend": IconSet["3d"], - "mesh": IconSet["3d"], - "mqo": IconSet["3d"], - "pmd": IconSet["3d"], - "pmx": IconSet["3d"], - "skp": IconSet["3d"], - "vac": IconSet["3d"], - "vdp": IconSet["3d"], - "vox": IconSet["3d"], - "svg": IconSet["svg"], - "vimrc": IconSet["vim"], - "gvimrc": IconSet["vim"], - "exrc": IconSet["vim"], - "moon": IconSet["moonscript"], - "iso": IconSet["disc"], - "f": IconSet["fortran"], - "f77": IconSet["fortran"], - "f90": IconSet["fortran"], - "f95": IconSet["fortran"], - "f03": IconSet["fortran"], - "f08": IconSet["fortran"], - "tcl": IconSet["tcl"], - "liquid": IconSet["liquid"], - "p": IconSet["prolog"], - "pro": IconSet["prolog"], - "coco": IconSet["coconut"], - "sketch": IconSet["sketch"], - "opam": IconSet["opam"], - "dhallb": IconSet["dhall"], - "pwn": IconSet["pawn"], - "amx": IconSet["pawn"], - "dhall": IconSet["dhall"], - "pas": IconSet["pascal"], - "unity": IconSet["shaderlab"], - "nupkg": IconSet["nuget"], - "command": IconSet["command"], - "dsc": IconSet["denizenscript"], - "deb": IconSet["debian"], - "rpm": IconSet["redhat"], - "snap": IconSet["ubuntu"], - "ebuild": IconSet["gentoo"], - "pkg": IconSet["applescript"], - "openbsd": IconSet["freebsd"], - // "ls": IconSet["livescript"], - // "re": IconSet["reason"], - // "rei": IconSet["reason"], - // "cmj": IconSet["bucklescript"], - // "nb": IconSet["mathematica"], - // "wl": IconSet["wolframlanguage"], - // "wls": IconSet["wolframlanguage"], - // "njk": IconSet["nunjucks"], - // "nunjucks": IconSet["nunjucks"], - // "au3": IconSet["autoit"], - // "haml": IconSet["haml"], - // "feature": IconSet["cucumber"], - // "riot": IconSet["riot"], - // "tag": IconSet["riot"], - // "vfl": IconSet["vfl"], - // "kl": IconSet["kl"], - // "cfml": IconSet["coldfusion"], - // "cfc": IconSet["coldfusion"], - // "lucee": IconSet["coldfusion"], - // "cfm": IconSet["coldfusion"], - // "cabal": IconSet["cabal"], - // "rql": IconSet["restql"], - // "restql": IconSet["restql"], - // "kv": IconSet["kivy"], - // "graphcool": IconSet["graphcool"], - // "sbt": IconSet["sbt"], - // "cr": IconSet["crystal"], - // "ecr": IconSet["crystal"], - // "cu": IconSet["cuda"], - // "cuh": IconSet["cuda"], - // "def": IconSet["dotjs"], - // "dot": IconSet["dotjs"], - // "jst": IconSet["dotjs"], - // "pde": IconSet["processing"], - // "wpy": IconSet["wepy"], - // "hcl": IconSet["hcl"], - // "san": IconSet["san"], - // "red": IconSet["red"], - // "fxp": IconSet["foxpro"], - // "prg": IconSet["foxpro"], - // "wat": IconSet["webassembly"], - // "wasm": IconSet["webassembly"], - // "ipynb": IconSet["jupyter"], - // "bal": IconSet["ballerina"], - // "balx": IconSet["ballerina"], - // "rkt": IconSet["racket"], - // "bzl": IconSet["bazel"], - // "bazel": IconSet["bazel"], - // "mint": IconSet["mint"], - // "vm": IconSet["velocity"], - // "fhtml": IconSet["velocity"], - // "vtl": IconSet["velocity"], - // "prisma": IconSet["prisma"], - // "abc": IconSet["abc"], - // "lisp": IconSet["lisp"], - // "lsp": IconSet["lisp"], - // "cl": IconSet["lisp"], - // "fast": IconSet["lisp"], - // "svelte": IconSet["svelte"], - // "prw": IconSet["advpl_prw"], - // "prx": IconSet["advpl_prw"], - // "ptm": IconSet["advpl_ptm"], - // "tlpp": IconSet["advpl_tlpp"], - // "ch": IconSet["advpl_include"], - // "4th": IconSet["forth"], - // "fth": IconSet["forth"], - // "frt": IconSet["forth"], - // "iuml": IconSet["uml"], - // "pu": IconSet["uml"], - // "puml": IconSet["uml"], - // "plantuml": IconSet["uml"], - // "wsd": IconSet["uml"], - // "sml": IconSet["sml"], - // "mlton": IconSet["sml"], - // "mlb": IconSet["sml"], - // "sig": IconSet["sml"], - // "fun": IconSet["sml"], - // "cm": IconSet["sml"], - // "lex": IconSet["sml"], - // "use": IconSet["sml"], - // "grm": IconSet["sml"], - // "imba": IconSet["imba"], - // "drawio": IconSet["drawio"], - // "dio": IconSet["drawio"], - // "sas": IconSet["sas"], - // "sas7bdat": IconSet["sas"], - // "sashdat": IconSet["sas"], - // "astore": IconSet["sas"], - // "ast": IconSet["sas"], - // "sast": IconSet["sas"], -} diff --git a/icons/filenames.go b/icons/filenames.go deleted file mode 100644 index 31779f9..0000000 --- a/icons/filenames.go +++ /dev/null @@ -1,415 +0,0 @@ -package icons - -// IconFileName is used to get the icon based on its filename. -var IconFileName = map[string]*IconInfo{ - ".pug-lintrc": IconSet["pug"], - ".pug-lintrc.js": IconSet["pug"], - ".pug-lintrc.json": IconSet["pug"], - ".jscsrc": IconSet["json"], - ".jshintrc": IconSet["json"], - "composer.lock": IconSet["json"], - ".jsbeautifyrc": IconSet["json"], - ".esformatter": IconSet["json"], - "cdp.pid": IconSet["json"], - ".mjmlconfig": IconSet["json"], - ".htaccess": IconSet["xml"], - ".jshintignore": IconSet["settings"], - ".buildignore": IconSet["settings"], - ".mrconfig": IconSet["settings"], - ".yardopts": IconSet["settings"], - "manifest.mf": IconSet["settings"], - ".clang-format": IconSet["settings"], - ".clang-tidy": IconSet["settings"], - "go.mod": IconSet["go-mod"], - "go.sum": IconSet["go-mod"], - "requirements.txt": IconSet["python-misc"], - "pipfile": IconSet["python-misc"], - ".python-version": IconSet["python-misc"], - "manifest.in": IconSet["python-misc"], - "gradle.properties": IconSet["gradle"], - "gradlew": IconSet["gradle"], - "gradle-wrapper.properties": IconSet["gradle"], - "license": IconSet["certificate"], - "license.md": IconSet["certificate"], - "license.txt": IconSet["certificate"], - "licence": IconSet["certificate"], - "licence.md": IconSet["certificate"], - "licence.txt": IconSet["certificate"], - "unlicense": IconSet["certificate"], - "unlicense.md": IconSet["certificate"], - "unlicense.txt": IconSet["certificate"], - ".htpasswd": IconSet["key"], - "gemfile": IconSet["gemfile"], - "dockerfile": IconSet["docker"], - "dockerfile.prod": IconSet["docker"], - "dockerfile.production": IconSet["docker"], - "docker-compose.yml": IconSet["docker"], - "docker-compose.yaml": IconSet["docker"], - "docker-compose.dev.yml": IconSet["docker"], - "docker-compose.local.yml": IconSet["docker"], - "docker-compose.ci.yml": IconSet["docker"], - "docker-compose.override.yml": IconSet["docker"], - "docker-compose.staging.yml": IconSet["docker"], - "docker-compose.prod.yml": IconSet["docker"], - "docker-compose.production.yml": IconSet["docker"], - "docker-compose.test.yml": IconSet["docker"], - ".mailmap": IconSet["email"], - ".graphqlconfig": IconSet["graphql"], - ".gitignore": IconSet["git"], - ".gitconfig": IconSet["git"], - ".gitattributes": IconSet["git"], - ".gitmodules": IconSet["git"], - ".gitkeep": IconSet["git"], - "git-history": IconSet["git"], - ".luacheckrc": IconSet["lua"], - ".Rhistory": IconSet["r"], - "cmakelists.txt": IconSet["cmake"], - "cmakecache.txt": IconSet["cmake"], - "vue.config.js": IconSet["vue-config"], - "vue.config.ts": IconSet["vue-config"], - "nuxt.config.js": IconSet["nuxt"], - "nuxt.config.ts": IconSet["nuxt"], - "security.md": IconSet["lock"], - "security.txt": IconSet["lock"], - "security": IconSet["lock"], - "vercel.json": IconSet["vercel"], - ".vercelignore": IconSet["vercel"], - "now.json": IconSet["vercel"], - ".nowignore": IconSet["vercel"], - "postcss.config.js": IconSet["postcss"], - ".postcssrc.js": IconSet["postcss"], - ".postcssrc": IconSet["postcss"], - ".postcssrc.json": IconSet["postcss"], - ".postcssrc.yml": IconSet["postcss"], - "CNAME": IconSet["http"], - "webpack.js": IconSet["webpack"], - "webpack.ts": IconSet["webpack"], - "webpack.base.js": IconSet["webpack"], - "webpack.base.ts": IconSet["webpack"], - "webpack.config.js": IconSet["webpack"], - "webpack.config.ts": IconSet["webpack"], - "webpack.common.js": IconSet["webpack"], - "webpack.common.ts": IconSet["webpack"], - "webpack.config.common.js": IconSet["webpack"], - "webpack.config.common.ts": IconSet["webpack"], - "webpack.config.common.babel.js": IconSet["webpack"], - "webpack.config.common.babel.ts": IconSet["webpack"], - "webpack.dev.js": IconSet["webpack"], - "webpack.dev.ts": IconSet["webpack"], - "webpack.development.js": IconSet["webpack"], - "webpack.development.ts": IconSet["webpack"], - "webpack.config.dev.js": IconSet["webpack"], - "webpack.config.dev.ts": IconSet["webpack"], - "webpack.config.dev.babel.js": IconSet["webpack"], - "webpack.config.dev.babel.ts": IconSet["webpack"], - "webpack.prod.js": IconSet["webpack"], - "webpack.prod.ts": IconSet["webpack"], - "webpack.production.js": IconSet["webpack"], - "webpack.production.ts": IconSet["webpack"], - "webpack.server.js": IconSet["webpack"], - "webpack.server.ts": IconSet["webpack"], - "webpack.client.js": IconSet["webpack"], - "webpack.client.ts": IconSet["webpack"], - "webpack.config.server.js": IconSet["webpack"], - "webpack.config.server.ts": IconSet["webpack"], - "webpack.config.client.js": IconSet["webpack"], - "webpack.config.client.ts": IconSet["webpack"], - "webpack.config.production.babel.js": IconSet["webpack"], - "webpack.config.production.babel.ts": IconSet["webpack"], - "webpack.config.prod.babel.js": IconSet["webpack"], - "webpack.config.prod.babel.ts": IconSet["webpack"], - "webpack.config.prod.js": IconSet["webpack"], - "webpack.config.prod.ts": IconSet["webpack"], - "webpack.config.production.js": IconSet["webpack"], - "webpack.config.production.ts": IconSet["webpack"], - "webpack.config.staging.js": IconSet["webpack"], - "webpack.config.staging.ts": IconSet["webpack"], - "webpack.config.babel.js": IconSet["webpack"], - "webpack.config.babel.ts": IconSet["webpack"], - "webpack.config.base.babel.js": IconSet["webpack"], - "webpack.config.base.babel.ts": IconSet["webpack"], - "webpack.config.base.js": IconSet["webpack"], - "webpack.config.base.ts": IconSet["webpack"], - "webpack.config.staging.babel.js": IconSet["webpack"], - "webpack.config.staging.babel.ts": IconSet["webpack"], - "webpack.config.coffee": IconSet["webpack"], - "webpack.config.test.js": IconSet["webpack"], - "webpack.config.test.ts": IconSet["webpack"], - "webpack.config.vendor.js": IconSet["webpack"], - "webpack.config.vendor.ts": IconSet["webpack"], - "webpack.config.vendor.production.js": IconSet["webpack"], - "webpack.config.vendor.production.ts": IconSet["webpack"], - "webpack.test.js": IconSet["webpack"], - "webpack.test.ts": IconSet["webpack"], - "webpack.dist.js": IconSet["webpack"], - "webpack.dist.ts": IconSet["webpack"], - "webpackfile.js": IconSet["webpack"], - "webpackfile.ts": IconSet["webpack"], - "ionic.config.json": IconSet["ionic"], - ".io-config.json": IconSet["ionic"], - "gulpfile.js": IconSet["gulp"], - "gulpfile.mjs": IconSet["gulp"], - "gulpfile.ts": IconSet["gulp"], - "gulpfile.babel.js": IconSet["gulp"], - "package.json": IconSet["nodejs"], - "package-lock.json": IconSet["nodejs"], - ".nvmrc": IconSet["nodejs"], - ".esmrc": IconSet["nodejs"], - ".node-version": IconSet["nodejs"], - ".npmignore": IconSet["npm"], - ".npmrc": IconSet["npm"], - ".yarnrc": IconSet["yarn"], - "yarn.lock": IconSet["yarn"], - ".yarnclean": IconSet["yarn"], - ".yarn-integrity": IconSet["yarn"], - "yarn-error.log": IconSet["yarn"], - ".yarnrc.yml": IconSet["yarn"], - ".yarnrc.yaml": IconSet["yarn"], - "androidmanifest.xml": IconSet["android"], - ".env.defaults": IconSet["tune"], - ".env.example": IconSet["tune"], - ".env.sample": IconSet["tune"], - ".env.schema": IconSet["tune"], - ".env.local": IconSet["tune"], - ".env.dev": IconSet["tune"], - ".env.development": IconSet["tune"], - ".env.qa": IconSet["tune"], - ".env.prod": IconSet["tune"], - ".env.production": IconSet["tune"], - ".env.staging": IconSet["tune"], - ".env.preview": IconSet["tune"], - ".env.test": IconSet["tune"], - ".env.testing": IconSet["tune"], - ".env.development.local": IconSet["tune"], - ".env.qa.local": IconSet["tune"], - ".env.production.local": IconSet["tune"], - ".env.staging.local": IconSet["tune"], - ".env.test.local": IconSet["tune"], - ".babelrc": IconSet["babel"], - ".babelrc.js": IconSet["babel"], - ".babelrc.json": IconSet["babel"], - "babel.config.json": IconSet["babel"], - "babel.config.js": IconSet["babel"], - "contributing.md": IconSet["contributing"], - "readme.md": IconSet["readme"], - "readme.txt": IconSet["readme"], - "readme": IconSet["readme"], - "changelog": IconSet["changelog"], - "changelog.md": IconSet["changelog"], - "changelog.txt": IconSet["changelog"], - "changes": IconSet["changelog"], - "changes.md": IconSet["changelog"], - "changes.txt": IconSet["changelog"], - "credits": IconSet["credits"], - "credits.txt": IconSet["credits"], - "credits.md": IconSet["credits"], - "authors": IconSet["authors"], - "authors.md": IconSet["authors"], - "authors.txt": IconSet["authors"], - "favicon.ico": IconSet["favicon"], - "karma.conf.js": IconSet["karma"], - "karma.conf.ts": IconSet["karma"], - "karma.conf.coffee": IconSet["karma"], - "karma.config.js": IconSet["karma"], - "karma.config.ts": IconSet["karma"], - "karma-main.js": IconSet["karma"], - "karma-main.ts": IconSet["karma"], - ".travis.yml": IconSet["travis"], - ".codecov.yml": IconSet["codecov"], - "codecov.yml": IconSet["codecov"], - "protractor.conf.js": IconSet["protractor"], - "protractor.conf.ts": IconSet["protractor"], - "protractor.conf.coffee": IconSet["protractor"], - "protractor.config.js": IconSet["protractor"], - "protractor.config.ts": IconSet["protractor"], - "procfile": IconSet["heroku"], - "procfile.windows": IconSet["heroku"], - ".bowerrc": IconSet["bower"], - "bower.json": IconSet["bower"], - ".eslintrc.js": IconSet["eslint"], - ".eslintrc.cjs": IconSet["eslint"], - ".eslintrc.yaml": IconSet["eslint"], - ".eslintrc.yml": IconSet["eslint"], - ".eslintrc.json": IconSet["eslint"], - ".eslintrc": IconSet["eslint"], - ".eslintignore": IconSet["eslint"], - ".eslintcache": IconSet["eslint"], - "code_of_conduct.md": IconSet["conduct"], - "code_of_conduct.txt": IconSet["conduct"], - "mocha.opts": IconSet["mocha"], - ".mocharc.yml": IconSet["mocha"], - ".mocharc.yaml": IconSet["mocha"], - ".mocharc.js": IconSet["mocha"], - ".mocharc.json": IconSet["mocha"], - ".mocharc.jsonc": IconSet["mocha"], - "jenkinsfile": IconSet["jenkins"], - "firebase.json": IconSet["firebase"], - ".firebaserc": IconSet["firebase"], - "firestore.rules": IconSet["firebase"], - "firestore.indexes.json": IconSet["firebase"], - ".stylelintrc": IconSet["stylelint"], - "stylelint.config.js": IconSet["stylelint"], - ".stylelintrc.json": IconSet["stylelint"], - ".stylelintrc.yaml": IconSet["stylelint"], - ".stylelintrc.yml": IconSet["stylelint"], - ".stylelintrc.js": IconSet["stylelint"], - ".stylelintignore": IconSet["stylelint"], - ".codeclimate.yml": IconSet["code-climate"], - ".prettierrc": IconSet["prettier"], - "prettier.config.js": IconSet["prettier"], - ".prettierrc.js": IconSet["prettier"], - ".prettierrc.json": IconSet["prettier"], - ".prettierrc.yaml": IconSet["prettier"], - ".prettierrc.yml": IconSet["prettier"], - ".prettierignore": IconSet["prettier"], - "gruntfile.js": IconSet["grunt"], - "gruntfile.ts": IconSet["grunt"], - "gruntfile.coffee": IconSet["grunt"], - "gruntfile.babel.js": IconSet["grunt"], - "gruntfile.babel.ts": IconSet["grunt"], - "gruntfile.babel.coffee": IconSet["grunt"], - "jest.config.js": IconSet["jest"], - "jest.config.ts": IconSet["jest"], - "jest.config.cjs": IconSet["jest"], - "jest.config.mjs": IconSet["jest"], - "jest.config.json": IconSet["jest"], - "jest.e2e.config.js": IconSet["jest"], - "jest.e2e.config.ts": IconSet["jest"], - "jest.e2e.config.cjs": IconSet["jest"], - "jest.e2e.config.mjs": IconSet["jest"], - "jest.e2e.config.json": IconSet["jest"], - "jest.setup.js": IconSet["jest"], - "jest.setup.ts": IconSet["jest"], - "jest.json": IconSet["jest"], - ".jestrc": IconSet["jest"], - ".jestrc.js": IconSet["jest"], - ".jestrc.json": IconSet["jest"], - "jest.teardown.js": IconSet["jest"], - "fastfile": IconSet["fastlane"], - "appfile": IconSet["fastlane"], - ".helmignore": IconSet["helm"], - "makefile": IconSet["makefile"], - ".releaserc": IconSet["semantic-release"], - ".releaserc.yaml": IconSet["semantic-release"], - ".releaserc.yml": IconSet["semantic-release"], - ".releaserc.json": IconSet["semantic-release"], - ".releaserc.js": IconSet["semantic-release"], - "release.config.js": IconSet["semantic-release"], - "bitbucket-pipelines.yaml": IconSet["bitbucket"], - "bitbucket-pipelines.yml": IconSet["bitbucket"], - "azure-pipelines.yml": IconSet["azure-pipelines"], - "azure-pipelines.yaml": IconSet["azure-pipelines"], - "vagrantfile": IconSet["vagrant"], - "tailwind.js": IconSet["tailwindcss"], - "tailwind.config.js": IconSet["tailwindcss"], - "codeowners": IconSet["codeowners"], - ".gcloudignore": IconSet["gcp"], - ".huskyrc": IconSet["husky"], - "husky.config.js": IconSet["husky"], - ".huskyrc.json": IconSet["husky"], - ".huskyrc.js": IconSet["husky"], - ".huskyrc.yaml": IconSet["husky"], - ".huskyrc.yml": IconSet["husky"], - ".commitlintrc": IconSet["commitlint"], - ".commitlintrc.js": IconSet["commitlint"], - "commitlint.config.js": IconSet["commitlint"], - ".commitlintrc.json": IconSet["commitlint"], - ".commitlint.yaml": IconSet["commitlint"], - ".commitlint.yml": IconSet["commitlint"], - "dune": IconSet["dune"], - "dune-project": IconSet["dune"], - "roadmap.md": IconSet["roadmap"], - "roadmap.txt": IconSet["roadmap"], - "timeline.md": IconSet["roadmap"], - "timeline.txt": IconSet["roadmap"], - "milestones.md": IconSet["roadmap"], - "milestones.txt": IconSet["roadmap"], - "nuget.config": IconSet["nuget"], - ".nuspec": IconSet["nuget"], - "nuget.exe": IconSet["nuget"], - "stryker.conf.js": IconSet["stryker"], - "stryker.conf.json": IconSet["stryker"], - ".modernizrrc": IconSet["modernizr"], - ".modernizrrc.js": IconSet["modernizr"], - ".modernizrrc.json": IconSet["modernizr"], - "routing.ts": IconSet["routing"], - "routing.tsx": IconSet["routing"], - "routing.js": IconSet["routing"], - "routing.jsx": IconSet["routing"], - "routes.ts": IconSet["routing"], - "routes.tsx": IconSet["routing"], - "routes.js": IconSet["routing"], - "routes.jsx": IconSet["routing"], - // ".vfl": IconSet["vfl"], - // ".kl": IconSet["kl"], - // "project.graphcool": IconSet["graphcool"], - // ".flowconfig": IconSet["flow"], - // ".bithoundrc": IconSet["bithound"], - // ".appveyor.yml": IconSet["appveyor"], - // "appveyor.yml": IconSet["appveyor"], - // "fuse.js": IconSet["fusebox"], - // ".editorconfig": IconSet["editorconfig"], - // ".watchmanconfig": IconSet["watchman"], - // "aurelia.json": IconSet["aurelia"], - // "rollup.config.js": IconSet["rollup"], - // "rollup.config.ts": IconSet["rollup"], - // "rollup-config.js": IconSet["rollup"], - // "rollup-config.ts": IconSet["rollup"], - // "rollup.config.common.js": IconSet["rollup"], - // "rollup.config.common.ts": IconSet["rollup"], - // "rollup.config.base.js": IconSet["rollup"], - // "rollup.config.base.ts": IconSet["rollup"], - // "rollup.config.prod.js": IconSet["rollup"], - // "rollup.config.prod.ts": IconSet["rollup"], - // "rollup.config.dev.js": IconSet["rollup"], - // "rollup.config.dev.ts": IconSet["rollup"], - // "rollup.config.prod.vendor.js": IconSet["rollup"], - // "rollup.config.prod.vendor.ts": IconSet["rollup"], - // ".hhconfig": IconSet["hack"], - // "apollo.config.js": IconSet["apollo"], - // "nodemon.json": IconSet["nodemon"], - // "nodemon-debug.json": IconSet["nodemon"], - // ".hintrc": IconSet["webhint"], - // "browserslist": IconSet["browserlist"], - // ".browserslistrc": IconSet["browserlist"], - // ".snyk": IconSet["snyk"], - // ".drone.yml": IconSet["drone"], - // ".sequelizerc": IconSet["sequelize"], - // "gatsby.config.js": IconSet["gatsby"], - // "gatsby-config.js": IconSet["gatsby"], - // "gatsby-node.js": IconSet["gatsby"], - // "gatsby-browser.js": IconSet["gatsby"], - // "gatsby-ssr.js": IconSet["gatsby"], - // ".wakatime-project": IconSet["wakatime"], - // "circle.yml": IconSet["circleci"], - // ".cfignore": IconSet["cloudfoundry"], - // "wallaby.js": IconSet["wallaby"], - // "wallaby.conf.js": IconSet["wallaby"], - // "stencil.config.js": IconSet["stencil"], - // "stencil.config.ts": IconSet["stencil"], - // ".bazelignore": IconSet["bazel"], - // ".bazelrc": IconSet["bazel"], - // "prisma.yml": IconSet["prisma"], - // ".nycrc": IconSet["istanbul"], - // ".nycrc.json": IconSet["istanbul"], - // "buildkite.yml": IconSet["buildkite"], - // "buildkite.yaml": IconSet["buildkite"], - // "netlify.json": IconSet["netlify"], - // "netlify.yml": IconSet["netlify"], - // "netlify.yaml": IconSet["netlify"], - // "netlify.toml": IconSet["netlify"], - // "nest-cli.json": IconSet["nest"], - // ".nest-cli.json": IconSet["nest"], - // "nestconfig.json": IconSet["nest"], - // ".nestconfig.json": IconSet["nest"], - // ".percy.yml": IconSet["percy"], - // ".gitpod.yml": IconSet["gitpod"], - // "tiltfile": IconSet["tilt"], - // "capacitor.config.json": IconSet["capacitor"], - // ".adonisrc.json": IconSet["adonis"], - // "ace": IconSet["adonis"], - // "meson.build": IconSet["meson"], - // ".buckconfig": IconSet["buck"], - // "nx.json": IconSet["nrwl"], - // ".slugignore": IconSet["slug"], -} diff --git a/icons/glyphs.go b/icons/glyphs.go deleted file mode 100644 index d220b2c..0000000 --- a/icons/glyphs.go +++ /dev/null @@ -1,362 +0,0 @@ -package icons - -import "fmt" - -// IconInfo is a struct that holds information about an icon. -type IconInfo struct { - icon string - color [3]uint8 - executable bool -} - -// GetGlyph returns the glyph for the icon. -func (i *IconInfo) GetGlyph() string { - return i.icon -} - -// GetColor returns the color for the icon. -func (i *IconInfo) GetColor(f uint8) string { - switch { - case i.executable: - return "\033[38;2;76;175;080m" - default: - return fmt.Sprintf("\033[38;2;%d;%d;%dm", i.color[0], i.color[1], i.color[2]) - } -} - -// MakeExe is a function that returns a new IconInfo struct with the executable flag set to true. -func (i *IconInfo) MakeExe() { - i.executable = true -} - -// IconSet is a map to represent all the icons. -var IconSet = map[string]*IconInfo{ - "html": {icon: "\uf13b", color: [3]uint8{228, 79, 57}}, // html - "markdown": {icon: "\uf853", color: [3]uint8{66, 165, 245}}, // markdown - "css": {icon: "\uf81b", color: [3]uint8{66, 165, 245}}, // css - "css-map": {icon: "\ue749", color: [3]uint8{66, 165, 245}}, // css-map - "sass": {icon: "\ue603", color: [3]uint8{237, 80, 122}}, // sass - "less": {icon: "\ue60b", color: [3]uint8{2, 119, 189}}, // less - "json": {icon: "\ue60b", color: [3]uint8{251, 193, 60}}, // json - "yaml": {icon: "\ue60b", color: [3]uint8{244, 68, 62}}, // yaml - "xml": {icon: "\uf72d", color: [3]uint8{64, 153, 69}}, // xml - "image": {icon: "\uf71e", color: [3]uint8{48, 166, 154}}, // image - "javascript": {icon: "\ue74e", color: [3]uint8{255, 202, 61}}, // javascript - "javascript-map": {icon: "\ue781", color: [3]uint8{255, 202, 61}}, // javascript-map - "test-jsx": {icon: "\uf595", color: [3]uint8{35, 188, 212}}, // test-jsx - "test-js": {icon: "\uf595", color: [3]uint8{255, 202, 61}}, // test-js - "react": {icon: "\ue7ba", color: [3]uint8{35, 188, 212}}, // react - "react_ts": {icon: "\ue7ba", color: [3]uint8{36, 142, 211}}, // react_ts - "settings": {icon: "\uf013", color: [3]uint8{66, 165, 245}}, // settings - "typescript": {icon: "\ue628", color: [3]uint8{3, 136, 209}}, // typescript - "typescript-def": {icon: "\ufbe4", color: [3]uint8{3, 136, 209}}, // typescript-def - "test-ts": {icon: "\uf595", color: [3]uint8{3, 136, 209}}, // test-ts - "pdf": {icon: "\uf724", color: [3]uint8{244, 68, 62}}, // pdf - "table": {icon: "\uf71a", color: [3]uint8{139, 195, 74}}, // table - "visualstudio": {icon: "\ue70c", color: [3]uint8{173, 99, 188}}, // visualstudio - "database": {icon: "\ue706", color: [3]uint8{255, 202, 61}}, // database - "mysql": {icon: "\ue704", color: [3]uint8{1, 94, 134}}, // mysql - "postgresql": {icon: "\ue76e", color: [3]uint8{49, 99, 140}}, // postgresql - "sqlite": {icon: "\ue7c4", color: [3]uint8{1, 57, 84}}, // sqlite - "csharp": {icon: "\uf81a", color: [3]uint8{2, 119, 189}}, // csharp - "zip": {icon: "\uf410", color: [3]uint8{175, 180, 43}}, // zip - "exe": {icon: "\uf2d0", color: [3]uint8{229, 77, 58}}, // exe - "java": {icon: "\uf675", color: [3]uint8{244, 68, 62}}, // java - "c": {icon: "\ufb70", color: [3]uint8{2, 119, 189}}, // c - "cpp": {icon: "\ufb71", color: [3]uint8{2, 119, 189}}, // cpp - "go": {icon: "\ufcd1", color: [3]uint8{32, 173, 194}}, // go - "go-mod": {icon: "\ufcd1", color: [3]uint8{237, 80, 122}}, // go-mod - "go-test": {icon: "\ufcd1", color: [3]uint8{255, 213, 79}}, // go-test - "python": {icon: "\uf81f", color: [3]uint8{52, 102, 143}}, // python - "python-misc": {icon: "\uf820", color: [3]uint8{130, 61, 28}}, // python-misc - "url": {icon: "\uf836", color: [3]uint8{66, 165, 245}}, // url - "console": {icon: "\uf68c", color: [3]uint8{250, 111, 66}}, // console - "word": {icon: "\uf72b", color: [3]uint8{1, 87, 155}}, // word - "certificate": {icon: "\uf623", color: [3]uint8{249, 89, 63}}, // certificate - "key": {icon: "\uf805", color: [3]uint8{48, 166, 154}}, // key - "font": {icon: "\uf031", color: [3]uint8{244, 68, 62}}, // font - "lib": {icon: "\uf831", color: [3]uint8{139, 195, 74}}, // lib - "ruby": {icon: "\ue739", color: [3]uint8{229, 61, 58}}, // ruby - "gemfile": {icon: "\ue21e", color: [3]uint8{229, 61, 58}}, // gemfile - "fsharp": {icon: "\ue7a7", color: [3]uint8{55, 139, 186}}, // fsharp - "swift": {icon: "\ufbe3", color: [3]uint8{249, 95, 63}}, // swift - "docker": {icon: "\uf308", color: [3]uint8{1, 135, 201}}, // docker - "powerpoint": {icon: "\uf726", color: [3]uint8{209, 71, 51}}, // powerpoint - "video": {icon: "\uf72a", color: [3]uint8{253, 154, 62}}, // video - "virtual": {icon: "\uf822", color: [3]uint8{3, 155, 229}}, // virtual - "email": {icon: "\uf6ed", color: [3]uint8{66, 165, 245}}, // email - "audio": {icon: "\ufb75", color: [3]uint8{239, 83, 80}}, // audio - "coffee": {icon: "\uf675", color: [3]uint8{66, 165, 245}}, // coffee - "document": {icon: "\uf718", color: [3]uint8{66, 165, 245}}, // document - "rust": {icon: "\ue7a8", color: [3]uint8{250, 111, 66}}, // rust - "raml": {icon: "\ue60b", color: [3]uint8{66, 165, 245}}, // raml - "xaml": {icon: "\ufb72", color: [3]uint8{66, 165, 245}}, // xaml - "haskell": {icon: "\ue61f", color: [3]uint8{254, 168, 62}}, // haskell - "git": {icon: "\ue702", color: [3]uint8{229, 77, 58}}, // git - "lua": {icon: "\ue620", color: [3]uint8{66, 165, 245}}, // lua - "clojure": {icon: "\ue76a", color: [3]uint8{100, 221, 23}}, // clojure - "groovy": {icon: "\uf2a6", color: [3]uint8{41, 198, 218}}, // groovy - "r": {icon: "\ufcd2", color: [3]uint8{25, 118, 210}}, // r - "dart": {icon: "\ue798", color: [3]uint8{87, 182, 240}}, // dart - "mxml": {icon: "\uf72d", color: [3]uint8{254, 168, 62}}, // mxml - "assembly": {icon: "\uf471", color: [3]uint8{250, 109, 63}}, // assembly - "vue": {icon: "\ufd42", color: [3]uint8{65, 184, 131}}, // vue - "vue-config": {icon: "\ufd42", color: [3]uint8{58, 121, 110}}, // vue-config - "lock": {icon: "\uf83d", color: [3]uint8{255, 213, 79}}, // lock - "handlebars": {icon: "\ue60f", color: [3]uint8{250, 111, 66}}, // handlebars - "perl": {icon: "\ue769", color: [3]uint8{149, 117, 205}}, // perl - "elixir": {icon: "\ue62d", color: [3]uint8{149, 117, 205}}, // elixir - "erlang": {icon: "\ue7b1", color: [3]uint8{244, 68, 62}}, // erlang - "twig": {icon: "\ue61c", color: [3]uint8{155, 185, 47}}, // twig - "julia": {icon: "\ue624", color: [3]uint8{134, 82, 159}}, // julia - "elm": {icon: "\ue62c", color: [3]uint8{96, 181, 204}}, // elm - "smarty": {icon: "\uf834", color: [3]uint8{255, 207, 60}}, // smarty - "stylus": {icon: "\ue600", color: [3]uint8{192, 202, 51}}, // stylus - "verilog": {icon: "\ufb19", color: [3]uint8{250, 111, 66}}, // verilog - "robot": {icon: "\ufba7", color: [3]uint8{249, 89, 63}}, // robot - "solidity": {icon: "\ufcb9", color: [3]uint8{3, 136, 209}}, // solidity - "yang": {icon: "\ufb7e", color: [3]uint8{66, 165, 245}}, // yang - "vercel": {icon: "\uf47e", color: [3]uint8{207, 216, 220}}, // vercel - "applescript": {icon: "\uf302", color: [3]uint8{120, 144, 156}}, // applescript - "cake": {icon: "\uf5ea", color: [3]uint8{250, 111, 66}}, // cake - "nim": {icon: "\uf6a4", color: [3]uint8{255, 202, 61}}, // nim - "todo": {icon: "\uf058", color: [3]uint8{124, 179, 66}}, // task - "nix": {icon: "\uf313", color: [3]uint8{80, 117, 193}}, // nix - "http": {icon: "\uf484", color: [3]uint8{66, 165, 245}}, // http - "webpack": {icon: "\ufc29", color: [3]uint8{142, 214, 251}}, // webpack - "ionic": {icon: "\ue7a9", color: [3]uint8{79, 143, 247}}, // ionic - "gulp": {icon: "\ue763", color: [3]uint8{229, 61, 58}}, // gulp - "nodejs": {icon: "\uf898", color: [3]uint8{139, 195, 74}}, // nodejs - "npm": {icon: "\ue71e", color: [3]uint8{203, 56, 55}}, // npm - "yarn": {icon: "\uf61a", color: [3]uint8{44, 142, 187}}, // yarn - "android": {icon: "\uf531", color: [3]uint8{139, 195, 74}}, // android - "tune": {icon: "\ufb69", color: [3]uint8{251, 193, 60}}, // tune - "contributing": {icon: "\uf64d", color: [3]uint8{255, 202, 61}}, // contributing - "readme": {icon: "\uf7fb", color: [3]uint8{66, 165, 245}}, // readme - "changelog": {icon: "\ufba6", color: [3]uint8{139, 195, 74}}, // changelog - "credits": {icon: "\uf75f", color: [3]uint8{156, 204, 101}}, // credits - "authors": {icon: "\uf0c0", color: [3]uint8{244, 68, 62}}, // authors - "favicon": {icon: "\ue623", color: [3]uint8{255, 213, 79}}, // favicon - "karma": {icon: "\ue622", color: [3]uint8{60, 190, 174}}, // karma - "travis": {icon: "\ue77e", color: [3]uint8{203, 58, 73}}, // travis - "heroku": {icon: "\ue607", color: [3]uint8{105, 99, 185}}, // heroku - "gitlab": {icon: "\uf296", color: [3]uint8{226, 69, 57}}, // gitlab - "bower": {icon: "\ue61a", color: [3]uint8{239, 88, 60}}, // bower - "conduct": {icon: "\uf64b", color: [3]uint8{205, 220, 57}}, // conduct - "jenkins": {icon: "\ue767", color: [3]uint8{240, 214, 183}}, // jenkins - "code-climate": {icon: "\uf7f4", color: [3]uint8{238, 238, 238}}, // code-climate - "log": {icon: "\uf719", color: [3]uint8{175, 180, 43}}, // log - "ejs": {icon: "\ue618", color: [3]uint8{255, 202, 61}}, // ejs - "grunt": {icon: "\ue611", color: [3]uint8{251, 170, 61}}, // grunt - "django": {icon: "\ue71d", color: [3]uint8{67, 160, 71}}, // django - "makefile": {icon: "\uf728", color: [3]uint8{239, 83, 80}}, // makefile - "bitbucket": {icon: "\uf171", color: [3]uint8{31, 136, 229}}, // bitbucket - "d": {icon: "\ue7af", color: [3]uint8{244, 68, 62}}, // d - "mdx": {icon: "\uf853", color: [3]uint8{255, 202, 61}}, // mdx - "azure-pipelines": {icon: "\uf427", color: [3]uint8{20, 101, 192}}, // azure-pipelines - "azure": {icon: "\ufd03", color: [3]uint8{31, 136, 229}}, // azure - "razor": {icon: "\uf564", color: [3]uint8{66, 165, 245}}, // razor - "asciidoc": {icon: "\uf718", color: [3]uint8{244, 68, 62}}, // asciidoc - "edge": {icon: "\uf564", color: [3]uint8{239, 111, 60}}, // edge - "scheme": {icon: "\ufb26", color: [3]uint8{244, 68, 62}}, // scheme - "3d": {icon: "\ue79b", color: [3]uint8{40, 182, 246}}, // 3d - "svg": {icon: "\ufc1f", color: [3]uint8{255, 181, 62}}, // svg - "vim": {icon: "\ue62b", color: [3]uint8{67, 160, 71}}, // vim - "moonscript": {icon: "\uf186", color: [3]uint8{251, 193, 60}}, // moonscript - "codeowners": {icon: "\uf507", color: [3]uint8{175, 180, 43}}, // codeowners - "disc": {icon: "\ue271", color: [3]uint8{176, 190, 197}}, // disc - "fortran": {icon: "F", color: [3]uint8{250, 111, 66}}, // fortran - "tcl": {icon: "\ufbd1", color: [3]uint8{239, 83, 80}}, // tcl - "liquid": {icon: "\ue275", color: [3]uint8{40, 182, 246}}, // liquid - "prolog": {icon: "\ue7a1", color: [3]uint8{239, 83, 80}}, // prolog - "husky": {icon: "\uf8e8", color: [3]uint8{229, 229, 229}}, // husky - "coconut": {icon: "\uf5d2", color: [3]uint8{141, 110, 99}}, // coconut - "sketch": {icon: "\uf6c7", color: [3]uint8{255, 194, 61}}, // sketch - "pawn": {icon: "\ue261", color: [3]uint8{239, 111, 60}}, // pawn - "commitlint": {icon: "\ufc16", color: [3]uint8{43, 150, 137}}, // commitlint - "dhall": {icon: "\uf448", color: [3]uint8{120, 144, 156}}, // dhall - "dune": {icon: "\uf7f4", color: [3]uint8{244, 127, 61}}, // dune - "shaderlab": {icon: "\ufbad", color: [3]uint8{25, 118, 210}}, // shaderlab - "command": {icon: "\ufb32", color: [3]uint8{175, 188, 194}}, // command - "stryker": {icon: "\uf05b", color: [3]uint8{239, 83, 80}}, // stryker - "modernizr": {icon: "\ue720", color: [3]uint8{234, 72, 99}}, // modernizr - "roadmap": {icon: "\ufb6d", color: [3]uint8{48, 166, 154}}, // roadmap - "debian": {icon: "\uf306", color: [3]uint8{211, 61, 76}}, // debian - "ubuntu": {icon: "\uf31c", color: [3]uint8{214, 73, 53}}, // ubuntu - "arch": {icon: "\uf303", color: [3]uint8{33, 142, 202}}, // arch - "redhat": {icon: "\uf316", color: [3]uint8{231, 61, 58}}, // redhat - "gentoo": {icon: "\uf30d", color: [3]uint8{148, 141, 211}}, // gentoo - "linux": {icon: "\ue712", color: [3]uint8{238, 207, 55}}, // linux - "raspberry-pi": {icon: "\uf315", color: [3]uint8{208, 60, 76}}, // raspberry-pi - "manjaro": {icon: "\uf312", color: [3]uint8{73, 185, 90}}, // manjaro - "opensuse": {icon: "\uf314", color: [3]uint8{111, 180, 36}}, // opensuse - "fedora": {icon: "\uf30a", color: [3]uint8{52, 103, 172}}, // fedora - "freebsd": {icon: "\uf30c", color: [3]uint8{175, 44, 42}}, // freebsd - "centOS": {icon: "\uf304", color: [3]uint8{157, 83, 135}}, // centOS - "alpine": {icon: "\uf300", color: [3]uint8{14, 87, 123}}, // alpine - "mint": {icon: "\uf30f", color: [3]uint8{125, 190, 58}}, // mint - "routing": {icon: "\ufb40", color: [3]uint8{67, 160, 71}}, // routing - "laravel": {icon: "\ue73f", color: [3]uint8{248, 80, 81}}, // laravel - "pug": {icon: "\ue60e", color: [3]uint8{239, 204, 163}}, // pug (Not supported by nerdFont) - "blink": {icon: "\uf72a", color: [3]uint8{249, 169, 60}}, // blink (The Foundry Nuke) (Not supported by nerdFont) - "postcss": {icon: "\uf81b", color: [3]uint8{244, 68, 62}}, // postcss (Not supported by nerdFont) - "jinja": {icon: "\ue000", color: [3]uint8{174, 44, 42}}, // jinja (Not supported by nerdFont) - "sublime": {icon: "\ue7aa", color: [3]uint8{239, 148, 58}}, // sublime (Not supported by nerdFont) - "markojs": {icon: "\uf13b", color: [3]uint8{2, 119, 189}}, // markojs (Not supported by nerdFont) - "vscode": {icon: "\ue70c", color: [3]uint8{33, 150, 243}}, // vscode (Not supported by nerdFont) - "qsharp": {icon: "\uf292", color: [3]uint8{251, 193, 60}}, // qsharp (Not supported by nerdFont) - "vala": {icon: "\uf7ab", color: [3]uint8{149, 117, 205}}, // vala (Not supported by nerdFont) - "zig": {icon: "Z", color: [3]uint8{249, 169, 60}}, // zig (Not supported by nerdFont) - "h": {icon: "h", color: [3]uint8{2, 119, 189}}, // h (Not supported by nerdFont) - "hpp": {icon: "h", color: [3]uint8{2, 119, 189}}, // hpp (Not supported by nerdFont) - "powershell": {icon: "\ufcb5", color: [3]uint8{5, 169, 244}}, // powershell (Not supported by nerdFont) - "gradle": {icon: "\ufcc4", color: [3]uint8{29, 151, 167}}, // gradle (Not supported by nerdFont) - "arduino": {icon: "\ue255", color: [3]uint8{35, 151, 156}}, // arduino (Not supported by nerdFont) - "tex": {icon: "\uf783", color: [3]uint8{66, 165, 245}}, // tex (Not supported by nerdFont) - "graphql": {icon: "\ue284", color: [3]uint8{237, 80, 122}}, // graphql (Not supported by nerdFont) - "kotlin": {icon: "\ue70e", color: [3]uint8{139, 195, 74}}, // kotlin (Not supported by nerdFont) - "actionscript": {icon: "\ufb25", color: [3]uint8{244, 68, 62}}, // actionscript (Not supported by nerdFont) - "autohotkey": {icon: "\uf812", color: [3]uint8{76, 175, 80}}, // autohotkey (Not supported by nerdFont) - "flash": {icon: "\uf740", color: [3]uint8{198, 52, 54}}, // flash (Not supported by nerdFont) - "swc": {icon: "\ufbd3", color: [3]uint8{198, 52, 54}}, // swc (Not supported by nerdFont) - "cmake": {icon: "\uf425", color: [3]uint8{178, 178, 179}}, // cmake (Not supported by nerdFont) - "nuxt": {icon: "\ue2a6", color: [3]uint8{65, 184, 131}}, // nuxt (Not supported by nerdFont) - "ocaml": {icon: "\uf1ce", color: [3]uint8{253, 154, 62}}, // ocaml (Not supported by nerdFont) - "haxe": {icon: "\uf425", color: [3]uint8{246, 137, 61}}, // haxe (Not supported by nerdFont) - "puppet": {icon: "\uf595", color: [3]uint8{251, 193, 60}}, // puppet (Not supported by nerdFont) - "purescript": {icon: "\uf670", color: [3]uint8{66, 165, 245}}, // purescript (Not supported by nerdFont) - "merlin": {icon: "\uf136", color: [3]uint8{66, 165, 245}}, // merlin (Not supported by nerdFont) - "mjml": {icon: "\ue714", color: [3]uint8{249, 89, 63}}, // mjml (Not supported by nerdFont) - "terraform": {icon: "\ue20f", color: [3]uint8{92, 107, 192}}, // terraform (Not supported by nerdFont) - "apiblueprint": {icon: "\uf031", color: [3]uint8{66, 165, 245}}, // apiblueprint (Not supported by nerdFont) - "slim": {icon: "\uf24e", color: [3]uint8{245, 129, 61}}, // slim (Not supported by nerdFont) - "babel": {icon: "\uf5a0", color: [3]uint8{253, 217, 59}}, // babel (Not supported by nerdFont) - "codecov": {icon: "\ue37c", color: [3]uint8{237, 80, 122}}, // codecov (Not supported by nerdFont) - "protractor": {icon: "\uf288", color: [3]uint8{229, 61, 58}}, // protractor (Not supported by nerdFont) - "eslint": {icon: "\ufbf6", color: [3]uint8{121, 134, 203}}, // eslint (Not supported by nerdFont) - "mocha": {icon: "\uf6a9", color: [3]uint8{161, 136, 127}}, // mocha (Not supported by nerdFont) - "firebase": {icon: "\ue787", color: [3]uint8{251, 193, 60}}, // firebase (Not supported by nerdFont) - "stylelint": {icon: "\ufb76", color: [3]uint8{207, 216, 220}}, // stylelint (Not supported by nerdFont) - "prettier": {icon: "\uf8e2", color: [3]uint8{86, 179, 180}}, // prettier (Not supported by nerdFont) - "jest": {icon: "J", color: [3]uint8{244, 85, 62}}, // jest (Not supported by nerdFont) - "storybook": {icon: "\ufd2c", color: [3]uint8{237, 80, 122}}, // storybook (Not supported by nerdFont) - "fastlane": {icon: "\ufbff", color: [3]uint8{149, 119, 232}}, // fastlane (Not supported by nerdFont) - "helm": {icon: "\ufd31", color: [3]uint8{32, 173, 194}}, // helm (Not supported by nerdFont) - "i18n": {icon: "\uf7be", color: [3]uint8{121, 134, 203}}, // i18n (Not supported by nerdFont) - "semantic-release": {icon: "\uf70f", color: [3]uint8{245, 245, 245}}, // semantic-release (Not supported by nerdFont) - "godot": {icon: "\ufba7", color: [3]uint8{79, 195, 247}}, // godot (Not supported by nerdFont) - "godot-assets": {icon: "\ufba7", color: [3]uint8{129, 199, 132}}, // godot-assets (Not supported by nerdFont) - "vagrant": {icon: "\uf27d", color: [3]uint8{20, 101, 192}}, // vagrant (Not supported by nerdFont) - "tailwindcss": {icon: "\ufc8b", color: [3]uint8{77, 182, 172}}, // tailwindcss (Not supported by nerdFont) - "gcp": {icon: "\uf662", color: [3]uint8{70, 136, 250}}, // gcp (Not supported by nerdFont) - "opam": {icon: "\uf1ce", color: [3]uint8{255, 213, 79}}, // opam (Not supported by nerdFont) - "pascal": {icon: "\uf8da", color: [3]uint8{3, 136, 209}}, // pascal (Not supported by nerdFont) - "nuget": {icon: "\ue77f", color: [3]uint8{3, 136, 209}}, // nuget (Not supported by nerdFont) - "denizenscript": {icon: "D", color: [3]uint8{255, 213, 79}}, // denizenscript (Not supported by nerdFont) - // "riot": {icon:"\u", color:[3]uint8{255, 255, 255}}, // riot - // "autoit": {icon:"\u", color:[3]uint8{255, 255, 255}}, // autoit - // "livescript": {icon:"\u", color:[3]uint8{255, 255, 255}}, // livescript - // "reason": {icon:"\u", color:[3]uint8{255, 255, 255}}, // reason - // "bucklescript": {icon:"\u", color:[3]uint8{255, 255, 255}}, // bucklescript - // "mathematica": {icon:"\u", color:[3]uint8{255, 255, 255}}, // mathematica - // "wolframlanguage": {icon:"\u", color:[3]uint8{255, 255, 255}}, // wolframlanguage - // "nunjucks": {icon:"\u", color:[3]uint8{255, 255, 255}}, // nunjucks - // "haml": {icon:"\u", color:[3]uint8{255, 255, 255}}, // haml - // "cucumber": {icon:"\u", color:[3]uint8{255, 255, 255}}, // cucumber - // "vfl": {icon:"\u", color:[3]uint8{255, 255, 255}}, // vfl - // "kl": {icon:"\u", color:[3]uint8{255, 255, 255}}, // kl - // "coldfusion": {icon:"\u", color:[3]uint8{255, 255, 255}}, // coldfusion - // "cabal": {icon:"\u", color:[3]uint8{255, 255, 255}}, // cabal - // "restql": {icon:"\u", color:[3]uint8{255, 255, 255}}, // restql - // "kivy": {icon:"\u", color:[3]uint8{255, 255, 255}}, // kivy - // "graphcool": {icon:"\u", color:[3]uint8{255, 255, 255}}, // graphcool - // "sbt": {icon:"\u", color:[3]uint8{255, 255, 255}}, // sbt - // "flow": {icon:"\u", color:[3]uint8{255, 255, 255}}, // flow - // "bithound": {icon:"\u", color:[3]uint8{255, 255, 255}}, // bithound - // "appveyor": {icon:"\u", color:[3]uint8{255, 255, 255}}, // appveyor - // "fusebox": {icon:"\u", color:[3]uint8{255, 255, 255}}, // fusebox - // "editorconfig": {icon:"\u", color:[3]uint8{255, 255, 255}}, // editorconfig - // "watchman": {icon:"\u", color:[3]uint8{255, 255, 255}}, // watchman - // "aurelia": {icon:"\u", color:[3]uint8{255, 255, 255}}, // aurelia - // "rollup": {icon:"\u", color:[3]uint8{255, 255, 255}}, // rollup - // "hack": {icon:"\u", color:[3]uint8{255, 255, 255}}, // hack - // "apollo": {icon:"\u", color:[3]uint8{255, 255, 255}}, // apollo - // "nodemon": {icon:"\u", color:[3]uint8{255, 255, 255}}, // nodemon - // "webhint": {icon:"\u", color:[3]uint8{255, 255, 255}}, // webhint - // "browserlist": {icon:"\u", color:[3]uint8{255, 255, 255}}, // browserlist - // "crystal": {icon:"\u", color:[3]uint8{255, 255, 255}}, // crystal - // "snyk": {icon:"\u", color:[3]uint8{255, 255, 255}}, // snyk - // "drone": {icon:"\u", color:[3]uint8{255, 255, 255}}, // drone - // "cuda": {icon:"\u", color:[3]uint8{255, 255, 255}}, // cuda - // "dotjs": {icon:"\u", color:[3]uint8{255, 255, 255}}, // dotjs - // "sequelize": {icon:"\u", color:[3]uint8{255, 255, 255}}, // sequelize - // "gatsby": {icon:"\u", color:[3]uint8{255, 255, 255}}, // gatsby - // "wakatime": {icon:"\u", color:[3]uint8{255, 255, 255}}, // wakatime - // "circleci": {icon:"\u", color:[3]uint8{255, 255, 255}}, // circleci - // "cloudfoundry": {icon:"\u", color:[3]uint8{255, 255, 255}}, // cloudfoundry - // "processing": {icon:"\u", color:[3]uint8{255, 255, 255}}, // processing - // "wepy": {icon:"\u", color:[3]uint8{255, 255, 255}}, // wepy - // "hcl": {icon:"\u", color:[3]uint8{255, 255, 255}}, // hcl - // "san": {icon:"\u", color:[3]uint8{255, 255, 255}}, // san - // "wallaby": {icon:"\u", color:[3]uint8{255, 255, 255}}, // wallaby - // "stencil": {icon:"\u", color:[3]uint8{255, 255, 255}}, // stencil - // "red": {icon:"\u", color:[3]uint8{255, 255, 255}}, // red - // "webassembly": {icon:"\u", color:[3]uint8{255, 255, 255}}, // webassembly - // "foxpro": {icon:"\u", color:[3]uint8{255, 255, 255}}, // foxpro - // "jupyter": {icon:"\u", color:[3]uint8{255, 255, 255}}, // jupyter - // "ballerina": {icon:"\u", color:[3]uint8{255, 255, 255}}, // ballerina - // "racket": {icon:"\u", color:[3]uint8{255, 255, 255}}, // racket - // "bazel": {icon:"\u", color:[3]uint8{255, 255, 255}}, // bazel - // "mint": {icon:"\u", color:[3]uint8{255, 255, 255}}, // mint - // "velocity": {icon:"\u", color:[3]uint8{255, 255, 255}}, // velocity - // "prisma": {icon:"\u", color:[3]uint8{255, 255, 255}}, // prisma - // "abc": {icon:"\u", color:[3]uint8{255, 255, 255}}, // abc - // "istanbul": {icon:"\u", color:[3]uint8{255, 255, 255}}, // istanbul - // "lisp": {icon:"\u", color:[3]uint8{255, 255, 255}}, // lisp - // "buildkite": {icon:"\u", color:[3]uint8{255, 255, 255}}, // buildkite - // "netlify": {icon:"\u", color:[3]uint8{255, 255, 255}}, // netlify - // "svelte": {icon:"\u", color:[3]uint8{255, 255, 255}}, // svelte - // "nest": {icon:"\u", color:[3]uint8{255, 255, 255}}, // nest - // "percy": {icon:"\u", color:[3]uint8{255, 255, 255}}, // percy - // "gitpod": {icon:"\u", color:[3]uint8{255, 255, 255}}, // gitpod - // "advpl_prw": {icon:"\u", color:[3]uint8{255, 255, 255}}, // advpl_prw - // "advpl_ptm": {icon:"\u", color:[3]uint8{255, 255, 255}}, // advpl_ptm - // "advpl_tlpp": {icon:"\u", color:[3]uint8{255, 255, 255}}, // advpl_tlpp - // "advpl_include": {icon:"\u", color:[3]uint8{255, 255, 255}}, // advpl_include - // "tilt": {icon:"\u", color:[3]uint8{255, 255, 255}}, // tilt - // "capacitor": {icon:"\u", color:[3]uint8{255, 255, 255}}, // capacitor - // "adonis": {icon:"\u", color:[3]uint8{255, 255, 255}}, // adonis - // "forth": {icon:"\u", color:[3]uint8{255, 255, 255}}, // forth - // "uml": {icon:"\u", color:[3]uint8{255, 255, 255}}, // uml - // "meson": {icon:"\u", color:[3]uint8{255, 255, 255}}, // meson - // "buck": {icon:"\u", color:[3]uint8{255, 255, 255}}, // buck - // "sml": {icon:"\u", color:[3]uint8{255, 255, 255}}, // sml - // "nrwl": {icon:"\u", color:[3]uint8{255, 255, 255}}, // nrwl - // "imba": {icon:"\u", color:[3]uint8{255, 255, 255}}, // imba - // "drawio": {icon:"\u", color:[3]uint8{255, 255, 255}}, // drawio - // "sas": {icon:"\u", color:[3]uint8{255, 255, 255}}, // sas - // "slug": {icon:"\u", color:[3]uint8{255, 255, 255}}, // slug - - "dir-config": {icon: "\ue5fc", color: [3]uint8{32, 173, 194}}, // dir-config - "dir-controller": {icon: "\ue5fc", color: [3]uint8{255, 194, 61}}, // dir-controller - "dir-git": {icon: "\ue5fb", color: [3]uint8{250, 111, 66}}, // dir-git - "dir-github": {icon: "\ue5fd", color: [3]uint8{84, 110, 122}}, // dir-github - "dir-npm": {icon: "\ue5fa", color: [3]uint8{203, 56, 55}}, // dir-npm - "dir-include": {icon: "\uf756", color: [3]uint8{3, 155, 229}}, // dir-include - "dir-import": {icon: "\uf756", color: [3]uint8{175, 180, 43}}, // dir-import - "dir-upload": {icon: "\uf758", color: [3]uint8{250, 111, 66}}, // dir-upload - "dir-download": {icon: "\uf74c", color: [3]uint8{76, 175, 80}}, // dir-download - "dir-secure": {icon: "\uf74f", color: [3]uint8{249, 169, 60}}, // dir-secure - "dir-images": {icon: "\uf74e", color: [3]uint8{43, 150, 137}}, // dir-images - "dir-environment": {icon: "\uf74e", color: [3]uint8{102, 187, 106}}, // dir-environment -} - -// IconDef is a map of default icons if none can be found. -var IconDef = map[string]*IconInfo{ - "dir": {icon: "\uf74a", color: [3]uint8{224, 177, 77}}, - "diropen": {icon: "\ufc6e", color: [3]uint8{224, 177, 77}}, - "hiddendir": {icon: "\uf755", color: [3]uint8{224, 177, 77}}, - "exe": {icon: "\uf713", color: [3]uint8{76, 175, 80}}, - "file": {icon: "\uf723", color: [3]uint8{65, 129, 190}}, - "hiddenfile": {icon: "\ufb12", color: [3]uint8{65, 129, 190}}, -} diff --git a/icons/icons b/icons/icons new file mode 100644 index 0000000..de28166 --- /dev/null +++ b/icons/icons @@ -0,0 +1,332 @@ +# vim:ft=conf + +# These examples require Nerd Fonts or a compatible font to be used. +# See https://www.nerdfonts.com for more information. + +# file types (with matching order) +ln  # LINK +or  # ORPHAN +tw t # STICKY_OTHER_WRITABLE +ow  # OTHER_WRITABLE +st t # STICKY +di  # DIR +pi p # FIFO +so s # SOCK +bd b # BLK +cd c # CHR +su u # SETUID +sg g # SETGID +ex  # EXEC +fi  # FILE + +# file extensions (vim-devicons) +*.styl  +*.sass  +*.scss  +*.htm  +*.html  +*.slim  +*.haml  +*.ejs  +*.css  +*.less  +*.md  +*.mdx  +*.markdown  +*.rmd  +*.json  +*.webmanifest  +*.js  +*.mjs  +*.jsx  +*.rb  +*.gemspec  +*.rake  +*.php  +*.py  +*.pyc  +*.pyo  +*.pyd  +*.coffee  +*.mustache  +*.hbs  +*.conf  +*.ini  +*.yml  +*.yaml  +*.toml  +*.bat  +*.mk  +*.jpg  +*.jpeg  +*.bmp  +*.png  +*.webp  +*.gif  +*.ico  +*.twig  +*.cpp  +*.c++  +*.cxx  +*.cc  +*.cp  +*.c  +*.cs  +*.h  +*.hh  +*.hpp  +*.hxx  +*.hs  +*.lhs  +*.nix  +*.lua  +*.java  +*.sh  +*.fish  +*.bash  +*.zsh  +*.ksh  +*.csh  +*.awk  +*.ps1  +*.ml λ +*.mli λ +*.diff  +*.db  +*.sql  +*.dump  +*.clj  +*.cljc  +*.cljs  +*.edn  +*.scala  +*.go  +*.dart  +*.xul  +*.sln  +*.suo  +*.pl  +*.pm  +*.t  +*.rss  +'*.f#'  +*.fsscript  +*.fsx  +*.fs  +*.fsi  +*.rs  +*.rlib  +*.d  +*.erl  +*.hrl  +*.ex  +*.exs  +*.eex  +*.leex  +*.heex  +*.vim  +*.ai  +*.psd  +*.psb  +*.ts  +*.tsx  +*.jl  +*.pp  +*.vue ﵂ +*.elm  +*.swift  +*.xcplayground  +*.tex ﭨ +*.r ﳒ +*.rproj 鉶 +*.sol ﲹ +*.pem  + +# file names (vim-devicons) +*gruntfile.coffee  +*gruntfile.js  +*gruntfile.ls  +*gulpfile.coffee  +*gulpfile.js  +*gulpfile.ls  +*mix.lock  +*dropbox  +*.ds_store  +*.gitconfig  +*.gitignore  +*.gitattributes  +*.gitlab-ci.yml  +*.bashrc  +*.zshrc  +*.zshenv  +*.zprofile  +*.vimrc  +*.gvimrc  +*_vimrc  +*_gvimrc  +*.bashprofile  +*favicon.ico  +*license  +*node_modules  +*react.jsx  +*procfile  +*dockerfile  +*docker-compose.yml  +*rakefile  +*config.ru  +*gemfile  +*makefile  +*cmakelists.txt  +*robots.txt ﮧ + +# file names (case-sensitive adaptations) +*Gruntfile.coffee  +*Gruntfile.js  +*Gruntfile.ls  +*Gulpfile.coffee  +*Gulpfile.js  +*Gulpfile.ls  +*Dropbox  +*.DS_Store  +*LICENSE  +*React.jsx  +*Procfile  +*Dockerfile  +*Docker-compose.yml  +*Rakefile  +*Gemfile  +*Makefile  +*CMakeLists.txt  + +# file patterns (file name adaptations) +*jquery.min.js  +*angular.min.js  +*backbone.min.js  +*require.min.js  +*materialize.min.js  +*materialize.min.css  +*mootools.min.js  +*vimrc  +Vagrantfile  + +# archives or compressed (extensions from dircolors defaults) +*.tar  +*.tgz  +*.arc  +*.arj  +*.taz  +*.lha  +*.lz4  +*.lzh  +*.lzma  +*.tlz  +*.txz  +*.tzo  +*.t7z  +*.zip  +*.z  +*.dz  +*.gz  +*.lrz  +*.lz  +*.lzo  +*.xz  +*.zst  +*.tzst  +*.bz2  +*.bz  +*.tbz  +*.tbz2  +*.tz  +*.deb  +*.rpm  +*.jar  +*.war  +*.ear  +*.sar  +*.rar  +*.alz  +*.ace  +*.zoo  +*.cpio  +*.7z  +*.rz  +*.cab  +*.wim  +*.swm  +*.dwm  +*.esd  + +# image formats (extensions from dircolors defaults) +*.jpg  +*.jpeg  +*.mjpg  +*.mjpeg  +*.gif  +*.bmp  +*.pbm  +*.pgm  +*.ppm  +*.tga  +*.xbm  +*.xpm  +*.tif  +*.tiff  +*.png  +*.svg  +*.svgz  +*.mng  +*.pcx  +*.xcf  +*.xwd  +*.yuv  +*.cgm  +*.emf  + +# Video formats +*.webm  +*.mov  +*.mpg  +*.mpeg  +*.m2v  +*.mkv  +*.ogm  +*.mp4  +*.m4v  +*.mp4v  +*.vob  +*.qt  +*.nuv  +*.wmv  +*.asf  +*.rm  +*.rmvb  +*.flc  +*.avi  +*.fli  +*.flv  +*.gl  +*.dl  +*.ogv  + +# audio formats +*.aac  +*.au  +*.flac  +*.m4a  +*.mid  +*.midi  +*.mka  +*.mp3  +*.mpc  +*.ogg  +*.ra  +*.wav  +*.oga  +*.opus  +*.spx  +*.xspf  +*.ogx  + +# other formats +*.pdf  \ No newline at end of file diff --git a/icons/icons.go b/icons/icons.go index e4550eb..db396f9 100644 --- a/icons/icons.go +++ b/icons/icons.go @@ -1,87 +1,178 @@ -// Package icons provides a set of unicode icons -// based on type and extension. package icons import ( + "bufio" + "embed" + "fmt" + "io" + "log" "os" + "os/user" + "path/filepath" "strings" + "unicode" ) -// GetIndicator returns the indicator for the given file. -func GetIndicator(modebit os.FileMode) (i string) { - switch { - case modebit&os.ModeDir > 0: - i = "/" - case modebit&os.ModeNamedPipe > 0: - i = "|" - case modebit&os.ModeSymlink > 0: - i = "@" - case modebit&os.ModeSocket > 0: - i = "=" - case modebit&1000000 > 0: - i = "*" - } +type iconMap map[string]string - return i +var Icons iconMap + +func ParseIcons() { + Icons = make(iconMap) + Icons.parse() } -// GetIcon returns the icon based on its name, extension and indicator. -func GetIcon(name, ext, indicator string) (icon, color string) { - var i *IconInfo - var ok bool - const DOT = '.' - - switch indicator { - case "/": - i, ok = IconDir[strings.ToLower(name+ext)] - if ok { - break - } - if len(name) == 0 || DOT == name[0] { - i = IconDef["hiddendir"] - break +//go:embed icons +var f embed.FS + +func (im iconMap) parse() { + icons, _ := f.Open("icons") + pairs, err := readPairs(icons) + if err != nil { + log.Printf("reading icons file: %s", err) + return + } + for _, pair := range pairs { + key, val := pair[0], pair[1] + key = replaceTilde(key) + if filepath.IsAbs(key) { + key = filepath.Clean(key) } - i = IconDef["dir"] - default: - i, ok = IconFileName[strings.ToLower(name+ext)] - if ok { - break + im[key] = val + } +} + +func (im iconMap) GetIcon(f os.FileInfo) string { + if f.IsDir() { + if val, ok := im[f.Name()+"/"]; ok { + return val } + } - if ext == ".go" && strings.HasSuffix(name, "_test") { - i = IconSet["go-test"] + var key string - break + switch { + case f.IsDir() && f.Mode()&os.ModeSticky != 0 && f.Mode()&0002 != 0: + key = "tw" + case f.IsDir() && f.Mode()&0002 != 0: + key = "ow" + case f.IsDir() && f.Mode()&os.ModeSticky != 0: + key = "st" + case f.IsDir(): + key = "di" + case f.Mode()&os.ModeNamedPipe != 0: + key = "pi" + case f.Mode()&os.ModeSocket != 0: + key = "so" + case f.Mode()&os.ModeDevice != 0: + key = "bd" + case f.Mode()&os.ModeCharDevice != 0: + key = "cd" + case f.Mode()&os.ModeSetuid != 0: + key = "su" + case f.Mode()&os.ModeSetgid != 0: + key = "sg" + } + if val, ok := im[key]; ok { + return val + } + if val, ok := im[f.Name()+"*"]; ok { + return val + } + if val, ok := im["*"+f.Name()]; ok { + return val + } + if val, ok := im[filepath.Base(f.Name())+".*"]; ok { + return val + } + ext := filepath.Ext(f.Name()) + if val, ok := im["*"+strings.ToLower(ext)]; ok { + return val + } + if f.Mode()&0111 != 0 { + if val, ok := im["ex"]; ok { + return val } + } + if val, ok := im["fi"]; ok { + return val + } + return " " +} - t := strings.Split(name, ".") - if len(t) > 1 && t[0] != "" { - i, ok = IconSubExt[strings.ToLower(t[len(t)-1]+ext)] - if ok { +func replaceTilde(s string) string { + u, err := user.Current() + if err != nil { + log.Printf("user: %s", err) + } + if strings.HasPrefix(s, "~") { + s = strings.Replace(s, "~", u.HomeDir, 1) + } + return s +} + +// This function reads whitespace separated string pairs at each line. Single +// or double quotes can be used to escape whitespaces. Hash characters can be +// used to add a comment until the end of line. Leading and trailing space is +// trimmed. Empty lines are skipped. +func readPairs(r io.Reader) ([][]string, error) { + var pairs [][]string + s := bufio.NewScanner(r) + for s.Scan() { + line := s.Text() + + squote, dquote := false, false + for i := 0; i < len(line); i++ { + if line[i] == '\'' && !dquote { + squote = !squote + } else if line[i] == '"' && !squote { + dquote = !dquote + } + if !squote && !dquote && line[i] == '#' { + line = line[:i] break } } - i, ok = IconExt[strings.ToLower(strings.TrimPrefix(ext, "."))] - if ok { - break + line = strings.TrimSpace(line) + + if line == "" { + continue } - if len(name) == 0 || DOT == name[0] { - i = IconDef["hiddenfile"] + squote, dquote = false, false + pair := strings.FieldsFunc(line, func(r rune) bool { + if r == '\'' && !dquote { + squote = !squote + } else if r == '"' && !squote { + dquote = !dquote + } + return !squote && !dquote && unicode.IsSpace(r) + }) - break + if len(pair) != 2 { + return nil, fmt.Errorf("expected pair but found: %s", s.Text()) } - i = IconDef["file"] - } - if indicator == "*" { - if i.GetGlyph() == "\uf723" { - i = IconDef["exe"] + for i := 0; i < len(pair); i++ { + squote, dquote = false, false + buf := make([]rune, 0, len(pair[i])) + for _, r := range pair[i] { + if r == '\'' && !dquote { + squote = !squote + continue + } + if r == '"' && !squote { + dquote = !dquote + continue + } + buf = append(buf, r) + } + pair[i] = string(buf) } - i.MakeExe() + pairs = append(pairs, pair) } - return i.GetGlyph(), i.GetColor(1) + return pairs, nil } diff --git a/icons/sub_extensions.go b/icons/sub_extensions.go deleted file mode 100644 index a293422..0000000 --- a/icons/sub_extensions.go +++ /dev/null @@ -1,48 +0,0 @@ -package icons - -// IconSubExt is a map to get the icon based on its subdirectory extension. -var IconSubExt = map[string]*IconInfo{ - "routing.ts": IconSet["routing"], - "routing.tsx": IconSet["routing"], - "routing.js": IconSet["routing"], - "routing.jsx": IconSet["routing"], - "routes.ts": IconSet["routing"], - "routes.tsx": IconSet["routing"], - "routes.js": IconSet["routing"], - "routes.jsx": IconSet["routing"], - "sln.dotsettings": IconSet["settings"], - "d.ts": IconSet["typescript-def"], - "vcxitems.filters": IconSet["visualstudio"], - "vcxproj.filters": IconSet["visualstudio"], - "js.map": IconSet["javascript-map"], - "mjs.map": IconSet["javascript-map"], - "css.map": IconSet["css-map"], - "spec.ts": IconSet["test-ts"], - "e2e-spec.ts": IconSet["test-ts"], - "test.ts": IconSet["test-ts"], - "ts.snap": IconSet["test-ts"], - "spec.tsx": IconSet["test-jsx"], - "test.tsx": IconSet["test-jsx"], - "tsx.snap": IconSet["test-jsx"], - "spec.jsx": IconSet["test-jsx"], - "test.jsx": IconSet["test-jsx"], - "jsx.snap": IconSet["test-jsx"], - "spec.js": IconSet["test-js"], - "e2e-spec.js": IconSet["test-js"], - "test.js": IconSet["test-js"], - "js.snap": IconSet["test-js"], - "tf.json": IconSet["terraform"], - "blade.php": IconSet["laravel"], - "inky.php": IconSet["laravel"], - "gitlab-ci.yml": IconSet["gitlab"], - "stories.js": IconSet["storybook"], - "stories.jsx": IconSet["storybook"], - "story.js": IconSet["storybook"], - "story.jsx": IconSet["storybook"], - "stories.ts": IconSet["storybook"], - "stories.tsx": IconSet["storybook"], - "story.ts": IconSet["storybook"], - "story.tsx": IconSet["storybook"], - "azure-pipelines.yml": IconSet["azure-pipelines"], - "azure-pipelines.yaml": IconSet["azure-pipelines"], -} diff --git a/internal/tui/model.go b/internal/tui/model.go index 9a01180..abfba4c 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -5,6 +5,7 @@ import ( "github.com/mistakenelf/fm/code" "github.com/mistakenelf/fm/filetree" "github.com/mistakenelf/fm/help" + "github.com/mistakenelf/fm/icons" "github.com/mistakenelf/fm/image" "github.com/mistakenelf/fm/internal/theme" "github.com/mistakenelf/fm/markdown" @@ -56,6 +57,10 @@ func New(cfg Config) model { filetreeModel.SetSelectionPath(cfg.SelectionPath) filetreeModel.SetShowIcons(cfg.ShowIcons) + if cfg.ShowIcons { + icons.ParseIcons() + } + codeModel := code.New() codeModel.SetSyntaxTheme(cfg.SyntaxTheme) codeModel.SetViewportDisabled(true) From 8d4f4b9e8c3199b5e742953cf281224603b40b92 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Mon, 18 Mar 2024 13:18:26 -0400 Subject: [PATCH 23/31] chore: cleanup --- code/code.go | 25 ++++++----- filesystem/filesystem.go | 6 ++- filetree/commands.go | 5 ++- filetree/update.go | 6 ++- help/help.go | 20 ++++++++- icons/icons.go | 5 +++ image/image.go | 96 ++++++++++++++++++++++++---------------- internal/tui/commands.go | 78 ++++++++++++++++++++++++++++++++ internal/tui/helpers.go | 53 ---------------------- internal/tui/keys.go | 4 ++ internal/tui/model.go | 55 +++++++++++++---------- internal/tui/update.go | 8 ++-- internal/tui/view.go | 16 +++++++ markdown/markdown.go | 68 +++++++++++++++++++--------- pdf/pdf.go | 62 ++++++++++++++++++-------- statusbar/statusbar.go | 19 +++++--- 16 files changed, 347 insertions(+), 179 deletions(-) create mode 100644 internal/tui/commands.go diff --git a/code/code.go b/code/code.go index 2d7d841..d56789b 100644 --- a/code/code.go +++ b/code/code.go @@ -59,8 +59,8 @@ func readFileContentCmd(fileName, syntaxTheme string) tea.Cmd { } // NewStatusMessage sets a new status message, which will show for a limited -// amount of time. Note that this also returns a command. -func (m *Model) NewStatusMessage(s string) tea.Cmd { +// amount of time. +func (m *Model) NewStatusMessageCmd(s string) tea.Cmd { m.StatusMessage = s if m.statusMessageTimer != nil { m.statusMessageTimer.Stop() @@ -75,6 +75,13 @@ func (m *Model) NewStatusMessage(s string) tea.Cmd { } } +// SetFileName sets current file to highlight. +func (m *Model) SetFileNameCmd(filename string) tea.Cmd { + m.Filename = filename + + return readFileContentCmd(filename, m.SyntaxTheme) +} + // New creates a new instance of code. func New() Model { viewPort := viewport.New(0, 0) @@ -92,13 +99,6 @@ func (m Model) Init() tea.Cmd { return nil } -// SetFileName sets current file to highlight. -func (m *Model) SetFileName(filename string) tea.Cmd { - m.Filename = filename - - return readFileContentCmd(filename, m.SyntaxTheme) -} - // SetSyntaxTheme sets the syntax theme of the rendered code. func (m *Model) SetSyntaxTheme(theme string) { m.SyntaxTheme = theme @@ -141,7 +141,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.StatusMessage = "" case errorMsg: m.Filename = "" - cmds = append(cmds, m.NewStatusMessage(lipgloss.NewStyle().Foreground(lipgloss.Color("#cc241d")).Bold(true).Render(string(msg)))) + cmds = append(cmds, m.NewStatusMessageCmd( + lipgloss.NewStyle(). + Foreground(lipgloss.Color("#cc241d")). + Bold(true). + Render(string(msg)), + )) } if !m.ViewportDisabled { diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go index 03a309c..b8a3cbb 100644 --- a/filesystem/filesystem.go +++ b/filesystem/filesystem.go @@ -189,6 +189,7 @@ func Zip(name string) error { fileExtension := filepath.Ext(name) splitFileName := strings.Split(name, "/") fileName := splitFileName[len(splitFileName)-1] + switch { case strings.HasPrefix(fileName, ".") && fileExtension != "" && fileExtension == fileName: output = fmt.Sprintf("%s_%d.zip", fileName, time.Now().Unix()) @@ -233,6 +234,7 @@ func Zip(name string) error { relPath := strings.TrimPrefix(filePath, name) zipFile, err := zipWriter.Create(relPath) + if err != nil { return errors.Unwrap(err) } @@ -393,6 +395,7 @@ func CopyFile(name string) error { fileExtension := filepath.Ext(name) splitFileName := strings.Split(name, "/") fileName := splitFileName[len(splitFileName)-1] + switch { case strings.HasPrefix(fileName, ".") && fileExtension != "" && fileExtension == fileName: output = fmt.Sprintf("%s_%d", fileName, time.Now().Unix()) @@ -452,6 +455,8 @@ func CopyDirectory(name string) error { // GetDirectoryItemSize calculates the size of a directory or file. func GetDirectoryItemSize(path string) (int64, error) { + var size int64 + curFile, err := os.Stat(path) if err != nil { return 0, errors.Unwrap(err) @@ -461,7 +466,6 @@ func GetDirectoryItemSize(path string) (int64, error) { return curFile.Size(), nil } - var size int64 err = filepath.WalkDir(path, func(path string, entry os.DirEntry, err error) error { if err != nil { return errors.Unwrap(err) diff --git a/filetree/commands.go b/filetree/commands.go index 604707a..3b88c4c 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -206,8 +206,8 @@ func writeSelectionPathCmd(selectionPath, filePath string) tea.Cmd { } // NewStatusMessage sets a new status message, which will show for a limited -// amount of time. Note that this also returns a command. -func (m *Model) NewStatusMessage(s string) tea.Cmd { +// amount of time. +func (m *Model) NewStatusMessageCmd(s string) tea.Cmd { m.StatusMessage = s if m.statusMessageTimer != nil { @@ -229,6 +229,7 @@ func openEditorCmd(file string) tea.Cmd { } c := exec.Command(editor, file) + return tea.ExecProcess(c, func(err error) tea.Msg { return editorFinishedMsg{err} }) diff --git a/filetree/update.go b/filetree/update.go index a0f19a6..6d0f7e5 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -26,7 +26,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, tea.Quit } case errorMsg: - cmds = append(cmds, m.NewStatusMessage( + cmds = append(cmds, m.NewStatusMessageCmd( lipgloss.NewStyle(). Foreground(lipgloss.Color("#cc241d")). Bold(true). @@ -34,7 +34,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case statusMessageTimeoutMsg: m.StatusMessage = "" case copyToClipboardMsg: - cmds = append(cmds, m.NewStatusMessage( + cmds = append(cmds, m.NewStatusMessageCmd( lipgloss.NewStyle(). Bold(true). Render(string(msg)))) @@ -120,9 +120,11 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } m.Cursor += m.height + if m.Cursor >= len(m.files) { m.Cursor = len(m.files) - 1 } + m.min += m.height m.max += m.height diff --git a/help/help.go b/help/help.go index 9021064..4dc1b98 100644 --- a/help/help.go +++ b/help/help.go @@ -92,7 +92,15 @@ func (m *Model) SetSize(w, h int) { m.Viewport.Width = w m.Viewport.Height = h - m.Viewport.SetContent(generateHelpScreen(m.Title, m.TitleColor, m.Entries, m.Viewport.Width, m.Viewport.Height)) + m.Viewport.SetContent( + generateHelpScreen( + m.Title, + m.TitleColor, + m.Entries, + m.Viewport.Width, + m.Viewport.Height, + ), + ) } // SetViewportDisabled toggles the state of the viewport. @@ -109,7 +117,15 @@ func (m *Model) GotoTop() { func (m *Model) SetTitleColor(color TitleColor) { m.TitleColor = color - m.Viewport.SetContent(generateHelpScreen(m.Title, m.TitleColor, m.Entries, m.Viewport.Width, m.Viewport.Height)) + m.Viewport.SetContent( + generateHelpScreen( + m.Title, + m.TitleColor, + m.Entries, + m.Viewport.Width, + m.Viewport.Height, + ), + ) } // Update handles UI interactions with the help bubble. diff --git a/icons/icons.go b/icons/icons.go index db396f9..9d6bac4 100644 --- a/icons/icons.go +++ b/icons/icons.go @@ -117,7 +117,9 @@ func replaceTilde(s string) string { // trimmed. Empty lines are skipped. func readPairs(r io.Reader) ([][]string, error) { var pairs [][]string + s := bufio.NewScanner(r) + for s.Scan() { line := s.Text() @@ -162,12 +164,15 @@ func readPairs(r io.Reader) ([][]string, error) { squote = !squote continue } + if r == '"' && !squote { dquote = !dquote continue } + buf = append(buf, r) } + pair[i] = string(buf) } diff --git a/image/image.go b/image/image.go index 0736c11..54d5986 100644 --- a/image/image.go +++ b/image/image.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" @@ -16,7 +17,19 @@ import ( ) type convertImageToStringMsg string -type errorMsg error +type errorMsg string +type statusMessageTimeoutMsg struct{} + +// Model represents the properties of a image bubble. +type Model struct { + Viewport viewport.Model + ViewportDisabled bool + FileName string + ImageString string + StatusMessage string + StatusMessageLifetime time.Duration + statusMessageTimer *time.Timer +} // ToString converts an image to a string representation of an image. func ToString(width int, img image.Image) string { @@ -46,16 +59,33 @@ func ToString(width int, img image.Image) string { return str.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) + + return func() tea.Msg { + <-m.statusMessageTimer.C + return statusMessageTimeoutMsg{} + } +} + func convertImageToStringCmd(width int, filename string) tea.Cmd { return func() tea.Msg { imageContent, err := os.Open(filepath.Clean(filename)) if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } img, _, err := image.Decode(imageContent) if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } imageString := ToString(width, img) @@ -64,38 +94,15 @@ func convertImageToStringCmd(width int, filename string) tea.Cmd { } } -// Model represents the properties of a image bubble. -type Model struct { - Viewport viewport.Model - ViewportDisabled bool - FileName string - ImageString string -} - -// New creates a new instance of an image. -func New() Model { - viewPort := viewport.New(0, 0) - - return Model{ - Viewport: viewPort, - ViewportDisabled: false, - } -} - -// Init initializes the image bubble. -func (m Model) Init() tea.Cmd { - return nil -} - -// SetFileName sets the image file and convers it to a string. -func (m *Model) SetFileName(filename string) tea.Cmd { +// SetFileName sets the image file and converts it to a string. +func (m *Model) SetFileNameCmd(filename string) tea.Cmd { m.FileName = filename return convertImageToStringCmd(m.Viewport.Width, filename) } // SetSize sets the size of the bubble. -func (m *Model) SetSize(w, h int) tea.Cmd { +func (m *Model) SetSizeCmd(w, h int) tea.Cmd { m.Viewport.Width = w m.Viewport.Height = h @@ -106,6 +113,22 @@ func (m *Model) SetSize(w, h int) tea.Cmd { return nil } +// New creates a new instance of an image. +func New() Model { + viewPort := viewport.New(0, 0) + + return Model{ + Viewport: viewPort, + ViewportDisabled: false, + StatusMessageLifetime: time.Second, + } +} + +// Init initializes the image bubble. +func (m Model) Init() tea.Cmd { + return nil +} + // SetViewportDisabled toggles the state of the viewport. func (m *Model) SetViewportDisabled(disabled bool) { m.ViewportDisabled = disabled @@ -134,15 +157,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, nil case errorMsg: - m.FileName = "" - m.ImageString = lipgloss.NewStyle(). - Width(m.Viewport.Width). - Height(m.Viewport.Height). - Render("Error: " + msg.Error()) - - m.Viewport.SetContent(m.ImageString) - - return m, nil + cmds = append(cmds, m.NewStatusMessageCmd( + lipgloss.NewStyle(). + Foreground(lipgloss.Color("#cc241d")). + Bold(true). + Render(string(msg)), + )) } if !m.ViewportDisabled { diff --git a/internal/tui/commands.go b/internal/tui/commands.go new file mode 100644 index 0000000..e3179e2 --- /dev/null +++ b/internal/tui/commands.go @@ -0,0 +1,78 @@ +package tui + +import ( + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +var forbiddenExtensions = []string{ + ".FCStd", + ".gif", + ".zip", + ".rar", + ".webm", + ".sqlite", + ".sqlite-shm", + ".sqlite-wal", + ".DS_Store", + ".db", + ".data", + ".plist", + ".webp", + ".img", +} + +type statusMessageTimeoutMsg struct{} + +func (m *model) openFileCmd() tea.Cmd { + selectedFile := m.filetree.GetSelectedItem() + + if !selectedFile.IsDirectory { + m.resetViewports() + + switch { + case selectedFile.Extension == ".png" || selectedFile.Extension == ".jpg" || selectedFile.Extension == ".jpeg": + m.state = showImageState + + return m.image.SetFileNameCmd(selectedFile.Name) + case selectedFile.Extension == ".md" && m.config.PrettyMarkdown: + m.state = showMarkdownState + + return m.markdown.SetFileNameCmd(selectedFile.Name) + case selectedFile.Extension == ".pdf": + m.state = showPdfState + + return m.pdf.SetFileNameCmd(selectedFile.Name) + case contains(forbiddenExtensions, selectedFile.Extension): + return m.newStatusMessageCmd(lipgloss.NewStyle(). + Foreground(lipgloss.Color("#cc241d")). + Bold(true). + Render("Selected file type is not supported")) + default: + m.state = showCodeState + + return m.code.SetFileNameCmd(selectedFile.Name) + } + } + + return nil +} + +// 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) + + return func() tea.Msg { + <-m.statusMessageTimer.C + return statusMessageTimeoutMsg{} + } +} diff --git a/internal/tui/helpers.go b/internal/tui/helpers.go index a2005d9..cd67848 100644 --- a/internal/tui/helpers.go +++ b/internal/tui/helpers.go @@ -1,26 +1,5 @@ package tui -import ( - tea "github.com/charmbracelet/bubbletea" -) - -var forbiddenExtensions = []string{ - ".FCStd", - ".gif", - ".zip", - ".rar", - ".webm", - ".sqlite", - ".sqlite-shm", - ".sqlite-wal", - ".DS_Store", - ".db", - ".data", - ".plist", - ".webp", - ".img", -} - func contains(s []string, str string) bool { for _, v := range s { if v == str { @@ -31,38 +10,6 @@ func contains(s []string, str string) bool { return false } -func (m *model) openFile() tea.Cmd { - selectedFile := m.filetree.GetSelectedItem() - - if !selectedFile.IsDirectory { - m.resetViewports() - - switch { - case selectedFile.Extension == ".png" || selectedFile.Extension == ".jpg" || selectedFile.Extension == ".jpeg": - m.state = showImageState - - return m.image.SetFileName(selectedFile.Name) - case selectedFile.Extension == ".md" && m.config.PrettyMarkdown: - m.state = showMarkdownState - - return m.markdown.SetFileName(selectedFile.Name) - case selectedFile.Extension == ".pdf": - m.state = showPdfState - - return m.pdf.SetFileName(selectedFile.Name) - case contains(forbiddenExtensions, selectedFile.Extension): - //TODO: Display an error status message in the statusbar. - return nil - default: - m.state = showCodeState - - return m.code.SetFileName(selectedFile.Name) - } - } - - return nil -} - func (m *model) disableAllViewports() { m.code.SetViewportDisabled(true) m.pdf.SetViewportDisabled(true) diff --git a/internal/tui/keys.go b/internal/tui/keys.go index 3ac5562..cb86584 100644 --- a/internal/tui/keys.go +++ b/internal/tui/keys.go @@ -9,6 +9,8 @@ type keyMap struct { ResetState key.Binding ShowTextInput key.Binding SubmitTextInput key.Binding + GotoTop key.Binding + GotoBottom key.Binding } func defaultKeyMap() keyMap { @@ -19,5 +21,7 @@ func defaultKeyMap() keyMap { ResetState: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "reset state")), ShowTextInput: key.NewBinding(key.WithKeys("N", "M"), key.WithHelp("N, M", "show text input")), SubmitTextInput: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit text input")), + GotoTop: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "go to top")), + GotoBottom: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "go to bottom")), } } diff --git a/internal/tui/model.go b/internal/tui/model.go index abfba4c..56a1dff 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -1,7 +1,10 @@ package tui import ( + "time" + "github.com/charmbracelet/bubbles/textinput" + "github.com/mistakenelf/fm/code" "github.com/mistakenelf/fm/filetree" "github.com/mistakenelf/fm/help" @@ -35,19 +38,22 @@ type Config struct { } type model struct { - filetree filetree.Model - help help.Model - code code.Model - image image.Model - markdown markdown.Model - pdf pdf.Model - statusbar statusbar.Model - state sessionState - keyMap keyMap - activePane int - config Config - showTextInput bool - textinput textinput.Model + filetree filetree.Model + help help.Model + code code.Model + image image.Model + markdown markdown.Model + pdf pdf.Model + statusbar statusbar.Model + state sessionState + keyMap keyMap + activePane int + config Config + showTextInput bool + textinput textinput.Model + statusMessage string + statusMessageLifetime time.Duration + statusMessageTimer *time.Timer } // New creates a new instance of the UI. @@ -134,16 +140,17 @@ func New(cfg Config) model { textInput := textinput.New() return model{ - filetree: filetreeModel, - help: helpModel, - code: codeModel, - image: imageModel, - markdown: markdownModel, - pdf: pdfModel, - statusbar: statusbarModel, - config: cfg, - keyMap: defaultKeyMap(), - showTextInput: false, - textinput: textInput, + filetree: filetreeModel, + help: helpModel, + code: codeModel, + image: imageModel, + markdown: markdownModel, + pdf: pdfModel, + statusbar: statusbarModel, + config: cfg, + keyMap: defaultKeyMap(), + showTextInput: false, + textinput: textInput, + statusMessageLifetime: time.Second, } } diff --git a/internal/tui/update.go b/internal/tui/update.go index 7afb1fd..2651e2a 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -15,12 +15,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ) switch msg := msg.(type) { + case statusMessageTimeoutMsg: + m.statusMessage = "" case tea.WindowSizeMsg: halfSize := msg.Width / 2 bubbleHeight := msg.Height - statusbar.Height - cmds = append(cmds, m.image.SetSize(halfSize, bubbleHeight)) - cmds = append(cmds, m.markdown.SetSize(halfSize, bubbleHeight)) + cmds = append(cmds, m.image.SetSizeCmd(halfSize, bubbleHeight)) + cmds = append(cmds, m.markdown.SetSizeCmd(halfSize, bubbleHeight)) m.filetree.SetSize(halfSize, bubbleHeight) m.help.SetSize(halfSize, bubbleHeight) @@ -35,7 +37,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit case key.Matches(msg, m.keyMap.OpenFile): if !m.showTextInput { - cmds = append(cmds, m.openFile()) + cmds = append(cmds, m.openFileCmd()) } case key.Matches(msg, m.keyMap.ResetState): m.state = idleState diff --git a/internal/tui/view.go b/internal/tui/view.go index c46e069..138cca4 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -35,6 +35,22 @@ func (m model) View() string { statusMessage = m.code.StatusMessage } + if m.markdown.StatusMessage != "" { + statusMessage = m.markdown.StatusMessage + } + + if m.pdf.StatusMessage != "" { + statusMessage = m.pdf.StatusMessage + } + + if m.image.StatusMessage != "" { + statusMessage = m.image.StatusMessage + } + + if m.statusMessage != "" { + statusMessage = m.statusMessage + } + if m.showTextInput { statusMessage = m.textinput.View() } diff --git a/markdown/markdown.go b/markdown/markdown.go index c8d2f34..cdef444 100644 --- a/markdown/markdown.go +++ b/markdown/markdown.go @@ -4,6 +4,7 @@ package markdown import ( "errors" + "time" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" @@ -14,13 +15,17 @@ import ( ) type renderMarkdownMsg string -type errorMsg error +type errorMsg string +type statusMessageTimeoutMsg struct{} // Model represents the properties of a markdown bubble. type Model struct { - Viewport viewport.Model - ViewportDisabled bool - FileName string + Viewport viewport.Model + ViewportDisabled bool + FileName string + StatusMessage string + StatusMessageLifetime time.Duration + statusMessageTimer *time.Timer } // RenderMarkdown renders the markdown content with glamour. @@ -48,43 +53,45 @@ func renderMarkdownCmd(width int, filename string) tea.Cmd { return func() tea.Msg { content, err := filesystem.ReadFileContent(filename) if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } markdownContent, err := RenderMarkdown(width, content) if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } return renderMarkdownMsg(markdownContent) } } -// New creates a new instance of markdown. -func New() Model { - viewPort := viewport.New(0, 0) +// 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 - return Model{ - Viewport: viewPort, - ViewportDisabled: false, + if m.statusMessageTimer != nil { + m.statusMessageTimer.Stop() } -} -// Init initializes the code bubble. -func (m Model) Init() tea.Cmd { - return nil + m.statusMessageTimer = time.NewTimer(m.StatusMessageLifetime) + + return func() tea.Msg { + <-m.statusMessageTimer.C + return statusMessageTimeoutMsg{} + } } // SetFileName sets current file to render, this // returns a cmd which will render the text. -func (m *Model) SetFileName(filename string) tea.Cmd { +func (m *Model) SetFileNameCmd(filename string) tea.Cmd { m.FileName = filename return renderMarkdownCmd(m.Viewport.Width, filename) } // SetSize sets the size of the bubble. -func (m *Model) SetSize(w, h int) tea.Cmd { +func (m *Model) SetSizeCmd(w, h int) tea.Cmd { m.Viewport.Width = w m.Viewport.Height = h @@ -95,6 +102,22 @@ func (m *Model) SetSize(w, h int) tea.Cmd { return nil } +// New creates a new instance of markdown. +func New() Model { + viewPort := viewport.New(0, 0) + + return Model{ + Viewport: viewPort, + ViewportDisabled: false, + StatusMessageLifetime: time.Second, + } +} + +// Init initializes the code bubble. +func (m Model) Init() tea.Cmd { + return nil +} + // GotoTop jumps to the top of the viewport. func (m *Model) GotoTop() { m.Viewport.GotoTop() @@ -124,9 +147,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, nil case errorMsg: m.FileName = "" - m.Viewport.SetContent(msg.Error()) - - return m, nil + cmds = append(cmds, m.NewStatusMessageCmd( + lipgloss.NewStyle(). + Foreground(lipgloss.Color("#cc241d")). + Bold(true). + Render(string(msg)), + )) } if !m.ViewportDisabled { diff --git a/pdf/pdf.go b/pdf/pdf.go index 146a85e..793e33c 100644 --- a/pdf/pdf.go +++ b/pdf/pdf.go @@ -5,6 +5,7 @@ package pdf import ( "bytes" "errors" + "time" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" @@ -13,13 +14,17 @@ import ( ) type renderPDFMsg string -type errorMsg error +type errorMsg string +type statusMessageTimeoutMsg struct{} // Model represents the properties of a pdf bubble. type Model struct { - Viewport viewport.Model - ViewportDisabled bool - FileName string + Viewport viewport.Model + ViewportDisabled bool + FileName string + StatusMessage string + StatusMessageLifetime time.Duration + statusMessageTimer *time.Timer } // ReadPDF reads the content of a PDF and returns it as a string. @@ -54,20 +59,46 @@ func renderPDFCmd(filename string) tea.Cmd { return func() tea.Msg { pdfContent, err := ReadPDF(filename) if err != nil { - return errorMsg(err) + return errorMsg(err.Error()) } return renderPDFMsg(pdfContent) } } +// 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) + + return func() tea.Msg { + <-m.statusMessageTimer.C + return statusMessageTimeoutMsg{} + } +} + +// SetFileName sets current file to render, this +// returns a cmd which will render the pdf. +func (m *Model) SetFileNameCmd(filename string) tea.Cmd { + m.FileName = filename + + return renderPDFCmd(filename) +} + // New creates a new instance of a PDF. func New() Model { viewPort := viewport.New(0, 0) return Model{ - Viewport: viewPort, - ViewportDisabled: false, + Viewport: viewPort, + ViewportDisabled: false, + StatusMessageLifetime: time.Second, } } @@ -76,14 +107,6 @@ func (m Model) Init() tea.Cmd { return nil } -// SetFileName sets current file to render, this -// returns a cmd which will render the pdf. -func (m *Model) SetFileName(filename string) tea.Cmd { - m.FileName = filename - - return renderPDFCmd(filename) -} - // SetSize sets the size of the bubble. func (m *Model) SetSize(w, h int) { m.Viewport.Width = w @@ -119,9 +142,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, nil case errorMsg: m.FileName = "" - m.Viewport.SetContent(msg.Error()) - - return m, nil + cmds = append(cmds, m.NewStatusMessageCmd( + lipgloss.NewStyle(). + Foreground(lipgloss.Color("#cc241d")). + Bold(true). + Render(string(msg)), + )) } if !m.ViewportDisabled { diff --git a/statusbar/statusbar.go b/statusbar/statusbar.go index 92c7b4f..bf9ac5b 100644 --- a/statusbar/statusbar.go +++ b/statusbar/statusbar.go @@ -33,7 +33,12 @@ type Model struct { } // New creates a new instance of the statusbar. -func New(firstColumnColors, secondColumnColors, thirdColumnColors, fourthColumnColors ColorConfig) Model { +func New( + firstColumnColors, + secondColumnColors, + thirdColumnColors, + fourthColumnColors ColorConfig, +) Model { return Model{ FirstColumnColors: firstColumnColors, SecondColumnColors: secondColumnColors, @@ -49,9 +54,8 @@ func (m *Model) SetSize(width int) { // Update updates the UI of the statusbar. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.SetSize(msg.Width) + if windowSizeMsg, ok := msg.(tea.WindowSizeMsg); ok { + m.SetSize(windowSizeMsg.Width) } return m, nil @@ -66,7 +70,12 @@ func (m *Model) SetContent(firstColumn, secondColumn, thirdColumn, fourthColumn } // SetColors sets the colors of the 4 columns. -func (m *Model) SetColors(firstColumnColors, secondColumnColors, thirdColumnColors, fourthColumnColors ColorConfig) { +func (m *Model) SetColors( + firstColumnColors, + secondColumnColors, + thirdColumnColors, + fourthColumnColors ColorConfig, +) { m.FirstColumnColors = firstColumnColors m.SecondColumnColors = secondColumnColors m.ThirdColumnColors = thirdColumnColors From 63123a60703789eec88d721f23e726298fbbb368 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sat, 23 Mar 2024 13:50:06 -0400 Subject: [PATCH 24/31] chore: update readme --- README.md | 52 ++++++++++++++-------------------------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 24d02b9..f8671d4 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ A terminal based file manager - [Glamour](https://github.com/charmbracelet/glamour) - [Chroma](https://github.com/alecthomas/chroma) - [Cobra](https://github.com/spf13/cobra) -- [Teacup](https://github.com/mistakenelf/teacup) ## Installation @@ -62,8 +61,7 @@ paru -S fm-bin ## Features -- Double pane layout -- File icons +- File icons (requires nerd font) - Layout adjusts to terminal resize - Syntax highlighting for source code with customizable themes using styles from [chroma](https://swapoff.org/chroma/playground/) (dracula, monokai etc.) - Render pretty markdown @@ -95,26 +93,32 @@ paru -S fm-bin - `fm update` will update fm to the latest version - `fm --start-dir=/some/start/dir` will start fm in the specified directory - `fm --selection-path=/tmp/tmpfile` will write the selected items path to the selection path when pressing E and exit fm +- `fm --start-dir=/some/dir` start fm at a specific directory +- `fm --enable-logging=true` start fm with logging enabled +- `fm --pretty-markdown=true` render markdown using glamour to make it look nice +- `fm --theme=default` set the theme of fm +- `fm --show-icons=false` set whether to show icons or not +- `fm --syntax-theme=dracula` sets the syntax theme to render code with ## Navigation | Key | Description | | --------------------- | ---------------------------------------------------------- | -| h or left | Paginate to the left | -| j or down | Move down in the file tree or scroll pane down | +| h or left | Go to previous directory | +| j or down | Move down in the file tree or scroll pane down | | k or up | Move up in the file tree or scroll pane up | -| l or right | Paginate to the right | +| l or right | Open file or directory | | G | Jump to bottom of file tree or pane | | g | Jump to top of file tree or pane | | ~ | Go to home directory | -| R | Go to the root directory | +| / | Go to the root directory | | . | Toggle hidden files and directories | | ctrl+c | Exit | | q | Exit if command bar is not open | | tab | Toggle between panes | -| esc | Blur filetree input | -| z | Create a zip file of the currently selected directory item | -| u | Unzip a zip file | +| esc | Reset app state and show help screen | +| Z | Create a zip file of the currently selected directory item | +| U | Unzip a zip file | | c | Create a copy of a file or directory | | x | Delete the currently selected file or directory | | n | Create a new file in the current directory | @@ -127,30 +131,6 @@ paru -S fm-bin | ? | Toggle filetree full help menu | | ctrl+r | Reload config | -## Configuration - -A config file will be generated when you first run `fm`. Depending on your operating system it can be found in one of the following locations: - -- macOS: ~/Library/Application\ Support/fm/config.yml -- Linux: ~/.config/fm/config.yml -- Windows: C:\Users\me\AppData\Roaming\fm\config.yml - -It will include the following default settings: - -```yml -settings: - borderless: false - enable_logging: false - pretty_markdown: true - show_icons: true - start_dir: . -theme: - app_theme: default - syntax_theme: - dark: dracula - light: pygments -``` - ## Local Development Follow the instructions below to get setup for local development @@ -172,7 +152,3 @@ make ```sh make build ``` - -## Credit - -- Thank you to this repo https://github.com/Yash-Handa/logo-ls for the icons From e0dce8483eb3b095e63fa1a5b4fe4ce1b45a4bba Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sat, 23 Mar 2024 13:50:14 -0400 Subject: [PATCH 25/31] feat: lots of improvements --- code/code.go | 5 ++ filetree/commands.go | 107 ++++++++++++++++++++------------------ filetree/init.go | 2 +- filetree/methods.go | 2 +- filetree/model.go | 2 + filetree/update.go | 112 ++++++++++------------------------------ filetree/view.go | 33 ++++++++++-- help/help.go | 5 ++ image/image.go | 5 ++ internal/tui/helpers.go | 65 +++++++++++++++++++++++ internal/tui/init.go | 6 ++- internal/tui/keys.go | 34 ++++++------ internal/tui/model.go | 9 ++++ internal/tui/update.go | 40 +++++++++++--- internal/tui/view.go | 43 +-------------- markdown/markdown.go | 5 ++ pdf/pdf.go | 5 ++ 17 files changed, 271 insertions(+), 209 deletions(-) diff --git a/code/code.go b/code/code.go index d56789b..dffddb7 100644 --- a/code/code.go +++ b/code/code.go @@ -115,6 +115,11 @@ 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 diff --git a/filetree/commands.go b/filetree/commands.go index 3b88c4c..22efad8 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -14,7 +14,10 @@ import ( "github.com/mistakenelf/fm/filesystem" ) -type getDirectoryListingMsg []DirectoryItem +type getDirectoryListingMsg struct { + files []DirectoryItem + workingDirectory string +} type errorMsg string type copyToClipboardMsg string type statusMessageTimeoutMsg struct{} @@ -22,8 +25,47 @@ type editorFinishedMsg struct{ err error } type createFileMsg struct{} type createDirectoryMsg struct{} -// getDirectoryListingCmd updates the directory listing based on the name of the directory provided. -func getDirectoryListingCmd(directoryName string, showHidden, directoriesOnly, filesOnly bool) tea.Cmd { +// NewStatusMessageCmd sets a new status message, which will show for a limited +// amount of time. Note that this also returns a command. +func (m *Model) NewStatusMessageCmd(s string) tea.Cmd { + m.StatusMessage = s + + if m.statusMessageTimer != nil { + m.statusMessageTimer.Stop() + } + + m.statusMessageTimer = time.NewTimer(m.StatusMessageLifetime) + + return func() tea.Msg { + <-m.statusMessageTimer.C + return statusMessageTimeoutMsg{} + } +} + +// CreateDirectoryCmd creates a directory based on the name provided. +func (m *Model) CreateDirectoryCmd(name string) tea.Cmd { + return func() tea.Msg { + if err := filesystem.CreateDirectory(name); err != nil { + return errorMsg(err.Error()) + } + + return createDirectoryMsg{} + } +} + +// CreateFileCmd creates a file based on the name provided. +func (m *Model) CreateFileCmd(name string) tea.Cmd { + return func() tea.Msg { + if err := filesystem.CreateFile(name); err != nil { + return errorMsg(err.Error()) + } + + return createFileMsg{} + } +} + +// GetDirectoryListingCmd updates the directory listing based on the name of the directory provided. +func (m Model) GetDirectoryListingCmd(directoryName string) tea.Cmd { return func() tea.Msg { var err error var directoryItems []DirectoryItem @@ -50,19 +92,19 @@ func getDirectoryListingCmd(directoryName string, showHidden, directoriesOnly, f return errorMsg(err.Error()) } - if !directoriesOnly && !filesOnly { - files, err = filesystem.GetDirectoryListing(directoryName, showHidden) + if !m.showDirectoriesOnly && !m.showFilesOnly { + files, err = filesystem.GetDirectoryListing(directoryName, m.showHidden) if err != nil { return errorMsg(err.Error()) } } else { listingType := filesystem.DirectoriesListingType - if filesOnly { + if m.showFilesOnly { listingType = filesystem.FilesListingType } - files, err = filesystem.GetDirectoryListingByType(directoryName, listingType, showHidden) + files, err = filesystem.GetDirectoryListingByType(directoryName, listingType, m.showHidden) if err != nil { return errorMsg(err.Error()) } @@ -79,22 +121,24 @@ func getDirectoryListingCmd(directoryName string, showHidden, directoriesOnly, f continue } - status := fmt.Sprintf("%s %s", - ConvertBytesToSizeString(fileInfo.Size()), - fileInfo.Mode().String()) + fileSize := ConvertBytesToSizeString(fileInfo.Size()) directoryItems = append(directoryItems, DirectoryItem{ Name: file.Name(), - Details: status, + Details: fileInfo.Mode().String(), Path: filepath.Join(workingDirectory, file.Name()), Extension: filepath.Ext(fileInfo.Name()), IsDirectory: fileInfo.IsDir(), CurrentDirectory: workingDirectory, FileInfo: fileInfo, + FileSize: fileSize, }) } - return getDirectoryListingMsg(directoryItems) + return getDirectoryListingMsg{ + files: directoryItems, + workingDirectory: workingDirectory, + } } } @@ -115,28 +159,6 @@ func deleteDirectoryItemCmd(name string, isDirectory bool) tea.Cmd { } } -// CreateDirectoryCmd creates a directory based on the name provided. -func (m *Model) CreateDirectoryCmd(name string) tea.Cmd { - return func() tea.Msg { - if err := filesystem.CreateDirectory(name); err != nil { - return errorMsg(err.Error()) - } - - return createDirectoryMsg{} - } -} - -// CreateFileCmd creates a file based on the name provided. -func (m *Model) CreateFileCmd(name string) tea.Cmd { - return func() tea.Msg { - if err := filesystem.CreateFile(name); err != nil { - return errorMsg(err.Error()) - } - - return createFileMsg{} - } -} - // zipDirectoryCmd zips a directory based on the name provided. func zipDirectoryCmd(name string) tea.Cmd { return func() tea.Msg { @@ -205,23 +227,6 @@ func writeSelectionPathCmd(selectionPath, filePath string) tea.Cmd { } } -// 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) - - return func() tea.Msg { - <-m.statusMessageTimer.C - return statusMessageTimeoutMsg{} - } -} - func openEditorCmd(file string) tea.Cmd { editor := os.Getenv("EDITOR") if editor == "" { diff --git a/filetree/init.go b/filetree/init.go index da8a50d..208cb3c 100644 --- a/filetree/init.go +++ b/filetree/init.go @@ -5,5 +5,5 @@ import ( ) func (m Model) Init() tea.Cmd { - return getDirectoryListingCmd(m.startDir, true, false, false) + return m.GetDirectoryListingCmd(m.startDir) } diff --git a/filetree/methods.go b/filetree/methods.go index 43d5cb9..394b052 100644 --- a/filetree/methods.go +++ b/filetree/methods.go @@ -63,7 +63,7 @@ func (m Model) GetTotalItems() int { // SetSize Sets the size of the filetree. func (m *Model) SetSize(width, height int) { - m.height = height - 2 + m.height = height m.width = width m.max = m.height - 1 } diff --git a/filetree/model.go b/filetree/model.go index b759f04..88d9eae 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -14,6 +14,7 @@ type DirectoryItem struct { Details string Path string Extension string + FileSize string CurrentDirectory string IsDirectory bool FileInfo os.FileInfo @@ -43,6 +44,7 @@ type Model struct { unselectedItemColor lipgloss.AdaptiveColor inactiveItemColor lipgloss.AdaptiveColor err error + CurrentDirectory string } func New(startDir string) Model { diff --git a/filetree/update.go b/filetree/update.go index 6d0f7e5..905216f 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -11,9 +11,7 @@ import ( ) func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { - var ( - cmds []tea.Cmd - ) + var cmds []tea.Cmd if m.Disabled { return m, nil @@ -42,30 +40,23 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.CreatingNewFile = false m.CreatingNewDirectory = false - return m, getDirectoryListingCmd( - filesystem.CurrentDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ) + return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) case createDirectoryMsg: m.CreatingNewDirectory = false m.CreatingNewFile = false - return m, getDirectoryListingCmd( - filesystem.CurrentDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ) + return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) case getDirectoryListingMsg: - if msg != nil { - m.files = msg - m.Cursor = 0 - m.min = 0 - m.max = m.height - m.max = max(m.max, m.height) + if msg.files != nil { + m.files = msg.files + } else { + m.files = make([]DirectoryItem, 0) } + + m.CurrentDirectory = msg.workingDirectory + m.Cursor = 0 + m.min = 0 + m.max = max(m.max, m.height-1) case tea.KeyMsg: switch { case key.Matches(msg, m.keyMap.Down): @@ -155,23 +146,13 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, nil } - return m, getDirectoryListingCmd( - filesystem.HomeDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ) + return m, m.GetDirectoryListingCmd(filesystem.HomeDirectory) case key.Matches(msg, m.keyMap.GoToRootDirectory): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil } - return m, getDirectoryListingCmd( - filesystem.RootDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ) + return m, m.GetDirectoryListingCmd(filesystem.RootDirectory) case key.Matches(msg, m.keyMap.ToggleHidden): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil @@ -179,35 +160,26 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.showHidden = !m.showHidden - return m, getDirectoryListingCmd( - filesystem.CurrentDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ) + return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) case key.Matches(msg, m.keyMap.OpenDirectory): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil } if m.files[m.Cursor].IsDirectory { - return m, getDirectoryListingCmd( - m.files[m.Cursor].Path, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ) + return m, m.GetDirectoryListingCmd(m.files[m.Cursor].Path) } case key.Matches(msg, m.keyMap.PreviousDirectory): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil } - return m, getDirectoryListingCmd( + if len(m.files) == 0 { + return m, m.GetDirectoryListingCmd(filepath.Dir(m.CurrentDirectory)) + } + + return m, m.GetDirectoryListingCmd( filepath.Dir(m.files[m.Cursor].CurrentDirectory), - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, ) case key.Matches(msg, m.keyMap.CopyPathToClipboard): if m.CreatingNewFile || m.CreatingNewDirectory { @@ -222,12 +194,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, tea.Sequence( copyDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), - getDirectoryListingCmd( - filesystem.CurrentDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ), + m.GetDirectoryListingCmd(filesystem.CurrentDirectory), ) case key.Matches(msg, m.keyMap.DeleteDirectoryItem): if m.CreatingNewFile || m.CreatingNewDirectory { @@ -236,12 +203,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, tea.Sequence( deleteDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), - getDirectoryListingCmd( - filesystem.CurrentDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ), + m.GetDirectoryListingCmd(filesystem.CurrentDirectory), ) case key.Matches(msg, m.keyMap.ZipDirectoryItem): if m.CreatingNewFile || m.CreatingNewDirectory { @@ -250,12 +212,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, tea.Sequence( zipDirectoryCmd(m.files[m.Cursor].Name), - getDirectoryListingCmd( - filesystem.CurrentDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ), + m.GetDirectoryListingCmd(filesystem.CurrentDirectory), ) case key.Matches(msg, m.keyMap.UnzipDirectoryItem): if m.CreatingNewFile || m.CreatingNewDirectory { @@ -264,12 +221,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, tea.Sequence( unzipDirectoryCmd(m.files[m.Cursor].Name), - getDirectoryListingCmd( - filesystem.CurrentDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ), + m.GetDirectoryListingCmd(filesystem.CurrentDirectory), ) case key.Matches(msg, m.keyMap.ShowDirectoriesOnly): if m.CreatingNewFile || m.CreatingNewDirectory { @@ -279,12 +231,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.showDirectoriesOnly = !m.showDirectoriesOnly m.showFilesOnly = false - return m, getDirectoryListingCmd( - filesystem.CurrentDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ) + return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) case key.Matches(msg, m.keyMap.ShowFilesOnly): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil @@ -293,12 +240,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.showFilesOnly = !m.showFilesOnly m.showDirectoriesOnly = false - return m, getDirectoryListingCmd( - filesystem.CurrentDirectory, - m.showHidden, - m.showDirectoriesOnly, - m.showFilesOnly, - ) + return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) case key.Matches(msg, m.keyMap.WriteSelectionPath): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil diff --git a/filetree/view.go b/filetree/view.go index d7a6828..40f2c17 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -34,22 +34,45 @@ func (m Model) View() string { if m.showIcons { icon := icons.Icons.GetIcon(file.FileInfo) - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(iconColor).Render(icon) + " ") + fileList.WriteString( + lipgloss.NewStyle(). + Bold(true). + Foreground(iconColor). + Render(icon) + " ", + ) } - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(textColor).Render(file.Name) + "\n") + fileList.WriteString( + lipgloss.NewStyle(). + Bold(true). + Foreground(textColor). + Render(file.Name) + "\n", + ) case i != m.Cursor && !m.Disabled: iconColor := m.unselectedItemColor textColor := m.unselectedItemColor if m.showIcons { icon := icons.Icons.GetIcon(file.FileInfo) - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(iconColor).Render(icon) + " ") + fileList.WriteString( + lipgloss.NewStyle(). + Bold(true). + Foreground(iconColor). + Render(icon) + " ", + ) } - fileList.WriteString(lipgloss.NewStyle().Bold(true).Foreground(textColor).Render(file.Name) + "\n") + fileList.WriteString( + lipgloss.NewStyle(). + Bold(true). + Foreground(textColor). + Render(file.Name) + "\n", + ) } } - return lipgloss.NewStyle().Width(m.width).Height(m.height).Render(fileList.String()) + return lipgloss.NewStyle(). + Width(m.width). + Height(m.height). + Render(fileList.String()) } diff --git a/help/help.go b/help/help.go index 4dc1b98..a339492 100644 --- a/help/help.go +++ b/help/help.go @@ -113,6 +113,11 @@ func (m *Model) GotoTop() { m.Viewport.GotoTop() } +// GotoBottom jumps to the bottom of the viewport. +func (m *Model) GotoBottom() { + m.Viewport.GotoBottom() +} + // SetTitleColor sets the color of the title. func (m *Model) SetTitleColor(color TitleColor) { m.TitleColor = color diff --git a/image/image.go b/image/image.go index 54d5986..5d9b9d8 100644 --- a/image/image.go +++ b/image/image.go @@ -139,6 +139,11 @@ func (m *Model) GotoTop() { m.Viewport.GotoTop() } +// GotoBottom jumps to the bottom of the viewport. +func (m *Model) GotoBottom() { + m.Viewport.GotoBottom() +} + // Update handles updating the UI of the image bubble. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var ( diff --git a/internal/tui/helpers.go b/internal/tui/helpers.go index cd67848..9d102aa 100644 --- a/internal/tui/helpers.go +++ b/internal/tui/helpers.go @@ -1,5 +1,11 @@ package tui +import ( + "fmt" + + "github.com/charmbracelet/lipgloss" +) + func contains(s []string, str string) bool { for _, v := range s { if v == str { @@ -25,3 +31,62 @@ func (m *model) resetViewports() { m.help.GotoTop() m.image.GotoTop() } + +func (m *model) updateStatusBar() { + if m.filetree.GetSelectedItem().Name != "" { + statusMessage := + m.filetree.GetSelectedItem().CurrentDirectory + + lipgloss.NewStyle(). + Padding(0, 1). + Foreground(lipgloss.Color("#eab308")). + Render(m.filetree.GetSelectedItem().Details) + + if m.filetree.StatusMessage != "" { + statusMessage = m.filetree.StatusMessage + } + + if m.code.StatusMessage != "" { + statusMessage = m.code.StatusMessage + } + + if m.markdown.StatusMessage != "" { + statusMessage = m.markdown.StatusMessage + } + + if m.pdf.StatusMessage != "" { + statusMessage = m.pdf.StatusMessage + } + + if m.image.StatusMessage != "" { + statusMessage = m.image.StatusMessage + } + + if m.statusMessage != "" { + statusMessage = m.statusMessage + } + + if m.showTextInput { + statusMessage = m.textinput.View() + } + + m.statusbar.SetContent( + m.filetree.GetSelectedItem().Name, + statusMessage, + fmt.Sprintf("%d/%d", m.filetree.Cursor+1, m.filetree.GetTotalItems()), + fmt.Sprintf(m.filetree.GetSelectedItem().FileSize), + ) + } else { + statusMessage := "Directory is empty" + + if m.showTextInput { + statusMessage = m.textinput.View() + } + + m.statusbar.SetContent( + "N/A", + statusMessage, + fmt.Sprintf("%d/%d", 0, 0), + "FM", + ) + } +} diff --git a/internal/tui/init.go b/internal/tui/init.go index 601db1e..f2c45dc 100644 --- a/internal/tui/init.go +++ b/internal/tui/init.go @@ -7,5 +7,9 @@ import ( // Init intializes the UI. func (m model) Init() tea.Cmd { - return tea.Batch(m.filetree.Init(), textinput.Blink) + return tea.Batch( + m.filetree.Init(), + m.secondaryFiletree.Init(), + textinput.Blink, + ) } diff --git a/internal/tui/keys.go b/internal/tui/keys.go index cb86584..ee911f2 100644 --- a/internal/tui/keys.go +++ b/internal/tui/keys.go @@ -3,25 +3,27 @@ package tui import "github.com/charmbracelet/bubbles/key" type keyMap struct { - Quit key.Binding - TogglePane key.Binding - OpenFile key.Binding - ResetState key.Binding - ShowTextInput key.Binding - SubmitTextInput key.Binding - GotoTop key.Binding - GotoBottom key.Binding + Quit key.Binding + TogglePane key.Binding + OpenFile key.Binding + ResetState key.Binding + ShowTextInput key.Binding + SubmitTextInput key.Binding + GotoTop key.Binding + GotoBottom key.Binding + MoveDirectoryItem key.Binding } func defaultKeyMap() keyMap { return keyMap{ - Quit: key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "quit")), - TogglePane: key.NewBinding(key.WithKeys("tab"), key.WithHelp("tab", "toggle pane")), - OpenFile: key.NewBinding(key.WithKeys("l"), key.WithHelp("l", "open file")), - ResetState: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "reset state")), - ShowTextInput: key.NewBinding(key.WithKeys("N", "M"), key.WithHelp("N, M", "show text input")), - SubmitTextInput: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit text input")), - GotoTop: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "go to top")), - GotoBottom: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "go to bottom")), + Quit: key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "quit")), + TogglePane: key.NewBinding(key.WithKeys("tab"), key.WithHelp("tab", "toggle pane")), + OpenFile: key.NewBinding(key.WithKeys("l", "right"), key.WithHelp("l", "open file")), + ResetState: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "reset state")), + ShowTextInput: key.NewBinding(key.WithKeys("N", "M"), key.WithHelp("N, M", "show text input")), + SubmitTextInput: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit text input")), + GotoTop: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "go to top")), + GotoBottom: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "go to bottom")), + MoveDirectoryItem: key.NewBinding(key.WithKeys("m"), key.WithHelp("m", "move directory item")), } } diff --git a/internal/tui/model.go b/internal/tui/model.go index 56a1dff..629dd22 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -25,6 +25,7 @@ const ( showMarkdownState showPdfState showHelpState + showMoveState ) type Config struct { @@ -39,6 +40,7 @@ type Config struct { type model struct { filetree filetree.Model + secondaryFiletree filetree.Model help help.Model code code.Model image image.Model @@ -52,6 +54,7 @@ type model struct { showTextInput bool textinput textinput.Model statusMessage string + directoryBeforeMove string statusMessageLifetime time.Duration statusMessageTimer *time.Timer } @@ -63,6 +66,11 @@ func New(cfg Config) model { filetreeModel.SetSelectionPath(cfg.SelectionPath) filetreeModel.SetShowIcons(cfg.ShowIcons) + secondaryFiletree := filetree.New(cfg.StartDir) + secondaryFiletree.SetTheme(cfg.Theme.SelectedTreeItemColor, cfg.Theme.UnselectedTreeItemColor) + secondaryFiletree.SetSelectionPath(cfg.SelectionPath) + secondaryFiletree.SetShowIcons(cfg.ShowIcons) + if cfg.ShowIcons { icons.ParseIcons() } @@ -141,6 +149,7 @@ func New(cfg Config) model { return model{ filetree: filetreeModel, + secondaryFiletree: secondaryFiletree, help: helpModel, code: codeModel, image: imageModel, diff --git a/internal/tui/update.go b/internal/tui/update.go index 2651e2a..8d58596 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -19,15 +19,16 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.statusMessage = "" case tea.WindowSizeMsg: halfSize := msg.Width / 2 - bubbleHeight := msg.Height - statusbar.Height + height := msg.Height - statusbar.Height - cmds = append(cmds, m.image.SetSizeCmd(halfSize, bubbleHeight)) - cmds = append(cmds, m.markdown.SetSizeCmd(halfSize, bubbleHeight)) + cmds = append(cmds, m.image.SetSizeCmd(halfSize, height)) + cmds = append(cmds, m.markdown.SetSizeCmd(halfSize, height)) - m.filetree.SetSize(halfSize, bubbleHeight) - m.help.SetSize(halfSize, bubbleHeight) - m.code.SetSize(halfSize, bubbleHeight) - m.pdf.SetSize(halfSize, bubbleHeight) + m.filetree.SetSize(halfSize, height) + m.secondaryFiletree.SetSize(halfSize, height) + m.help.SetSize(halfSize, height) + m.code.SetSize(halfSize, height) + m.pdf.SetSize(halfSize, height) m.statusbar.SetSize(msg.Width) return m, tea.Batch(cmds...) @@ -52,9 +53,15 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textinput, cmd = m.textinput.Update(msg) cmds = append(cmds, cmd) + cmds = append(cmds, m.filetree.GetDirectoryListingCmd(m.directoryBeforeMove)) + m.textinput.Reset() + case key.Matches(msg, m.keyMap.MoveDirectoryItem): + m.directoryBeforeMove = m.filetree.GetSelectedItem().CurrentDirectory + m.state = showMoveState + m.filetree.SetDisabled(true) case key.Matches(msg, m.keyMap.ShowTextInput): - if m.activePane == 0 { + if m.activePane == 0 && !m.filetree.CreatingNewDirectory && !m.filetree.CreatingNewFile { m.showTextInput = true m.textinput.Focus() m.disableAllViewports() @@ -107,6 +114,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } } + case key.Matches(msg, m.keyMap.GotoTop): + if m.activePane != 0 { + m.resetViewports() + } + case key.Matches(msg, m.keyMap.GotoBottom): + if m.activePane != 0 { + m.code.GotoBottom() + m.pdf.GotoBottom() + m.markdown.GotoBottom() + m.help.GotoBottom() + m.image.GotoBottom() + } } } @@ -118,6 +137,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.filetree, cmd = m.filetree.Update(msg) cmds = append(cmds, cmd) + m.secondaryFiletree, cmd = m.secondaryFiletree.Update(msg) + cmds = append(cmds, cmd) + m.code, cmd = m.code.Update(msg) cmds = append(cmds, cmd) @@ -133,5 +155,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.help, cmd = m.help.Update(msg) cmds = append(cmds, cmd) + m.updateStatusBar() + return m, tea.Batch(cmds...) } diff --git a/internal/tui/view.go b/internal/tui/view.go index 138cca4..663b1be 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -1,8 +1,6 @@ package tui import ( - "fmt" - "github.com/charmbracelet/lipgloss" ) @@ -22,45 +20,8 @@ func (m model) View() string { rightBox = m.pdf.View() case showMarkdownState: rightBox = m.markdown.View() - } - - if m.filetree.GetSelectedItem().Name != "" { - statusMessage := m.filetree.GetSelectedItem().CurrentDirectory - - if m.filetree.StatusMessage != "" { - statusMessage = m.filetree.StatusMessage - } - - if m.code.StatusMessage != "" { - statusMessage = m.code.StatusMessage - } - - if m.markdown.StatusMessage != "" { - statusMessage = m.markdown.StatusMessage - } - - if m.pdf.StatusMessage != "" { - statusMessage = m.pdf.StatusMessage - } - - if m.image.StatusMessage != "" { - statusMessage = m.image.StatusMessage - } - - if m.statusMessage != "" { - statusMessage = m.statusMessage - } - - if m.showTextInput { - statusMessage = m.textinput.View() - } - - m.statusbar.SetContent( - m.filetree.GetSelectedItem().Name, - statusMessage, - fmt.Sprintf("%d/%d", m.filetree.Cursor+1, m.filetree.GetTotalItems()), - fmt.Sprintf("%s %s", "🗀", "FM"), - ) + case showMoveState: + rightBox = m.secondaryFiletree.View() } return lipgloss.JoinVertical(lipgloss.Top, diff --git a/markdown/markdown.go b/markdown/markdown.go index cdef444..11e5afe 100644 --- a/markdown/markdown.go +++ b/markdown/markdown.go @@ -123,6 +123,11 @@ 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 diff --git a/pdf/pdf.go b/pdf/pdf.go index 793e33c..df89a6d 100644 --- a/pdf/pdf.go +++ b/pdf/pdf.go @@ -123,6 +123,11 @@ func (m *Model) GotoTop() { m.Viewport.GotoTop() } +// GotoBottom jumps to the bottom of the viewport. +func (m *Model) GotoBottom() { + m.Viewport.GotoBottom() +} + // Update handles updating the UI of the pdf bubble. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { var ( From e629e0d09f3264086e00c9df7b4e43cf5c2b074e Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sat, 23 Mar 2024 14:01:04 -0400 Subject: [PATCH 26/31] fix: dont reset dir if not moving --- internal/tui/update.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/tui/update.go b/internal/tui/update.go index 8d58596..ce2369d 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -24,7 +24,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)) - m.filetree.SetSize(halfSize, height) + m.filetree.SetSize(halfSize, height-3) m.secondaryFiletree.SetSize(halfSize, height) m.help.SetSize(halfSize, height) m.code.SetSize(halfSize, height) @@ -41,6 +41,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, m.openFileCmd()) } case key.Matches(msg, m.keyMap.ResetState): + if m.state == showMoveState { + cmds = append(cmds, m.filetree.GetDirectoryListingCmd(m.directoryBeforeMove)) + } + m.state = idleState m.showTextInput = false m.disableAllViewports() @@ -53,8 +57,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textinput, cmd = m.textinput.Update(msg) cmds = append(cmds, cmd) - cmds = append(cmds, m.filetree.GetDirectoryListingCmd(m.directoryBeforeMove)) - m.textinput.Reset() case key.Matches(msg, m.keyMap.MoveDirectoryItem): m.directoryBeforeMove = m.filetree.GetSelectedItem().CurrentDirectory From f48f050142e26330886b16a7a95e7c70db9f6eee Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Sat, 23 Mar 2024 14:38:47 -0400 Subject: [PATCH 27/31] feat: WIP move directory items --- filetree/commands.go | 12 ++++++++++++ filetree/update.go | 5 +++++ filetree/view.go | 1 - internal/tui/keys.go | 4 ++-- internal/tui/model.go | 1 + internal/tui/update.go | 27 ++++++++++++++++++--------- internal/tui/view.go | 2 +- 7 files changed, 39 insertions(+), 13 deletions(-) diff --git a/filetree/commands.go b/filetree/commands.go index 22efad8..e96c613 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -24,6 +24,7 @@ type statusMessageTimeoutMsg struct{} type editorFinishedMsg struct{ err error } type createFileMsg struct{} type createDirectoryMsg struct{} +type moveDirectoryItemMsg struct{} // NewStatusMessageCmd sets a new status message, which will show for a limited // amount of time. Note that this also returns a command. @@ -64,6 +65,17 @@ func (m *Model) CreateFileCmd(name string) tea.Cmd { } } +// MoveDirectoryItemCmd moves an item from one place to another. +func (m Model) MoveDirectoryItemCmd(source, destination string) tea.Cmd { + return func() tea.Msg { + if err := filesystem.MoveDirectoryItem(source, destination); err != nil { + return errorMsg(err.Error()) + } + + return moveDirectoryItemMsg{} + } +} + // GetDirectoryListingCmd updates the directory listing based on the name of the directory provided. func (m Model) GetDirectoryListingCmd(directoryName string) tea.Cmd { return func() tea.Msg { diff --git a/filetree/update.go b/filetree/update.go index 905216f..5ba79dd 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -31,6 +31,11 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { Render(string(msg)))) case statusMessageTimeoutMsg: m.StatusMessage = "" + case moveDirectoryItemMsg: + m.CreatingNewFile = false + m.CreatingNewDirectory = false + + return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) case copyToClipboardMsg: cmds = append(cmds, m.NewStatusMessageCmd( lipgloss.NewStyle(). diff --git a/filetree/view.go b/filetree/view.go index 40f2c17..2ea1ea2 100644 --- a/filetree/view.go +++ b/filetree/view.go @@ -73,6 +73,5 @@ func (m Model) View() string { return lipgloss.NewStyle(). Width(m.width). - Height(m.height). Render(fileList.String()) } diff --git a/internal/tui/keys.go b/internal/tui/keys.go index ee911f2..914e8ba 100644 --- a/internal/tui/keys.go +++ b/internal/tui/keys.go @@ -8,7 +8,7 @@ type keyMap struct { OpenFile key.Binding ResetState key.Binding ShowTextInput key.Binding - SubmitTextInput key.Binding + Submit key.Binding GotoTop key.Binding GotoBottom key.Binding MoveDirectoryItem key.Binding @@ -21,7 +21,7 @@ func defaultKeyMap() keyMap { OpenFile: key.NewBinding(key.WithKeys("l", "right"), key.WithHelp("l", "open file")), ResetState: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "reset state")), ShowTextInput: key.NewBinding(key.WithKeys("N", "M"), key.WithHelp("N, M", "show text input")), - SubmitTextInput: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit text input")), + Submit: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit text input")), GotoTop: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "go to top")), GotoBottom: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "go to bottom")), MoveDirectoryItem: key.NewBinding(key.WithKeys("m"), key.WithHelp("m", "move directory item")), diff --git a/internal/tui/model.go b/internal/tui/model.go index 629dd22..08287f4 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -50,6 +50,7 @@ type model struct { state sessionState keyMap keyMap activePane int + height int config Config showTextInput bool textinput textinput.Model diff --git a/internal/tui/update.go b/internal/tui/update.go index ce2369d..04e64d4 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -20,12 +20,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: halfSize := msg.Width / 2 height := msg.Height - statusbar.Height + m.height = height cmds = append(cmds, m.image.SetSizeCmd(halfSize, height)) cmds = append(cmds, m.markdown.SetSizeCmd(halfSize, height)) m.filetree.SetSize(halfSize, height-3) - m.secondaryFiletree.SetSize(halfSize, height) + m.secondaryFiletree.SetSize(halfSize, height-3) m.help.SetSize(halfSize, height) m.code.SetSize(halfSize, height) m.pdf.SetSize(halfSize, height) @@ -37,7 +38,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, m.keyMap.Quit): return m, tea.Quit case key.Matches(msg, m.keyMap.OpenFile): - if !m.showTextInput { + if !m.showTextInput && m.activePane == 0 { cmds = append(cmds, m.openFileCmd()) } case key.Matches(msg, m.keyMap.ResetState): @@ -59,9 +60,11 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textinput.Reset() case key.Matches(msg, m.keyMap.MoveDirectoryItem): - m.directoryBeforeMove = m.filetree.GetSelectedItem().CurrentDirectory - m.state = showMoveState - m.filetree.SetDisabled(true) + if m.activePane == 0 && !m.filetree.CreatingNewDirectory && !m.filetree.CreatingNewFile { + m.directoryBeforeMove = m.filetree.GetSelectedItem().CurrentDirectory + m.state = showMoveState + m.filetree.SetDisabled(true) + } case key.Matches(msg, m.keyMap.ShowTextInput): if m.activePane == 0 && !m.filetree.CreatingNewDirectory && !m.filetree.CreatingNewFile { m.showTextInput = true @@ -73,13 +76,19 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textinput.Reset() } - case key.Matches(msg, m.keyMap.SubmitTextInput): + case key.Matches(msg, m.keyMap.Submit): if m.filetree.CreatingNewFile { cmds = append(cmds, m.filetree.CreateFileCmd(m.textinput.Value())) - } - - if m.filetree.CreatingNewDirectory { + } else if m.filetree.CreatingNewDirectory { cmds = append(cmds, m.filetree.CreateDirectoryCmd(m.textinput.Value())) + } else { + cmds = append( + cmds, + m.filetree.MoveDirectoryItemCmd( + m.filetree.GetSelectedItem().Path, + m.secondaryFiletree.CurrentDirectory+"/"+m.filetree.GetSelectedItem().Name, + ), + ) } m.resetViewports() diff --git a/internal/tui/view.go b/internal/tui/view.go index 663b1be..61f4e65 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -25,7 +25,7 @@ func (m model) View() string { } return lipgloss.JoinVertical(lipgloss.Top, - lipgloss.JoinHorizontal(lipgloss.Top, leftBox, rightBox), + lipgloss.NewStyle().Height(m.height).Render(lipgloss.JoinHorizontal(lipgloss.Top, leftBox, rightBox)), m.statusbar.View(), ) } From aaf924b73e0098de638ec2d4610df33ddea2f1a5 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Tue, 26 Mar 2024 19:34:28 -0400 Subject: [PATCH 28/31] chore: update deps --- go.mod | 3 +-- go.sum | 33 ++------------------------------- 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 0b69bc3..a2b868b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/atotto/clipboard v0.1.4 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 - github.com/charmbracelet/glamour v0.6.0 + github.com/charmbracelet/glamour v0.7.0 github.com/charmbracelet/lipgloss v0.10.0 github.com/disintegration/imaging v1.6.2 github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06 @@ -17,7 +17,6 @@ require ( ) require ( - github.com/alecthomas/chroma v0.10.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/containerd/console v1.0.4 // indirect diff --git a/go.sum b/go.sum index 352296d..0d189fd 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,11 @@ github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= -github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= @@ -17,22 +14,17 @@ github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/ github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= -github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= -github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= +github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng= +github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps= github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -43,17 +35,14 @@ github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06 h1:kacRlPN7EN++tVpG github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= @@ -62,13 +51,10 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -78,41 +64,26 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= -golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From bd1f42c08c79a2e0ebb88f218d28af18b85b02b0 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Tue, 26 Mar 2024 19:34:38 -0400 Subject: [PATCH 29/31] feat: move directory items --- filesystem/filesystem.go | 39 ++++++++++++++++++++++-------- filetree/commands.go | 52 ++++++++++++++++++---------------------- filetree/model.go | 15 ++++++------ filetree/update.go | 34 ++++++++++++-------------- internal/tui/commands.go | 8 +++---- internal/tui/helpers.go | 2 +- internal/tui/model.go | 1 + internal/tui/update.go | 16 +++++++++---- 8 files changed, 92 insertions(+), 75 deletions(-) diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go index b8a3cbb..628dfe8 100644 --- a/filesystem/filesystem.go +++ b/filesystem/filesystem.go @@ -431,26 +431,45 @@ func CopyFile(name string) error { return errors.Unwrap(err) } -// CopyDirectory copies a directory given a name. -func CopyDirectory(name string) error { +// CopyDirectory copies a directory given a path. +func CopyDirectory(pathname string) error { + name := filepath.Base(pathname) output := fmt.Sprintf("%s_%d", name, time.Now().Unix()) - err := filepath.Walk(name, func(path string, info os.FileInfo, err error) error { - relPath := strings.Replace(path, name, "", 1) + err := filepath.Walk(pathname, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err // Return early if there's an error walking the path + } + + relPath, err := filepath.Rel(pathname, path) + if err != nil { + return err // Return if there's an error getting the relative path + } + + targetPath := filepath.Join(output, relPath) if info.IsDir() { - return fmt.Errorf("%w", os.Mkdir(filepath.Join(output, relPath), os.ModePerm)) + return os.Mkdir(targetPath, os.ModePerm) } - var data, err1 = os.ReadFile(filepath.Join(filepath.Clean(name), filepath.Clean(relPath))) - if err1 != nil { - return errors.Unwrap(err) + data, err := os.ReadFile(path) + if err != nil { + return err // Return if there's an error reading the file } - return fmt.Errorf("%w", os.WriteFile(filepath.Join(output, relPath), data, os.ModePerm)) + err = os.WriteFile(targetPath, data, os.ModePerm) + if err != nil { + return err // Return if there's an error writing the file + } + + return nil }) - return errors.Unwrap(err) + if err != nil { + return err // Return the final error, if any + } + + return nil } // GetDirectoryItemSize calculates the size of a directory or file. diff --git a/filetree/commands.go b/filetree/commands.go index e96c613..b8be9b2 100644 --- a/filetree/commands.go +++ b/filetree/commands.go @@ -82,6 +82,7 @@ func (m Model) GetDirectoryListingCmd(directoryName string) tea.Cmd { var err error var directoryItems []DirectoryItem var files []fs.DirEntry + var directoryPath string if directoryName == filesystem.HomeDirectory { directoryName, err = filesystem.GetHomeDirectory() @@ -90,7 +91,17 @@ func (m Model) GetDirectoryListingCmd(directoryName string) tea.Cmd { } } - directoryInfo, err := os.Stat(directoryName) + if !filepath.IsAbs(directoryName) { + directoryPath, err = filepath.Abs(directoryName) + fmt.Println(directoryPath) + if err != nil { + return errorMsg(err.Error()) + } + } else { + directoryPath = directoryName + } + + directoryInfo, err := os.Stat(directoryPath) if err != nil { return errorMsg(err.Error()) } @@ -99,11 +110,6 @@ func (m Model) GetDirectoryListingCmd(directoryName string) tea.Cmd { return nil } - err = os.Chdir(directoryName) - if err != nil { - return errorMsg(err.Error()) - } - if !m.showDirectoriesOnly && !m.showFilesOnly { files, err = filesystem.GetDirectoryListing(directoryName, m.showHidden) if err != nil { @@ -122,11 +128,6 @@ func (m Model) GetDirectoryListingCmd(directoryName string) tea.Cmd { } } - workingDirectory, err := filesystem.GetWorkingDirectory() - if err != nil { - return errorMsg(err.Error()) - } - for _, file := range files { fileInfo, err := file.Info() if err != nil { @@ -136,20 +137,19 @@ func (m Model) GetDirectoryListingCmd(directoryName string) tea.Cmd { fileSize := ConvertBytesToSizeString(fileInfo.Size()) directoryItems = append(directoryItems, DirectoryItem{ - Name: file.Name(), - Details: fileInfo.Mode().String(), - Path: filepath.Join(workingDirectory, file.Name()), - Extension: filepath.Ext(fileInfo.Name()), - IsDirectory: fileInfo.IsDir(), - CurrentDirectory: workingDirectory, - FileInfo: fileInfo, - FileSize: fileSize, + Name: file.Name(), + Details: fileInfo.Mode().String(), + Path: filepath.Join(directoryPath, file.Name()), + Extension: filepath.Ext(fileInfo.Name()), + IsDirectory: fileInfo.IsDir(), + FileInfo: fileInfo, + FileSize: fileSize, }) } return getDirectoryListingMsg{ files: directoryItems, - workingDirectory: workingDirectory, + workingDirectory: directoryPath, } } } @@ -211,20 +211,14 @@ func copyDirectoryItemCmd(name string, isDirectory bool) tea.Cmd { } // copyToClipboardCmd copies the provided string to the clipboard. -func copyToClipboardCmd(name string) tea.Cmd { +func copyToClipboardCmd(path string) tea.Cmd { return func() tea.Msg { - workingDir, err := filesystem.GetWorkingDirectory() - if err != nil { - return errorMsg(err.Error()) - } - - filePath := filepath.Join(workingDir, name) - err = clipboard.WriteAll(filePath) + err := clipboard.WriteAll(path) if err != nil { return errorMsg(err.Error()) } - return copyToClipboardMsg(fmt.Sprintf("%s %s %s", "Successfully copied", filePath, "to clipboard")) + return copyToClipboardMsg(fmt.Sprintf("%s %s %s", "Successfully copied", path, "to clipboard")) } } diff --git a/filetree/model.go b/filetree/model.go index 88d9eae..55cdab2 100644 --- a/filetree/model.go +++ b/filetree/model.go @@ -10,14 +10,13 @@ import ( ) type DirectoryItem struct { - Name string - Details string - Path string - Extension string - FileSize string - CurrentDirectory string - IsDirectory bool - FileInfo os.FileInfo + Name string + Details string + Path string + Extension string + FileSize string + IsDirectory bool + FileInfo os.FileInfo } type Model struct { diff --git a/filetree/update.go b/filetree/update.go index 5ba79dd..ce62b3e 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -35,7 +35,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.CreatingNewFile = false m.CreatingNewDirectory = false - return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) + return m, m.GetDirectoryListingCmd(m.CurrentDirectory) case copyToClipboardMsg: cmds = append(cmds, m.NewStatusMessageCmd( lipgloss.NewStyle(). @@ -45,12 +45,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.CreatingNewFile = false m.CreatingNewDirectory = false - return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) + return m, m.GetDirectoryListingCmd(m.CurrentDirectory) case createDirectoryMsg: m.CreatingNewDirectory = false m.CreatingNewFile = false - return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) + return m, m.GetDirectoryListingCmd(m.CurrentDirectory) case getDirectoryListingMsg: if msg.files != nil { m.files = msg.files @@ -165,7 +165,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.showHidden = !m.showHidden - return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) + return m, m.GetDirectoryListingCmd(m.CurrentDirectory) case key.Matches(msg, m.keyMap.OpenDirectory): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil @@ -179,27 +179,23 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, nil } - if len(m.files) == 0 { - return m, m.GetDirectoryListingCmd(filepath.Dir(m.CurrentDirectory)) - } - return m, m.GetDirectoryListingCmd( - filepath.Dir(m.files[m.Cursor].CurrentDirectory), + filepath.Dir(m.CurrentDirectory), ) case key.Matches(msg, m.keyMap.CopyPathToClipboard): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil } - return m, copyToClipboardCmd(m.files[m.Cursor].Name) + return m, copyToClipboardCmd(m.files[m.Cursor].Path) case key.Matches(msg, m.keyMap.CopyDirectoryItem): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil } return m, tea.Sequence( - copyDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), - m.GetDirectoryListingCmd(filesystem.CurrentDirectory), + copyDirectoryItemCmd(m.files[m.Cursor].Path, m.files[m.Cursor].IsDirectory), + m.GetDirectoryListingCmd(m.CurrentDirectory), ) case key.Matches(msg, m.keyMap.DeleteDirectoryItem): if m.CreatingNewFile || m.CreatingNewDirectory { @@ -207,8 +203,8 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } return m, tea.Sequence( - deleteDirectoryItemCmd(m.files[m.Cursor].Name, m.files[m.Cursor].IsDirectory), - m.GetDirectoryListingCmd(filesystem.CurrentDirectory), + deleteDirectoryItemCmd(m.files[m.Cursor].Path, m.files[m.Cursor].IsDirectory), + m.GetDirectoryListingCmd(m.CurrentDirectory), ) case key.Matches(msg, m.keyMap.ZipDirectoryItem): if m.CreatingNewFile || m.CreatingNewDirectory { @@ -216,8 +212,8 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } return m, tea.Sequence( - zipDirectoryCmd(m.files[m.Cursor].Name), - m.GetDirectoryListingCmd(filesystem.CurrentDirectory), + zipDirectoryCmd(m.files[m.Cursor].Path), + m.GetDirectoryListingCmd(m.CurrentDirectory), ) case key.Matches(msg, m.keyMap.UnzipDirectoryItem): if m.CreatingNewFile || m.CreatingNewDirectory { @@ -226,7 +222,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, tea.Sequence( unzipDirectoryCmd(m.files[m.Cursor].Name), - m.GetDirectoryListingCmd(filesystem.CurrentDirectory), + m.GetDirectoryListingCmd(m.CurrentDirectory), ) case key.Matches(msg, m.keyMap.ShowDirectoriesOnly): if m.CreatingNewFile || m.CreatingNewDirectory { @@ -236,7 +232,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.showDirectoriesOnly = !m.showDirectoriesOnly m.showFilesOnly = false - return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) + return m, m.GetDirectoryListingCmd(m.CurrentDirectory) case key.Matches(msg, m.keyMap.ShowFilesOnly): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil @@ -245,7 +241,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.showFilesOnly = !m.showFilesOnly m.showDirectoriesOnly = false - return m, m.GetDirectoryListingCmd(filesystem.CurrentDirectory) + return m, m.GetDirectoryListingCmd(m.CurrentDirectory) case key.Matches(msg, m.keyMap.WriteSelectionPath): if m.CreatingNewFile || m.CreatingNewDirectory { return m, nil diff --git a/internal/tui/commands.go b/internal/tui/commands.go index e3179e2..94614f4 100644 --- a/internal/tui/commands.go +++ b/internal/tui/commands.go @@ -36,15 +36,15 @@ func (m *model) openFileCmd() tea.Cmd { case selectedFile.Extension == ".png" || selectedFile.Extension == ".jpg" || selectedFile.Extension == ".jpeg": m.state = showImageState - return m.image.SetFileNameCmd(selectedFile.Name) + return m.image.SetFileNameCmd(selectedFile.Path) case selectedFile.Extension == ".md" && m.config.PrettyMarkdown: m.state = showMarkdownState - return m.markdown.SetFileNameCmd(selectedFile.Name) + return m.markdown.SetFileNameCmd(selectedFile.Path) case selectedFile.Extension == ".pdf": m.state = showPdfState - return m.pdf.SetFileNameCmd(selectedFile.Name) + return m.pdf.SetFileNameCmd(selectedFile.Path) case contains(forbiddenExtensions, selectedFile.Extension): return m.newStatusMessageCmd(lipgloss.NewStyle(). Foreground(lipgloss.Color("#cc241d")). @@ -53,7 +53,7 @@ func (m *model) openFileCmd() tea.Cmd { default: m.state = showCodeState - return m.code.SetFileNameCmd(selectedFile.Name) + return m.code.SetFileNameCmd(selectedFile.Path) } } diff --git a/internal/tui/helpers.go b/internal/tui/helpers.go index 9d102aa..f5ee6fd 100644 --- a/internal/tui/helpers.go +++ b/internal/tui/helpers.go @@ -35,7 +35,7 @@ func (m *model) resetViewports() { func (m *model) updateStatusBar() { if m.filetree.GetSelectedItem().Name != "" { statusMessage := - m.filetree.GetSelectedItem().CurrentDirectory + + m.filetree.CurrentDirectory + lipgloss.NewStyle(). Padding(0, 1). Foreground(lipgloss.Color("#eab308")). diff --git a/internal/tui/model.go b/internal/tui/model.go index 08287f4..6081adb 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -71,6 +71,7 @@ func New(cfg Config) model { secondaryFiletree.SetTheme(cfg.Theme.SelectedTreeItemColor, cfg.Theme.UnselectedTreeItemColor) secondaryFiletree.SetSelectionPath(cfg.SelectionPath) secondaryFiletree.SetShowIcons(cfg.ShowIcons) + secondaryFiletree.SetDisabled(true) if cfg.ShowIcons { icons.ParseIcons() diff --git a/internal/tui/update.go b/internal/tui/update.go index 04e64d4..0422b23 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -61,9 +61,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textinput.Reset() case key.Matches(msg, m.keyMap.MoveDirectoryItem): if m.activePane == 0 && !m.filetree.CreatingNewDirectory && !m.filetree.CreatingNewFile { - m.directoryBeforeMove = m.filetree.GetSelectedItem().CurrentDirectory + m.activePane = (m.activePane + 1) % 2 + m.directoryBeforeMove = m.filetree.CurrentDirectory m.state = showMoveState m.filetree.SetDisabled(true) + m.secondaryFiletree.SetDisabled(false) + cmds = append(cmds, m.secondaryFiletree.GetDirectoryListingCmd(m.filetree.CurrentDirectory)) } case key.Matches(msg, m.keyMap.ShowTextInput): if m.activePane == 0 && !m.filetree.CreatingNewDirectory && !m.filetree.CreatingNewFile { @@ -77,11 +80,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textinput.Reset() } case key.Matches(msg, m.keyMap.Submit): - if m.filetree.CreatingNewFile { + switch { + case m.filetree.CreatingNewFile: cmds = append(cmds, m.filetree.CreateFileCmd(m.textinput.Value())) - } else if m.filetree.CreatingNewDirectory { + case m.filetree.CreatingNewDirectory: cmds = append(cmds, m.filetree.CreateDirectoryCmd(m.textinput.Value())) - } else { + default: cmds = append( cmds, m.filetree.MoveDirectoryItemCmd( @@ -102,6 +106,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.activePane == 0 { m.filetree.SetDisabled(false) + m.secondaryFiletree.SetDisabled(true) m.disableAllViewports() } else { m.filetree.SetDisabled(true) @@ -122,6 +127,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case showMarkdownState: m.disableAllViewports() m.markdown.SetViewportDisabled(false) + case showMoveState: + m.secondaryFiletree.SetDisabled(false) + m.disableAllViewports() } } } From 3065ecb08f4ea5507bff792a5c6ea451e6039304 Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Tue, 26 Mar 2024 19:38:48 -0400 Subject: [PATCH 30/31] fix: reset pane and disable secondary tree --- internal/tui/update.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/tui/update.go b/internal/tui/update.go index 0422b23..d86f27b 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -54,6 +54,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textinput.Blur() m.filetree.CreatingNewDirectory = false m.filetree.CreatingNewFile = false + m.secondaryFiletree.SetDisabled(true) + m.activePane = 0 m.textinput, cmd = m.textinput.Update(msg) cmds = append(cmds, cmd) From 890b5acdd12bac368eda9732aab23f7a7589e1eb Mon Sep 17 00:00:00 2001 From: mistakenelf Date: Tue, 26 Mar 2024 19:48:53 -0400 Subject: [PATCH 31/31] feat: created shared color library polish --- code/code.go | 3 ++- filetree/update.go | 3 ++- image/image.go | 4 +++- internal/tui/commands.go | 3 ++- markdown/markdown.go | 3 ++- pdf/pdf.go | 3 ++- polish/polish.go | 11 +++++++++++ 7 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 polish/polish.go diff --git a/code/code.go b/code/code.go index dffddb7..9c4dc45 100644 --- a/code/code.go +++ b/code/code.go @@ -14,6 +14,7 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/mistakenelf/fm/filesystem" + "github.com/mistakenelf/fm/polish" ) type syntaxMsg string @@ -148,7 +149,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.Filename = "" cmds = append(cmds, m.NewStatusMessageCmd( lipgloss.NewStyle(). - Foreground(lipgloss.Color("#cc241d")). + Foreground(polish.Colors.Red600). Bold(true). Render(string(msg)), )) diff --git a/filetree/update.go b/filetree/update.go index ce62b3e..57ffd57 100644 --- a/filetree/update.go +++ b/filetree/update.go @@ -8,6 +8,7 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/mistakenelf/fm/filesystem" + "github.com/mistakenelf/fm/polish" ) func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { @@ -26,7 +27,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case errorMsg: cmds = append(cmds, m.NewStatusMessageCmd( lipgloss.NewStyle(). - Foreground(lipgloss.Color("#cc241d")). + Foreground(polish.Colors.Red600). Bold(true). Render(string(msg)))) case statusMessageTimeoutMsg: diff --git a/image/image.go b/image/image.go index 5d9b9d8..3128cb1 100644 --- a/image/image.go +++ b/image/image.go @@ -14,6 +14,8 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/disintegration/imaging" "github.com/lucasb-eyer/go-colorful" + + "github.com/mistakenelf/fm/polish" ) type convertImageToStringMsg string @@ -164,7 +166,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case errorMsg: cmds = append(cmds, m.NewStatusMessageCmd( lipgloss.NewStyle(). - Foreground(lipgloss.Color("#cc241d")). + Foreground(polish.Colors.Red600). Bold(true). Render(string(msg)), )) diff --git a/internal/tui/commands.go b/internal/tui/commands.go index 94614f4..0db96e6 100644 --- a/internal/tui/commands.go +++ b/internal/tui/commands.go @@ -5,6 +5,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/mistakenelf/fm/polish" ) var forbiddenExtensions = []string{ @@ -47,7 +48,7 @@ func (m *model) openFileCmd() tea.Cmd { return m.pdf.SetFileNameCmd(selectedFile.Path) case contains(forbiddenExtensions, selectedFile.Extension): return m.newStatusMessageCmd(lipgloss.NewStyle(). - Foreground(lipgloss.Color("#cc241d")). + Foreground(polish.Colors.Red600). Bold(true). Render("Selected file type is not supported")) default: diff --git a/markdown/markdown.go b/markdown/markdown.go index 11e5afe..8561c3e 100644 --- a/markdown/markdown.go +++ b/markdown/markdown.go @@ -12,6 +12,7 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/mistakenelf/fm/filesystem" + "github.com/mistakenelf/fm/polish" ) type renderMarkdownMsg string @@ -154,7 +155,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.FileName = "" cmds = append(cmds, m.NewStatusMessageCmd( lipgloss.NewStyle(). - Foreground(lipgloss.Color("#cc241d")). + Foreground(polish.Colors.Red600). Bold(true). Render(string(msg)), )) diff --git a/pdf/pdf.go b/pdf/pdf.go index df89a6d..327cc05 100644 --- a/pdf/pdf.go +++ b/pdf/pdf.go @@ -11,6 +11,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/ledongthuc/pdf" + "github.com/mistakenelf/fm/polish" ) type renderPDFMsg string @@ -149,7 +150,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.FileName = "" cmds = append(cmds, m.NewStatusMessageCmd( lipgloss.NewStyle(). - Foreground(lipgloss.Color("#cc241d")). + Foreground(polish.Colors.Red600). Bold(true). Render(string(msg)), )) diff --git a/polish/polish.go b/polish/polish.go new file mode 100644 index 0000000..6bcce93 --- /dev/null +++ b/polish/polish.go @@ -0,0 +1,11 @@ +package polish + +import "github.com/charmbracelet/lipgloss" + +type ColorMap struct { + Red600 lipgloss.Color +} + +var Colors = ColorMap{ + Red600: lipgloss.Color("#dc2626"), +}