Enter on File Path inputs opens a file browser (load/save, extension filter, hidden toggle, overwrite confirm). Enter on Connection String inputs opens a builder for PostgreSQL, MSSQL and SQLite with masked password/preview, parsing and optional connection test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
135 lines
3.5 KiB
Go
135 lines
3.5 KiB
Go
package ui
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// FileEntry is a single row in the file browser.
|
|
type FileEntry struct {
|
|
Name string
|
|
IsDir bool
|
|
}
|
|
|
|
// formatExtensions maps a UI format name to the file extensions it reads or writes.
|
|
var formatExtensions = map[string][]string{
|
|
"dbml": {".dbml"},
|
|
"dctx": {".dctx"},
|
|
"drawdb": {".json"},
|
|
"graphql": {".graphql", ".gql"},
|
|
"json": {".json"},
|
|
"yaml": {".yaml", ".yml"},
|
|
"gorm": {".go"},
|
|
"bun": {".go"},
|
|
"drizzle": {".ts"},
|
|
"prisma": {".prisma"},
|
|
"typeorm": {".ts"},
|
|
"pgsql": {".sql"},
|
|
"sqlite": {".db", ".sqlite", ".sqlite3"},
|
|
}
|
|
|
|
// directoryFormats are formats whose reader/writer accepts a directory.
|
|
var directoryFormats = map[string]bool{
|
|
"gorm": true, "bun": true, "drizzle": true, "typeorm": true,
|
|
}
|
|
|
|
// FormatExtensions returns the extensions for a format, or nil (no filter) if unknown.
|
|
func FormatExtensions(format string) []string {
|
|
return formatExtensions[format]
|
|
}
|
|
|
|
// IsDirectoryFormat reports whether a format can be loaded from or saved to a directory.
|
|
func IsDirectoryFormat(format string) bool {
|
|
return directoryFormats[format]
|
|
}
|
|
|
|
// ExpandHome replaces a leading ~ with the user's home directory.
|
|
func ExpandHome(p string) string {
|
|
if strings.HasPrefix(p, "~") {
|
|
if home, err := os.UserHomeDir(); err == nil {
|
|
return filepath.Join(home, p[1:])
|
|
}
|
|
}
|
|
return p
|
|
}
|
|
|
|
// MatchesExtension reports whether name has one of exts (case-insensitive).
|
|
// An empty extension list matches everything.
|
|
func MatchesExtension(name string, exts []string) bool {
|
|
if len(exts) == 0 {
|
|
return true
|
|
}
|
|
ext := strings.ToLower(filepath.Ext(name))
|
|
for _, e := range exts {
|
|
if strings.ToLower(e) == ext {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ListDir returns the entries of dir: directories first, then files that match
|
|
// exts, each group sorted case-insensitively. Hidden (dot) entries are skipped
|
|
// unless showHidden is set.
|
|
func ListDir(dir string, exts []string, showHidden bool) ([]FileEntry, error) {
|
|
items, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var dirs, files []FileEntry
|
|
for _, item := range items {
|
|
name := item.Name()
|
|
if !showHidden && strings.HasPrefix(name, ".") {
|
|
continue
|
|
}
|
|
isDir := item.IsDir()
|
|
if !isDir && item.Type()&os.ModeSymlink != 0 {
|
|
// Follow symlinks so links to directories are navigable.
|
|
if info, err := os.Stat(filepath.Join(dir, name)); err == nil {
|
|
isDir = info.IsDir()
|
|
}
|
|
}
|
|
if isDir {
|
|
dirs = append(dirs, FileEntry{Name: name, IsDir: true})
|
|
} else if MatchesExtension(name, exts) {
|
|
files = append(files, FileEntry{Name: name})
|
|
}
|
|
}
|
|
|
|
byName := func(s []FileEntry) {
|
|
sort.Slice(s, func(i, j int) bool {
|
|
return strings.ToLower(s[i].Name) < strings.ToLower(s[j].Name)
|
|
})
|
|
}
|
|
byName(dirs)
|
|
byName(files)
|
|
return append(dirs, files...), nil
|
|
}
|
|
|
|
// ResolveStart works out where the browser should open for the current input
|
|
// value. It returns the directory to show and, if the input named a file, its
|
|
// base name. Falls back to the working directory.
|
|
func ResolveStart(input string) (dir, name string) {
|
|
input = strings.TrimSpace(input)
|
|
if input != "" {
|
|
p := ExpandHome(input)
|
|
if abs, err := filepath.Abs(p); err == nil {
|
|
p = abs
|
|
}
|
|
if info, err := os.Stat(p); err == nil && info.IsDir() {
|
|
return p, ""
|
|
}
|
|
if info, err := os.Stat(filepath.Dir(p)); err == nil && info.IsDir() {
|
|
return filepath.Dir(p), filepath.Base(p)
|
|
}
|
|
}
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
wd = "."
|
|
}
|
|
return wd, ""
|
|
}
|