paste.go

  1package fsext
  2
  3import (
  4	"os"
  5	"strings"
  6)
  7
  8func ParsePastedFiles(s string) []string {
  9	s = strings.TrimSpace(s)
 10
 11	// NOTE: Rio on Windows adds NULL chars for some reason.
 12	s = strings.ReplaceAll(s, "\x00", "")
 13
 14	switch {
 15	case attemptStat(s):
 16		return strings.Split(s, "\n")
 17	case os.Getenv("WT_SESSION") != "":
 18		return windowsTerminalParsePastedFiles(s)
 19	default:
 20		return unixParsePastedFiles(s)
 21	}
 22}
 23
 24func attemptStat(s string) bool {
 25	for path := range strings.SplitSeq(s, "\n") {
 26		if info, err := os.Stat(path); err != nil || info.IsDir() {
 27			return false
 28		}
 29	}
 30	return true
 31}
 32
 33func windowsTerminalParsePastedFiles(s string) []string {
 34	if strings.TrimSpace(s) == "" {
 35		return nil
 36	}
 37
 38	var (
 39		paths    []string
 40		current  strings.Builder
 41		inQuotes = false
 42	)
 43	for i := range len(s) {
 44		ch := s[i]
 45
 46		switch {
 47		case ch == '"':
 48			if inQuotes {
 49				// End of quoted section
 50				if current.Len() > 0 {
 51					paths = append(paths, current.String())
 52					current.Reset()
 53				}
 54				inQuotes = false
 55			} else {
 56				// Start of quoted section
 57				inQuotes = true
 58			}
 59		case inQuotes:
 60			current.WriteByte(ch)
 61		case ch != ' ':
 62			// Text outside quotes is not allowed
 63			return nil
 64		}
 65	}
 66
 67	// Add any remaining content if quotes were properly closed
 68	if current.Len() > 0 && !inQuotes {
 69		paths = append(paths, current.String())
 70	}
 71
 72	// If quotes were not closed, return empty (malformed input)
 73	if inQuotes {
 74		return nil
 75	}
 76
 77	return paths
 78}
 79
 80func unixParsePastedFiles(s string) []string {
 81	if strings.TrimSpace(s) == "" {
 82		return nil
 83	}
 84
 85	var (
 86		paths   []string
 87		current strings.Builder
 88		escaped = false
 89	)
 90	for i := range len(s) {
 91		ch := s[i]
 92
 93		switch {
 94		case escaped:
 95			// After a backslash, add the character as-is (including space)
 96			current.WriteByte(ch)
 97			escaped = false
 98		case ch == '\\':
 99			// Check if this backslash is at the end of the string
100			if i == len(s)-1 {
101				// Trailing backslash, treat as literal
102				current.WriteByte(ch)
103			} else {
104				// Start of escape sequence
105				escaped = true
106			}
107		case ch == ' ':
108			// Space separates paths (unless escaped)
109			if current.Len() > 0 {
110				paths = append(paths, current.String())
111				current.Reset()
112			}
113		default:
114			current.WriteByte(ch)
115		}
116	}
117
118	// Handle trailing backslash if present
119	if escaped {
120		current.WriteByte('\\')
121	}
122
123	// Add the last path if any
124	if current.Len() > 0 {
125		paths = append(paths, current.String())
126	}
127
128	return paths
129}