paste.go 2.4 KB

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