Kaynağa Gözat

SVI Обновление вендоринга

SVI 2 yıl önce
ebeveyn
işleme
3616e9acb6
100 değiştirilmiş dosya ile 4065 ekleme ve 788 silme
  1. 22 19
      go.mod
  2. 44 55
      go.sum
  3. 27 3
      vendor/fyne.io/fyne/v2/CHANGELOG.md
  4. 14 15
      vendor/fyne.io/fyne/v2/README.md
  5. 5 5
      vendor/fyne.io/fyne/v2/app/app_mobile_and.c
  6. 8 1
      vendor/fyne.io/fyne/v2/container/doctabs.go
  7. 6 0
      vendor/fyne.io/fyne/v2/data/binding/listbinding.go
  8. 6 0
      vendor/fyne.io/fyne/v2/data/binding/treebinding.go
  9. 13 13
      vendor/fyne.io/fyne/v2/internal/driver/mobile/android.c
  10. 1 1
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/GoNativeActivity.java
  11. 6 4
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/android.c
  12. 0 16
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/android.go
  13. 2 2
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/app.go
  14. 2 2
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/x11.c
  15. 3 2
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/x11.go
  16. 4 0
      vendor/fyne.io/fyne/v2/internal/driver/mobile/canvas.go
  17. 3 0
      vendor/fyne.io/fyne/v2/internal/driver/mobile/menu.go
  18. 11 0
      vendor/fyne.io/fyne/v2/internal/driver/mobile/mobileinit/ctx_android.go
  19. 5 5
      vendor/fyne.io/fyne/v2/internal/repository/memory.go
  20. 2 0
      vendor/fyne.io/fyne/v2/internal/svg/svg.go
  21. 1 0
      vendor/fyne.io/fyne/v2/test/theme.go
  22. 1 1
      vendor/fyne.io/fyne/v2/theme/json.go
  23. 8 5
      vendor/fyne.io/fyne/v2/widget/gridwrap.go
  24. 7 4
      vendor/fyne.io/fyne/v2/widget/list.go
  25. 2 2
      vendor/fyne.io/fyne/v2/widget/richtext.go
  26. 13 11
      vendor/fyne.io/fyne/v2/widget/table.go
  27. 18 10
      vendor/fyne.io/fyne/v2/widget/tree.go
  28. 0 8
      vendor/github.com/andybalholm/brotli/http.go
  29. 13 0
      vendor/github.com/fsnotify/fsnotify/.cirrus.yml
  30. 1 0
      vendor/github.com/fsnotify/fsnotify/.gitignore
  31. 77 6
      vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
  32. 52 29
      vendor/github.com/fsnotify/fsnotify/README.md
  33. 515 37
      vendor/github.com/fsnotify/fsnotify/backend_fen.go
  34. 256 121
      vendor/github.com/fsnotify/fsnotify/backend_inotify.go
  35. 185 110
      vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
  36. 172 33
      vendor/github.com/fsnotify/fsnotify/backend_other.go
  37. 164 83
      vendor/github.com/fsnotify/fsnotify/backend_windows.go
  38. 78 13
      vendor/github.com/fsnotify/fsnotify/fsnotify.go
  39. 88 37
      vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
  40. 1 0
      vendor/github.com/go-gl/gl/v2.1/gl/KHR/dummy.go
  41. 1 0
      vendor/github.com/go-gl/gl/v2.1/gl/build_cgo_hack.go
  42. 3 3
      vendor/github.com/go-gl/gl/v2.1/gl/conversions.go
  43. 1 1
      vendor/github.com/go-gl/gl/v2.1/gl/package.go
  44. 5 5
      vendor/github.com/go-gl/gl/v2.1/gl/procaddr.go
  45. 1 0
      vendor/github.com/go-gl/gl/v3.1/gles2/KHR/dummy.go
  46. 1 0
      vendor/github.com/go-gl/gl/v3.1/gles2/build_cgo_hack.go
  47. 3 3
      vendor/github.com/go-gl/gl/v3.1/gles2/conversions.go
  48. 1 1
      vendor/github.com/go-gl/gl/v3.1/gles2/package.go
  49. 5 5
      vendor/github.com/go-gl/gl/v3.1/gles2/procaddr.go
  50. 1 0
      vendor/github.com/go-gl/gl/v3.2-core/gl/KHR/dummy.go
  51. 1 0
      vendor/github.com/go-gl/gl/v3.2-core/gl/build_cgo_hack.go
  52. 3 3
      vendor/github.com/go-gl/gl/v3.2-core/gl/conversions.go
  53. 1 1
      vendor/github.com/go-gl/gl/v3.2-core/gl/package.go
  54. 5 5
      vendor/github.com/go-gl/gl/v3.2-core/gl/procaddr.go
  55. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/build_cgo_hack.go
  56. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/c_glfw_bsd.go
  57. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/c_glfw_lin.go
  58. 1 2
      vendor/github.com/go-gl/glfw/v3.3/glfw/error.go
  59. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/dummy.go
  60. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/glad/dummy.go
  61. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/mingw/dummy.go
  62. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/vs2008/dummy.go
  63. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/include/GLFW/dummy.go
  64. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/dummy.go
  65. 1 19
      vendor/github.com/go-gl/glfw/v3.3/glfw/input.go
  66. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/native_linbsd_wayland.go
  67. 1 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/native_linbsd_x11.go
  68. 26 0
      vendor/github.com/go-gl/glfw/v3.3/glfw/util.go
  69. 1 19
      vendor/github.com/go-gl/glfw/v3.3/glfw/window.go
  70. 6 0
      vendor/github.com/go-text/typesetting/opentype/api/font/variations.go
  71. 12 0
      vendor/github.com/go-text/typesetting/opentype/loader/reader.go
  72. 7 2
      vendor/github.com/go-text/typesetting/opentype/loader/reader_otf.go
  73. 80 0
      vendor/github.com/go-text/typesetting/opentype/loader/writer.go
  74. 23 0
      vendor/github.com/go-text/typesetting/shaping/output.go
  75. 53 25
      vendor/github.com/go-text/typesetting/shaping/wrapping.go
  76. 2 2
      vendor/github.com/gofiber/fiber/v2/app.go
  77. 78 29
      vendor/github.com/gofiber/fiber/v2/ctx.go
  78. 1 1
      vendor/github.com/gofiber/fiber/v2/helpers.go
  79. 2 1
      vendor/github.com/gofiber/fiber/v2/listen.go
  80. 5 1
      vendor/github.com/gofiber/fiber/v2/log/default.go
  81. 65 0
      vendor/github.com/gofiber/fiber/v2/middleware/compress/compress.go
  82. 56 0
      vendor/github.com/gofiber/fiber/v2/middleware/compress/config.go
  83. 2 1
      vendor/github.com/gofiber/fiber/v2/path.go
  84. 2 1
      vendor/github.com/gofiber/fiber/v2/prefork.go
  85. 1 1
      vendor/github.com/gofiber/fiber/v2/utils/assertions.go
  86. 0 1
      vendor/github.com/gofiber/fiber/v2/utils/convert_b2s_new.go
  87. 0 1
      vendor/github.com/gofiber/fiber/v2/utils/convert_b2s_old.go
  88. 0 1
      vendor/github.com/gofiber/fiber/v2/utils/convert_s2b_new.go
  89. 0 1
      vendor/github.com/gofiber/fiber/v2/utils/convert_s2b_old.go
  90. 33 0
      vendor/github.com/gofiber/template/.gitignore
  91. 284 0
      vendor/github.com/gofiber/template/.golangci.yml
  92. 21 0
      vendor/github.com/gofiber/template/LICENSE
  93. 250 0
      vendor/github.com/gofiber/template/README.md
  94. 21 0
      vendor/github.com/gofiber/template/html/v2/LICENSE
  95. 203 0
      vendor/github.com/gofiber/template/html/v2/README.md
  96. 582 0
      vendor/github.com/gofiber/template/html/v2/TEMPLATES_CHEATSHEET.md
  97. 156 0
      vendor/github.com/gofiber/template/html/v2/html.go
  98. 104 0
      vendor/github.com/gofiber/template/template.go
  99. 21 0
      vendor/github.com/gofiber/utils/LICENSE
  100. 87 0
      vendor/github.com/gofiber/utils/README.md

+ 22 - 19
go.mod

@@ -3,37 +3,40 @@ module wartank
 go 1.20
 
 require (
-	fyne.io/fyne/v2 v2.4.0
+	fyne.io/fyne/v2 v2.4.1
 	github.com/charmbracelet/bubbletea v0.24.2
-	github.com/gofiber/fiber/v2 v2.49.2
+	github.com/gofiber/fiber/v2 v2.50.0
+	github.com/gofiber/template/html/v2 v2.0.5
 	github.com/sirupsen/logrus v1.9.3
 	github.com/syndtr/goleveldb v1.0.0
 )
 
 require (
 	fyne.io/systray v1.10.1-0.20230722100817-88df1e0ffa9a // indirect
-	github.com/andybalholm/brotli v1.0.5 // indirect
+	github.com/andybalholm/brotli v1.0.6 // indirect
 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/fredbi/uri v1.1.0 // indirect
-	github.com/fsnotify/fsnotify v1.6.0 // indirect
+	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934 // indirect
 	github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 // indirect
 	github.com/fyne-io/image v0.0.0-20230811065323-ed435dc8bca6 // indirect
-	github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
-	github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
+	github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
+	github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231102141658-eca20e8abded // indirect
 	github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 // indirect
-	github.com/go-text/typesetting v0.0.0-20230905121921-abdbcca6e0eb // indirect
+	github.com/go-text/typesetting v0.0.0-20231110223828-31a9559ebc00 // indirect
 	github.com/godbus/dbus/v5 v5.1.0 // indirect
+	github.com/gofiber/template v1.8.2 // indirect
+	github.com/gofiber/utils v1.1.0 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
-	github.com/google/uuid v1.3.1 // indirect
+	github.com/google/uuid v1.4.0 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
 	github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
-	github.com/klauspost/compress v1.17.0 // indirect
+	github.com/klauspost/compress v1.17.2 // indirect
 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
-	github.com/mattn/go-isatty v0.0.19 // 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/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
@@ -49,14 +52,14 @@ require (
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
 	github.com/valyala/fasthttp v1.50.0 // indirect
 	github.com/valyala/tcplisten v1.0.0 // indirect
-	github.com/yuin/goldmark v1.5.6 // indirect
-	golang.org/x/image v0.12.0 // indirect
-	golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 // indirect
-	golang.org/x/net v0.15.0 // indirect
-	golang.org/x/sync v0.3.0 // indirect
-	golang.org/x/sys v0.12.0 // indirect
-	golang.org/x/term v0.12.0 // indirect
-	golang.org/x/text v0.13.0 // indirect
+	github.com/yuin/goldmark v1.6.0 // indirect
+	golang.org/x/image v0.14.0 // indirect
+	golang.org/x/mobile v0.0.0-20231108233038-35478a0c49da // indirect
+	golang.org/x/net v0.18.0 // indirect
+	golang.org/x/sync v0.5.0 // indirect
+	golang.org/x/sys v0.14.0 // indirect
+	golang.org/x/term v0.14.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	honnef.co/go/js/dom v0.0.0-20230808055721-96db8f4d5e3b // indirect
+	honnef.co/go/js/dom v0.0.0-20231030024858-cb489e859d05 // indirect
 )

+ 44 - 55
go.sum

@@ -37,14 +37,14 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-fyne.io/fyne/v2 v2.4.0 h1:LlyOyHmvkSo9IBm3aY+NVWSBIw+GMnssmyyIMK8F7zM=
-fyne.io/fyne/v2 v2.4.0/go.mod h1:AWM1iPM2YfliduZ4u/kQzP9E6ARIWm0gg+57GpYzWro=
+fyne.io/fyne/v2 v2.4.1 h1:Es100N6HIhJGg8H2ZAS2j5H/YibfxecXHs2V4A4hbq8=
+fyne.io/fyne/v2 v2.4.1/go.mod h1:AWM1iPM2YfliduZ4u/kQzP9E6ARIWm0gg+57GpYzWro=
 fyne.io/systray v1.10.1-0.20230722100817-88df1e0ffa9a h1:6Xf9fP3/mt72NrqlQhJWhQGcNf6GoG9X96NTaXr+K6A=
 fyne.io/systray v1.10.1-0.20230722100817-88df1e0ffa9a/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
-github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
+github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -84,8 +84,8 @@ github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
 github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
-github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934 h1:dZC5aKobSN07hf71oMivxUmAofFja5GrfPK2rBlttX4=
 github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
 github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 h1:0Ayg0/do/sqX2R7NonoLZvWxGrd9utTVf3A0QvCbC88=
@@ -93,24 +93,31 @@ github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33/go.mod h1:gLRWYfYn
 github.com/fyne-io/image v0.0.0-20230811065323-ed435dc8bca6 h1:kZNUHSV3ZTddRiWy5JHK6RgB3zdH/875SYXmt3EoNvQ=
 github.com/fyne-io/image v0.0.0-20230811065323-ed435dc8bca6/go.mod h1:aX1w6epS9BQn2bePY+3rkQejetaffeFhXl0s8QjXJJk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
 github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
+github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
+github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231102141658-eca20e8abded h1:W0pE6Cn9okp21yrBqVzPDf2NLK6J+i/f6XlqxQJ9iOw=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231102141658-eca20e8abded/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 h1:VkKnvzbvHqgEfm351rfr8Uclu5fnwq8HP2ximUzJsBM=
 github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8/go.mod h1:h29xCucjNsDcYb7+0rJokxVwYAq+9kQ19WiFuBKkYtc=
-github.com/go-text/typesetting v0.0.0-20230905121921-abdbcca6e0eb h1:4GpJirtA8yY24aqbU3uppiXGYiVpWfLIrqc2NNKKk9s=
-github.com/go-text/typesetting v0.0.0-20230905121921-abdbcca6e0eb/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
+github.com/go-text/typesetting v0.0.0-20231110223828-31a9559ebc00 h1:/yTFNi/eblKZMs407CQ7bdpXrHAcq9y/kQG4tPJ0n68=
+github.com/go-text/typesetting v0.0.0-20231110223828-31a9559ebc00/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
 github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
 github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gofiber/fiber/v2 v2.49.2 h1:ONEN3/Vc+dUCxxDgZZwpqvhISgHqb+bu+isBiEyKEQs=
-github.com/gofiber/fiber/v2 v2.49.2/go.mod h1:gNsKnyrmfEWFpJxQAV0qvW6l70K1dZGno12oLtukcts=
+github.com/gofiber/fiber/v2 v2.50.0 h1:ia0JaB+uw3GpNSCR5nvC5dsaxXjRU5OEu36aytx+zGw=
+github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw=
+github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk=
+github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
+github.com/gofiber/template/html/v2 v2.0.5 h1:BKLJ6Qr940NjntbGmpO3zVa4nFNGDCi/IfUiDB9OC20=
+github.com/gofiber/template/html/v2 v2.0.5/go.mod h1:RCF14eLeQDCSUPp0IGc2wbSSDv6yt+V54XB/+Unz+LM=
+github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
+github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -176,8 +183,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
-github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
+github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -220,8 +227,8 @@ github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6U
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
-github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
+github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -236,8 +243,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
-github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+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.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
@@ -333,9 +340,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.0/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.6 h1:COmQAWTCcGetChm3Ig7G/t8AFAN00t+o8Mt4cf7JpwA=
-github.com/yuin/goldmark v1.5.6/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
+github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
 go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
@@ -357,7 +363,6 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -371,8 +376,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
-golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
+golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
+golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -388,8 +393,8 @@ golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPI
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
-golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 h1:Q6NT8ckDYNcwmi/bmxe+XbiDMXqMRW1xFBtJ+bIpie4=
-golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57/go.mod h1:wEyOn6VvNW7tcf+bW/wBz1sehi2s2BZ4TimyR7qZen4=
+golang.org/x/mobile v0.0.0-20231108233038-35478a0c49da h1:gS9sVMAeHM+gVBmM9bTM6vUi/NHv58O3QzJ3vjjN84M=
+golang.org/x/mobile v0.0.0-20231108233038-35478a0c49da/go.mod h1:IEceR0jfVklLJXrbUe90rfdAFAYDW0SQwKl4qvO1GBQ=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@@ -399,8 +404,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -439,10 +442,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
-golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
+golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -466,10 +467,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
-golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
+golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -516,21 +515,15 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/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-20220715151400-c0bba94af5f8/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220908164124-27713097b956/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.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.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.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
-golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
+golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -539,10 +532,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -599,8 +590,6 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
-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=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -727,8 +716,8 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
-honnef.co/go/js/dom v0.0.0-20230808055721-96db8f4d5e3b h1:xXAdf0+OXZAdmc4cewENXYdfry1u03Fax3ucj3F9Duw=
-honnef.co/go/js/dom v0.0.0-20230808055721-96db8f4d5e3b/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
+honnef.co/go/js/dom v0.0.0-20231030024858-cb489e859d05 h1:RfirDq7o2ELiU+mAxRB4AO3u7czT1QEg6SZgSBXeSus=
+honnef.co/go/js/dom v0.0.0-20231030024858-cb489e859d05/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 27 - 3
vendor/fyne.io/fyne/v2/CHANGELOG.md

@@ -3,9 +3,33 @@
 This file lists the main changes with each version of the Fyne toolkit.
 More detailed release notes can be found on the [releases page](https://github.com/fyne-io/fyne/releases). 
 
+## 2.4.1 - 8 October 2023
+
+### Fixed
+
+* Left key on tree now collapses open branch
+* Avoid memory leak in Android driver code
+* Entry Field on Android in Landscape Mode Shows "0" (#4036)
+* DocTabs Indicator remains visible after last tab is removed (#4220)
+* Some SVG resources don't update appearance correctly with the theme (#3900)
+* Fix mobile simulation builds on OpenBSD
+* Fix alignment of menu button on mobile
+* Fix Compilation with Android NDK r26
+* Clicking table headers causes high CPU consumption (#4264)
+* Frequent clicking on table may cause the program to not respond (#4210)
+* Application stops responding when scrolling a table (#4263)
+* Possible crash parsing malformed JSON color (#4270)
+* NewFolderOpen: incomplete filenames (#2165)
+* Resolve issue where storage.List could crash with short URI (#4271)
+* TextTruncateEllipsis abnormally truncates strings with multi-byte UTF-8 characters (#4283)
+* Last character doesn't appear in Select when there is a special character (#4293)
+* Resolve random crash in DocTab (#3909)
+* Selecting items from a list caused the keyboard to popup on Android (#4236)
+
+
 ## 2.4.0 - 1 September 2023
 
-## Added
+### Added
 
 * Rounded corners in rectangle (#1090)
 * Support for emoji in text
@@ -43,7 +67,7 @@ More detailed release notes can be found on the [releases page](https://github.c
 * Add `--pprof` option to fyne build commands to enable profiling
 * Support compiling from Android (termux)
 
-## Changed
+### Changed
 
 * Go 1.17 or later is now required.
 * Theme updated for rounded corners on buttons and input widgets
@@ -60,7 +84,7 @@ More detailed release notes can be found on the [releases page](https://github.c
 * Improving performance of lookup for theme data
 * Improved application startup time
 
-## Fixed
+### Fixed
 
 * Rendering performance enhancements
 * `dialog.NewProgressInfinite` is deprecated, but dialog.NewCustom isn't equivalent

+ 14 - 15
vendor/fyne.io/fyne/v2/README.md

@@ -1,7 +1,7 @@
 <p align="center">
   <a href="https://pkg.go.dev/fyne.io/fyne/v2?tab=doc" title="Go API Reference" rel="nofollow"><img src="https://img.shields.io/badge/go-documentation-blue.svg?style=flat" alt="Go API Reference"></a>
   <a href="https://img.shields.io/github/v/release/fyne-io/fyne?include_prereleases" title="Latest Release" rel="nofollow"><img src="https://img.shields.io/github/v/release/fyne-io/fyne?include_prereleases" alt="Latest Release"></a>
-  <a href='http://gophers.slack.com/messages/fyne'><img src='https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=blue' alt='Join us on Slack' /></a>
+  <a href='https://gophers.slack.com/messages/fyne'><img src='https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=blue' alt='Join us on Slack' /></a>
   <br />
   <a href="https://goreportcard.com/report/fyne.io/fyne/v2"><img src="https://goreportcard.com/badge/fyne.io/fyne/v2" alt="Code Status" /></a>
   <a href="https://github.com/fyne-io/fyne/actions"><img src="https://github.com/fyne-io/fyne/workflows/Platform%20Tests/badge.svg" alt="Build Status" /></a>
@@ -14,22 +14,22 @@
 It is designed to build applications that run on desktop and mobile devices with a
 single codebase.
 
-Version 2.3 is the current release of the Fyne API, it added a refined theme design,
-cloud storage, improved text handling for international languages and many
+Version 2.4 is the current release of the Fyne API, it added rounded corners, emoji,
+layout debug support and table headers, along with a large number of
 smaller feature additions.
 We are now working towards the next big release, codenamed
-[Dalwhinnie](https://github.com/fyne-io/fyne/milestone/18)
+[Elgin](https://github.com/fyne-io/fyne/milestone/21)
 and more news will follow in our news feeds and GitHub project.
 
 # Prerequisites
 
-To develop apps using Fyne you will need Go version 1.14 or later, a C compiler and your system's development tools.
+To develop apps using Fyne you will need Go version 1.17 or later, a C compiler and your system's development tools.
 If you're not sure if that's all installed or you don't know how then check out our
 [Getting Started](https://fyne.io/develop/) document.
 
 Using the standard go tools you can install Fyne's core library using:
 
-    go get fyne.io/fyne/v2
+    go get fyne.io/fyne/v2@latest
 
 After importing a new module, run the following command before compiling the code for the first time. Avoid running it before writing code that uses the module to prevent accidental removal of dependencies:
 
@@ -45,19 +45,19 @@ To run a showcase of the features of Fyne execute the following:
 And you should see something like this (after you click a few buttons):
 
 <p align="center" markdown="1" style="max-width: 100%">
-  <img src="img/widgets-dark.png" width="752" height="617" alt="Fyne Demo Dark Theme" style="max-width: 100%" />
+  <img src="img/widgets-dark.png" width="752" alt="Fyne Demo Dark Theme" style="max-width: 100%" />
 </p>
 
 Or if you are using the light theme:
 
 <p align="center" markdown="1" style="max-width: 100%">
-  <img src="img/widgets-light.png" width="752" height="617" alt="Fyne Demo Light Theme" style="max-width: 100%" />
+  <img src="img/widgets-light.png" width="752" alt="Fyne Demo Light Theme" style="max-width: 100%" />
 </p>
 
 And even running on a mobile device:
 
 <p align="center" markdown="1" style="max-width: 100%">
-  <img src="img/widgets-mobile-light.png" width="348" height="617" alt="Fyne Demo Mobile Light Theme" style="max-width: 100%" />
+  <img src="img/widgets-mobile-light.png" width="348" alt="Fyne Demo Mobile Light Theme" style="max-width: 100%" />
 </p>
 
 # Getting Started
@@ -102,16 +102,13 @@ It should look like this:
 <div align="center">
   <table cellpadding="0" cellspacing="0" style="margin: auto; border-collapse: collapse;">
     <tr style="border: none;"><td style="border: none;">
-      <img src="img/hello-light.png" width="207" height="212" alt="Fyne Hello Dark Theme" />
+      <img src="img/hello-light.png" width="207" alt="Fyne Hello Dark Theme" />
     </td><td style="border: none;">
-      <img src="img/hello-dark.png" width="207" height="212" alt="Fyne Hello Dark Theme" />
+      <img src="img/hello-dark.png" width="207" alt="Fyne Hello Dark Theme" />
     </td></tr>
   </table>
 </div>
 
-> Note that Windows applications load from a command prompt by default, which means if you click an icon you may see a command window.
-> To fix this add the parameters `-ldflags -H=windowsgui` to your run or build commands.
-
 ## Run in mobile simulation
 
 There is a helpful mobile simulation mode that gives a hint of how your app would work on a mobile device:
@@ -186,4 +183,6 @@ These are optional applications but can help to create a more complete desktop e
 
 ## FyneDesk (Linux / BSD)
 
-To go all the way with Fyne on your desktop / laptop computer you could install [FyneDesk](https://github.com/fyne-io/fynedesk) as well :)
+To go all the way with Fyne on your desktop / laptop computer you could install [FyneDesk](https://github.com/fyshos/fynedesk) as well :)
+
+![FyneDesk screenshopt in dark mode](https://fyshos.com/img/desktop.png)

+ 5 - 5
vendor/fyne.io/fyne/v2/app/app_mobile_and.c

@@ -42,10 +42,10 @@ jobject getSystemService(uintptr_t jni_env, uintptr_t ctx, char *service) {
 	JNIEnv *env = (JNIEnv*)jni_env;
 	jstring serviceStr = (*env)->NewStringUTF(env, service);
 
-	jclass ctxClass = (*env)->GetObjectClass(env, ctx);
+	jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx);
 	jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
 
-	return (jobject)(*env)->CallObjectMethod(env, ctx, getSystemService, serviceStr);
+	return (jobject)(*env)->CallObjectMethod(env, (jobject)ctx, getSystemService, serviceStr);
 }
 
 int nextId = 1;
@@ -81,7 +81,7 @@ void openURL(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *url) {
 
 	jclass contextClass = find_class(env, "android/content/Context");
 	jmethodID start = find_method(env, contextClass, "startActivity", "(Landroid/content/Intent;)V");
-	(*env)->CallVoidMethod(env, ctx, start, intent);
+	(*env)->CallVoidMethod(env, (jobject)ctx, start, intent);
 }
 
 void sendNotification(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *title, char *body) {
@@ -94,7 +94,7 @@ void sendNotification(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char
 	jobject builder = (*env)->NewObject(env, cls, constructor, ctx);
 
 	jclass mgrCls = find_class(env, "android/app/NotificationManager");
-	jobject mgr = getSystemService(env, ctx, "notification");
+	jobject mgr = getSystemService((uintptr_t)env, ctx, "notification");
 
 	if (isOreoOrLater(env)) {
 		jstring channelId = (*env)->NewStringUTF(env, "fyne-notif");
@@ -128,4 +128,4 @@ void sendNotification(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char
 	jmethodID notify = find_method(env, mgrCls, "notify", "(ILandroid/app/Notification;)V");
 	(*env)->CallVoidMethod(env, mgr, notify, nextId, notif);
 	nextId++;
-}
+}

+ 8 - 1
vendor/fyne.io/fyne/v2/container/doctabs.go

@@ -363,6 +363,13 @@ func (r *docTabsRenderer) buildTabButtons(count int, buttons *fyne.Container) {
 
 func (r *docTabsRenderer) scrollToSelected() {
 	buttons := r.scroller.Content.(*fyne.Container)
+
+	// https://github.com/fyne-io/fyne/issues/3909
+	// very dirty temporary fix to this crash!
+	if r.docTabs.current < 0 || r.docTabs.current >= len(buttons.Objects) {
+		return
+	}
+
 	button := buttons.Objects[r.docTabs.current]
 	pos := button.Position()
 	size := button.Size()
@@ -388,7 +395,7 @@ func (r *docTabsRenderer) scrollToSelected() {
 func (r *docTabsRenderer) updateIndicator(animate bool) {
 	if r.docTabs.current < 0 {
 		r.indicator.FillColor = color.Transparent
-		r.indicator.Refresh()
+		r.moveIndicator(fyne.NewPos(0, 0), fyne.NewSize(0, 0), animate)
 		return
 	}
 

+ 6 - 0
vendor/fyne.io/fyne/v2/data/binding/listbinding.go

@@ -16,6 +16,9 @@ type listBase struct {
 
 // GetItem returns the DataItem at the specified index.
 func (b *listBase) GetItem(i int) (DataItem, error) {
+	b.lock.RLock()
+	defer b.lock.RUnlock()
+
 	if i < 0 || i >= len(b.items) {
 		return nil, errOutOfBounds
 	}
@@ -25,6 +28,9 @@ func (b *listBase) GetItem(i int) (DataItem, error) {
 
 // Length returns the number of items in this data list.
 func (b *listBase) Length() int {
+	b.lock.RLock()
+	defer b.lock.RUnlock()
+
 	return len(b.items)
 }
 

+ 6 - 0
vendor/fyne.io/fyne/v2/data/binding/treebinding.go

@@ -21,6 +21,9 @@ type treeBase struct {
 
 // GetItem returns the DataItem at the specified id.
 func (t *treeBase) GetItem(id string) (DataItem, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
 	if item, ok := t.items[id]; ok {
 		return item, nil
 	}
@@ -30,6 +33,9 @@ func (t *treeBase) GetItem(id string) (DataItem, error) {
 
 // ChildIDs returns the ordered IDs of items in this data tree that are children of the specified ID.
 func (t *treeBase) ChildIDs(id string) []string {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
 	if ids, ok := t.ids[id]; ok {
 		return ids
 	}

+ 13 - 13
vendor/fyne.io/fyne/v2/internal/driver/mobile/android.c

@@ -41,7 +41,7 @@ static jmethodID find_static_method(JNIEnv *env, jclass clazz, const char *name,
 	return m;
 }
 
-char* getString(uintptr_t jni_env, uintptr_t ctx, jstring str) {
+const char* getString(uintptr_t jni_env, uintptr_t ctx, jstring str) {
 	JNIEnv *env = (JNIEnv*)jni_env;
 
 	const char *chars = (*env)->GetStringUTFChars(env, str, NULL);
@@ -65,11 +65,11 @@ jobject parseURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
 
 jobject getClipboard(uintptr_t jni_env, uintptr_t ctx) {
 	JNIEnv *env = (JNIEnv*)jni_env;
-	jclass ctxClass = (*env)->GetObjectClass(env, ctx);
+	jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx);
 	jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
 
 	jstring service = (*env)->NewStringUTF(env, "clipboard");
-	jobject ret = (jobject)(*env)->CallObjectMethod(env, ctx, getSystemService, service);
+	jobject ret = (*env)->CallObjectMethod(env, (jobject)ctx, getSystemService, service);
 	jthrowable err = (*env)->ExceptionOccurred(env);
 
 	if (err != NULL) {
@@ -80,7 +80,7 @@ jobject getClipboard(uintptr_t jni_env, uintptr_t ctx) {
 	return ret;
 }
 
-char *getClipboardContent(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
+const char *getClipboardContent(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
 	JNIEnv *env = (JNIEnv*)jni_env;
 	jobject mgr = getClipboard(jni_env, ctx);
 	if (mgr == NULL) {
@@ -120,10 +120,10 @@ void setClipboardContent(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, ch
 
 jobject getContentResolver(uintptr_t jni_env, uintptr_t ctx) {
 	JNIEnv *env = (JNIEnv*)jni_env;
-	jclass ctxClass = (*env)->GetObjectClass(env, ctx);
+	jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx);
 	jmethodID getContentResolver = find_method(env, ctxClass, "getContentResolver", "()Landroid/content/ContentResolver;");
 
-	return (jobject)(*env)->CallObjectMethod(env, ctx, getContentResolver);
+	return (jobject)(*env)->CallObjectMethod(env, (jobject)ctx, getContentResolver);
 }
 
 void* openStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
@@ -165,7 +165,7 @@ void* saveStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
 	return (*env)->NewGlobalRef(env, stream);
 }
 
-char* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* total) {
+jbyte* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* total) {
 	JNIEnv *env = (JNIEnv*)jni_env;
 	jclass streamClass = (*env)->GetObjectClass(env, stream);
 	jmethodID read = find_method(env, streamClass, "read", "([BII)I");
@@ -178,12 +178,12 @@ char* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* t
 		return NULL;
 	}
 
-	char* bytes = malloc(sizeof(char)*count);
+	jbyte* bytes = (jbyte*)malloc(sizeof(jbyte)*count);
 	(*env)->GetByteArrayRegion(env, data, 0, count, bytes);
 	return bytes;
 }
 
-void writeStream(uintptr_t jni_env, uintptr_t ctx, void* stream, char* buf, int len) {
+void writeStream(uintptr_t jni_env, uintptr_t ctx, void* stream, jbyte* buf, int len) {
 	JNIEnv *env = (JNIEnv*)jni_env;
 	jclass streamClass = (*env)->GetObjectClass(env, stream);
 	jmethodID write = find_method(env, streamClass, "write", "([BII)V");
@@ -246,7 +246,7 @@ bool canListContentURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
 		return false;
 	}
 
-	char *str = getString(jni_env, ctx, type);
+	const char *str = getString(jni_env, ctx, type);
 	return strcmp(str, "vnd.android.document/directory") == 0;
 }
 
@@ -297,7 +297,7 @@ bool createListableURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
 	return false;
 }
 
-char* contentURIGetFileName(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
+const char* contentURIGetFileName(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
 	JNIEnv *env = (JNIEnv*)jni_env;
 	jobject resolver = getContentResolver(jni_env, ctx);
 	jobject uri = parseURI(jni_env, ctx, uriCstr);
@@ -322,7 +322,7 @@ char* contentURIGetFileName(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
 
 	if (((jboolean)(*env)->CallBooleanMethod(env, cursor, first)) == JNI_TRUE) {
 		jstring name = (jstring)(*env)->CallObjectMethod(env, cursor, get, 0);
-		char *fname = getString(jni_env, ctx, name);
+		const char *fname = getString(jni_env, ctx, name);
 		return fname;
 	}
 
@@ -401,7 +401,7 @@ char* listContentURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
 		jmethodID toString = (*env)->GetMethodID(env, uriClass, "toString", "()Ljava/lang/String;");
 		jstring s = (jstring)(*env)->CallObjectMethod(env, childUri, toString);
 
-		char *uid = getString(jni_env, ctx, s);
+		const char *uid = getString(jni_env, ctx, s);
 
 		// append
 		char *old = ret;

+ 1 - 1
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/GoNativeActivity.java

@@ -104,7 +104,7 @@ public class GoNativeActivity extends NativeActivity {
                     default:
                         Log.e("Fyne", "unknown keyboard type, use default");
                 }
-                mTextEdit.setImeOptions(imeOptions);
+                mTextEdit.setImeOptions(imeOptions|EditorInfo.IME_FLAG_NO_FULLSCREEN);
                 mTextEdit.setInputType(inputType);
 
                 mTextEdit.setOnEditorActionListener(new OnEditorActionListener() {

+ 6 - 4
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/android.c

@@ -67,13 +67,15 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
 static int main_running = 0;
 
 // ensure we refresh context on resume in case something has changed...
-void processOnResume(ANativeActivity *activity) {
+void onResume(ANativeActivity *activity) {
 	JNIEnv* env = activity->env;
 	setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, activity->clazz));
-
-    onResume(activity);
 }
 
+void onStart(ANativeActivity *activity) {}
+void onPause(ANativeActivity *activity) {}
+void onStop(ANativeActivity *activity) {}
+
 // Entry point from our subclassed NativeActivity.
 //
 // By here, the Go runtime has been initialized (as we are running in
@@ -127,7 +129,7 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_
 	// Note that onNativeWindowResized is not called on resize. Avoid it.
 	// https://code.google.com/p/android/issues/detail?id=180645
 	activity->callbacks->onStart = onStart;
-	activity->callbacks->onResume = processOnResume;
+	activity->callbacks->onResume = onResume;
 	activity->callbacks->onSaveInstanceState = onSaveInstanceState;
 	activity->callbacks->onPause = onPause;
 	activity->callbacks->onStop = onStop;

+ 0 - 16
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/android.go

@@ -131,27 +131,11 @@ func callMain(mainPC uintptr) {
 	go callfn.CallFn(mainPC)
 }
 
-//export onStart
-func onStart(activity *C.ANativeActivity) {
-}
-
-//export onResume
-func onResume(activity *C.ANativeActivity) {
-}
-
 //export onSaveInstanceState
 func onSaveInstanceState(activity *C.ANativeActivity, outSize *C.size_t) unsafe.Pointer {
 	return nil
 }
 
-//export onPause
-func onPause(activity *C.ANativeActivity) {
-}
-
-//export onStop
-func onStop(activity *C.ANativeActivity) {
-}
-
 //export onBackPressed
 func onBackPressed() {
 	k := key.Event{

+ 2 - 2
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/app.go

@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build freebsd || linux || darwin || windows
-// +build freebsd linux darwin windows
+//go:build freebsd || linux || darwin || windows || openbsd
+// +build freebsd linux darwin windows openbsd
 
 package app
 

+ 2 - 2
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/x11.c

@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build (linux && !android) || freebsd
-// +build linux,!android freebsd
+//go:build (linux && !android) || freebsd || openbsd
+// +build linux,!android freebsd openbsd
 
 #include "_cgo_export.h"
 #include <EGL/egl.h>

+ 3 - 2
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/x11.go

@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build (linux && !android) || freebsd
-// +build linux,!android freebsd
+//go:build (linux && !android) || freebsd || openbsd
+// +build linux,!android freebsd openbsd
 
 package app
 
@@ -16,6 +16,7 @@ than screens with touch panels.
 /*
 #cgo LDFLAGS: -lEGL -lGLESv2 -lX11
 #cgo freebsd CFLAGS: -I/usr/local/include/
+#cgo openbsd CFLAGS: -I/usr/X11R6/include/
 
 void createWindow(void);
 void processEvents(void);

+ 4 - 0
vendor/fyne.io/fyne/v2/internal/driver/mobile/canvas.go

@@ -7,6 +7,7 @@ import (
 	"time"
 
 	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/container"
 	"fyne.io/fyne/v2/driver/mobile"
 	"fyne.io/fyne/v2/internal/app"
 	"fyne.io/fyne/v2/internal/driver"
@@ -167,6 +168,9 @@ func (c *mobileCanvas) setMenu(menu fyne.CanvasObject) {
 }
 
 func (c *mobileCanvas) setWindowHead(head fyne.CanvasObject) {
+	if c.padded {
+		head = container.NewPadded(head)
+	}
 	c.windowHead = head
 	c.SetMobileWindowHeadTree(head)
 }

+ 3 - 0
vendor/fyne.io/fyne/v2/internal/driver/mobile/menu.go

@@ -55,6 +55,9 @@ func (c *mobileCanvas) showMenu(menu *fyne.MainMenu) {
 	for _, item := range menu.Items {
 		panel.Add(newMenuLabel(item, panel, c))
 	}
+	if c.padded {
+		panel = container.NewPadded(panel)
+	}
 
 	bg := canvas.NewRectangle(theme.BackgroundColor())
 	shadow := canvas.NewHorizontalGradient(theme.ShadowColor(), color.Transparent)

+ 11 - 0
vendor/fyne.io/fyne/v2/internal/driver/mobile/mobileinit/ctx_android.go

@@ -55,6 +55,11 @@ static char* checkException(uintptr_t jnienv) {
 static void unlockJNI(JavaVM *vm) {
 	(*vm)->DetachCurrentThread(vm);
 }
+
+static void deletePrevCtx(JNIEnv* env,jobject ctx){
+    if (ctx == NULL) { return; }
+    (*env)->DeleteGlobalRef(env, ctx);
+}
 */
 import "C"
 
@@ -80,7 +85,13 @@ var currentCtx C.jobject
 // The android.context.Context object must be a global reference.
 func SetCurrentContext(vm unsafe.Pointer, ctx uintptr) {
 	currentVM = (*C.JavaVM)(vm)
+	currentCtxPrev := currentCtx
 	currentCtx = (C.jobject)(ctx)
+	RunOnJVM(func(vm, jniEnv, ctx uintptr) error {
+		env := (*C.JNIEnv)(unsafe.Pointer(jniEnv))
+		C.deletePrevCtx(env, C.jobject(currentCtxPrev))
+		return nil
+	})
 }
 
 // RunOnJVM runs fn on a new goroutine locked to an OS thread with a JNIEnv.

+ 5 - 5
vendor/fyne.io/fyne/v2/internal/repository/memory.go

@@ -1,13 +1,13 @@
 package repository
 
 import (
-	"fyne.io/fyne/v2"
-	"fyne.io/fyne/v2/storage"
-	"fyne.io/fyne/v2/storage/repository"
-
 	"fmt"
 	"io"
 	"strings"
+
+	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/storage"
+	"fyne.io/fyne/v2/storage/repository"
 )
 
 // declare conformance to interfaces
@@ -297,7 +297,7 @@ func (m *InMemoryRepository) List(u fyne.URI) ([]fyne.URI, error) {
 		// does not have one.
 		pSplit := strings.Split(p, "/")
 		ncomp := len(pSplit)
-		if p[len(p)-1] == '/' {
+		if len(p) > 0 && p[len(p)-1] == '/' {
 			ncomp--
 		}
 

+ 2 - 0
vendor/fyne.io/fyne/v2/internal/svg/svg.go

@@ -212,6 +212,7 @@ type objGroup struct {
 	Ellipses        []*ellipseObj `xml:"ellipse"`
 	Rects           []*rectObj    `xml:"rect"`
 	Polygons        []*polygonObj `xml:"polygon"`
+	Groups          []*objGroup   `xml:"g"`
 }
 
 func replacePathsFill(paths []*pathObj, hexColor string, opacity string) {
@@ -266,6 +267,7 @@ func replaceGroupObjectFill(groups []*objGroup, hexColor string, opacity string)
 		replacePathsFill(grp.Paths, hexColor, opacity)
 		replaceRectsFill(grp.Rects, hexColor, opacity)
 		replacePolygonsFill(grp.Polygons, hexColor, opacity)
+		replaceGroupObjectFill(grp.Groups, hexColor, opacity)
 	}
 }
 

+ 1 - 0
vendor/fyne.io/fyne/v2/test/theme.go

@@ -31,6 +31,7 @@ func Theme() fyne.Theme {
 				theme.ColorNameForeground:        color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff},
 				theme.ColorNameHover:             color.NRGBA{R: 0x88, G: 0xff, B: 0xff, A: 0x22},
 				theme.ColorNameHeaderBackground:  color.NRGBA{R: 0x22, G: 0x22, B: 0x22, A: 0xff},
+				theme.ColorNameHyperlink:         color.NRGBA{R: 0xff, G: 0xcc, B: 0x80, A: 0xff},
 				theme.ColorNameInputBackground:   color.NRGBA{R: 0x66, G: 0x66, B: 0x66, A: 0xff},
 				theme.ColorNameInputBorder:       color.NRGBA{R: 0x86, G: 0x86, B: 0x86, A: 0xff},
 				theme.ColorNameMenuBackground:    color.NRGBA{R: 0x56, G: 0x56, B: 0x56, A: 0xff},

+ 1 - 1
vendor/fyne.io/fyne/v2/theme/json.go

@@ -39,7 +39,7 @@ type hexColor string
 
 func (h hexColor) color() (color.Color, error) {
 	data := h
-	switch len(h) {
+	switch len([]rune(h)) {
 	case 8, 6:
 	case 9, 7: // remove # prefix
 		data = h[1:]

+ 8 - 5
vendor/fyne.io/fyne/v2/widget/gridwrap.go

@@ -528,13 +528,16 @@ func (l *gridWrapLayout) setupGridItem(li *gridWrapItem, id GridWrapItemID, focu
 		f(id, li.child)
 	}
 	li.onTapped = func() {
-		l.list.RefreshItem(l.list.currentFocus)
-		canvas := fyne.CurrentApp().Driver().CanvasForObject(l.list)
-		if canvas != nil {
-			canvas.Focus(l.list)
+		if !fyne.CurrentDevice().IsMobile() {
+			l.list.RefreshItem(l.list.currentFocus)
+			canvas := fyne.CurrentApp().Driver().CanvasForObject(l.list)
+			if canvas != nil {
+				canvas.Focus(l.list)
+			}
+
+			l.list.currentFocus = id
 		}
 
-		l.list.currentFocus = id
 		l.list.Select(id)
 	}
 }

+ 7 - 4
vendor/fyne.io/fyne/v2/widget/list.go

@@ -611,12 +611,15 @@ func (l *listLayout) setupListItem(li *listItem, id ListItemID, focus bool) {
 		f(id, li.child)
 	}
 	li.onTapped = func() {
-		canvas := fyne.CurrentApp().Driver().CanvasForObject(l.list)
-		if canvas != nil {
-			canvas.Focus(l.list)
+		if !fyne.CurrentDevice().IsMobile() {
+			canvas := fyne.CurrentApp().Driver().CanvasForObject(l.list)
+			if canvas != nil {
+				canvas.Focus(l.list)
+			}
+
+			l.list.currentFocus = id
 		}
 
-		l.list.currentFocus = id
 		l.list.Select(id)
 	}
 }

+ 2 - 2
vendor/fyne.io/fyne/v2/widget/richtext.go

@@ -1017,8 +1017,8 @@ func lineBounds(seg *TextSegment, wrap fyne.TextWrap, trunc fyne.TextTruncation,
 			}
 		default:
 			if trunc == fyne.TextTruncateEllipsis {
-				txt := seg.Text[low:high]
-				end, full := truncateLimit(txt, seg.Visual().(*canvas.Text), int(measureWidth), []rune{'…'})
+				txt := []rune(seg.Text)[low:high]
+				end, full := truncateLimit(string(txt), seg.Visual().(*canvas.Text), int(measureWidth), []rune{'…'})
 				high = low + end
 				bounds = append(bounds, rowBoundary{[]RichTextSegment{seg}, reuse, low, high, !full})
 				reuse++

+ 13 - 11
vendor/fyne.io/fyne/v2/widget/table.go

@@ -40,7 +40,7 @@ type TableCellID struct {
 type Table struct {
 	BaseWidget
 
-	Length       func() (int, int)                                `json:"-"`
+	Length       func() (rows int, cols int)                      `json:"-"`
 	CreateCell   func() fyne.CanvasObject                         `json:"-"`
 	UpdateCell   func(id TableCellID, template fyne.CanvasObject) `json:"-"`
 	OnSelected   func(id TableCellID)                             `json:"-"`
@@ -104,7 +104,7 @@ type Table struct {
 // passed template CanvasObject.
 //
 // Since: 1.4
-func NewTable(length func() (int, int), create func() fyne.CanvasObject, update func(TableCellID, fyne.CanvasObject)) *Table {
+func NewTable(length func() (rows int, cols int), create func() fyne.CanvasObject, update func(TableCellID, fyne.CanvasObject)) *Table {
 	t := &Table{Length: length, CreateCell: create, UpdateCell: update}
 	t.ExtendBaseWidget(t)
 	return t
@@ -117,7 +117,7 @@ func NewTable(length func() (int, int), create func() fyne.CanvasObject, update
 // The row and column headers will stick to the leading and top edges of the table and contain "1-10" and "A-Z" formatted labels.
 //
 // Since: 2.4
-func NewTableWithHeaders(length func() (int, int), create func() fyne.CanvasObject, update func(TableCellID, fyne.CanvasObject)) *Table {
+func NewTableWithHeaders(length func() (rows int, cols int), create func() fyne.CanvasObject, update func(TableCellID, fyne.CanvasObject)) *Table {
 	t := NewTable(length, create, update)
 	t.ShowHeaderRow = true
 	t.ShowHeaderColumn = true
@@ -549,22 +549,24 @@ func (t *Table) Tapped(e *fyne.PointEvent) {
 	}
 
 	col := t.columnAt(e.Position)
-	if col == -1 {
+	if col == noCellMatch {
 		return // out of col range
 	}
 	row := t.rowAt(e.Position)
-	if row == -1 {
+	if row == noCellMatch {
 		return // out of row range
 	}
 	t.Select(TableCellID{row, col})
 
-	t.RefreshItem(t.currentFocus)
-	canvas := fyne.CurrentApp().Driver().CanvasForObject(t)
-	if canvas != nil {
-		canvas.Focus(t)
+	if !fyne.CurrentDevice().IsMobile() {
+		t.RefreshItem(t.currentFocus)
+		canvas := fyne.CurrentApp().Driver().CanvasForObject(t)
+		if canvas != nil {
+			canvas.Focus(t)
+		}
+		t.currentFocus = TableCellID{row, col}
+		t.RefreshItem(t.currentFocus)
 	}
-	t.currentFocus = TableCellID{row, col}
-	t.RefreshItem(t.currentFocus)
 }
 
 // columnAt returns a positive integer (or 0) for the column that is found at the `pos` X position.

+ 18 - 10
vendor/fyne.io/fyne/v2/widget/tree.go

@@ -348,11 +348,17 @@ func (t *Tree) TypedKey(event *fyne.KeyEvent) {
 		t.ScrollTo(t.currentFocus)
 		t.RefreshItem(t.currentFocus)
 	case fyne.KeyLeft:
-		t.walk(t.Root, "", 0, func(id, p TreeNodeID, _ bool, _ int) {
-			if id == t.currentFocus && p != "" {
-				t.currentFocus = p
-			}
-		})
+		// If the current focus is on a branch which is open, just close it
+		if t.IsBranch(t.currentFocus) && t.IsBranchOpen(t.currentFocus) {
+			t.CloseBranch(t.currentFocus)
+		} else {
+			// Every other case should move the focus to the current parent node
+			t.walk(t.Root, "", 0, func(id, p TreeNodeID, _ bool, _ int) {
+				if id == t.currentFocus && p != "" {
+					t.currentFocus = p
+				}
+			})
+		}
 
 		t.RefreshItem(t.currentFocus)
 		t.ScrollTo(t.currentFocus)
@@ -877,12 +883,14 @@ func (n *treeNode) Tapped(*fyne.PointEvent) {
 	}
 
 	n.tree.Select(n.uid)
-	canvas := fyne.CurrentApp().Driver().CanvasForObject(n.tree)
-	if canvas != nil {
-		canvas.Focus(n.tree)
+	if !fyne.CurrentDevice().IsMobile() {
+		canvas := fyne.CurrentApp().Driver().CanvasForObject(n.tree)
+		if canvas != nil {
+			canvas.Focus(n.tree)
+		}
+		n.tree.currentFocus = n.uid
+		n.Refresh()
 	}
-	n.tree.currentFocus = n.uid
-	n.Refresh()
 }
 
 func (n *treeNode) partialRefresh() {

+ 0 - 8
vendor/github.com/andybalholm/brotli/http.go

@@ -11,15 +11,7 @@ import (
 // the Accept-Encoding header, sets the Content-Encoding header, and returns a
 // WriteCloser that implements that compression. The Close method must be called
 // before the current HTTP handler returns.
-//
-// Due to https://github.com/golang/go/issues/31753, the response will not be
-// compressed unless you set a Content-Type header before you call
-// HTTPCompressor.
 func HTTPCompressor(w http.ResponseWriter, r *http.Request) io.WriteCloser {
-	if w.Header().Get("Content-Type") == "" {
-		return nopCloser{w}
-	}
-
 	if w.Header().Get("Vary") == "" {
 		w.Header().Set("Vary", "Accept-Encoding")
 	}

+ 13 - 0
vendor/github.com/fsnotify/fsnotify/.cirrus.yml

@@ -0,0 +1,13 @@
+freebsd_task:
+  name: 'FreeBSD'
+  freebsd_instance:
+    image_family: freebsd-13-2
+  install_script:
+    - pkg update -f
+    - pkg install -y go
+  test_script:
+      # run tests as user "cirrus" instead of root
+    - pw useradd cirrus -m
+    - chown -R cirrus:cirrus .
+    - FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
+    -                      sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...

+ 1 - 0
vendor/github.com/fsnotify/fsnotify/.gitignore

@@ -4,3 +4,4 @@
 
 # Output of go build ./cmd/fsnotify
 /fsnotify
+/fsnotify.exe

+ 77 - 6
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md

@@ -1,16 +1,87 @@
 # Changelog
 
-All notable changes to this project will be documented in this file.
+Unreleased
+----------
+Nothing yet.
 
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+1.7.0 - 2023-10-22
+------------------
+This version of fsnotify needs Go 1.17.
 
-## [Unreleased]
+### Additions
 
-Nothing yet.
+- illumos: add FEN backend to support illumos and Solaris. ([#371])
+
+- all: add `NewBufferedWatcher()` to use a buffered channel, which can be useful
+  in cases where you can't control the kernel buffer and receive a large number
+  of events in bursts. ([#550], [#572])
+
+- all: add `AddWith()`, which is identical to `Add()` but allows passing
+  options. ([#521])
+
+- windows: allow setting the ReadDirectoryChangesW() buffer size with
+  `fsnotify.WithBufferSize()`; the default of 64K is the highest value that
+  works on all platforms and is enough for most purposes, but in some cases a
+  highest buffer is needed. ([#521])
+
+### Changes and fixes
+
+- inotify: remove watcher if a watched path is renamed ([#518])
+
+  After a rename the reported name wasn't updated, or even an empty string.
+  Inotify doesn't provide any good facilities to update it, so just remove the
+  watcher. This is already how it worked on kqueue and FEN.
+
+  On Windows this does work, and remains working.
+
+- windows: don't listen for file attribute changes ([#520])
+
+  File attribute changes are sent as `FILE_ACTION_MODIFIED` by the Windows API,
+  with no way to see if they're a file write or attribute change, so would show
+  up as a fsnotify.Write event. This is never useful, and could result in many
+  spurious Write events.
+
+- windows: return `ErrEventOverflow` if the buffer is full ([#525])
+
+  Before it would merely return "short read", making it hard to detect this
+  error.
+
+- kqueue: make sure events for all files are delivered properly when removing a
+  watched directory ([#526])
+
+  Previously they would get sent with `""` (empty string) or `"."` as the path
+  name.
+
+- kqueue: don't emit spurious Create events for symbolic links ([#524])
+
+  The link would get resolved but kqueue would "forget" it already saw the link
+  itself, resulting on a Create for every Write event for the directory.
+
+- all: return `ErrClosed` on `Add()` when the watcher is closed ([#516])
+
+- other: add `Watcher.Errors` and `Watcher.Events` to the no-op `Watcher` in
+  `backend_other.go`, making it easier to use on unsupported platforms such as
+  WASM, AIX, etc. ([#528])
+
+- other: use the `backend_other.go` no-op if the `appengine` build tag is set;
+  Google AppEngine forbids usage of the unsafe package so the inotify backend
+  won't compile there.
 
-## [1.6.0] - 2022-10-13
+[#371]: https://github.com/fsnotify/fsnotify/pull/371
+[#516]: https://github.com/fsnotify/fsnotify/pull/516
+[#518]: https://github.com/fsnotify/fsnotify/pull/518
+[#520]: https://github.com/fsnotify/fsnotify/pull/520
+[#521]: https://github.com/fsnotify/fsnotify/pull/521
+[#524]: https://github.com/fsnotify/fsnotify/pull/524
+[#525]: https://github.com/fsnotify/fsnotify/pull/525
+[#526]: https://github.com/fsnotify/fsnotify/pull/526
+[#528]: https://github.com/fsnotify/fsnotify/pull/528
+[#537]: https://github.com/fsnotify/fsnotify/pull/537
+[#550]: https://github.com/fsnotify/fsnotify/pull/550
+[#572]: https://github.com/fsnotify/fsnotify/pull/572
 
+1.6.0 - 2022-10-13
+------------------
 This version of fsnotify needs Go 1.16 (this was already the case since 1.5.1,
 but not documented). It also increases the minimum Linux version to 2.6.32.
 

+ 52 - 29
vendor/github.com/fsnotify/fsnotify/README.md

@@ -1,29 +1,31 @@
 fsnotify is a Go library to provide cross-platform filesystem notifications on
-Windows, Linux, macOS, and BSD systems.
+Windows, Linux, macOS, BSD, and illumos.
 
-Go 1.16 or newer is required; the full documentation is at
+Go 1.17 or newer is required; the full documentation is at
 https://pkg.go.dev/github.com/fsnotify/fsnotify
 
-**It's best to read the documentation at pkg.go.dev, as it's pinned to the last
-released version, whereas this README is for the last development version which
-may include additions/changes.**
-
 ---
 
 Platform support:
 
-| Adapter               | OS             | Status                                                       |
-| --------------------- | ---------------| -------------------------------------------------------------|
-| inotify               | Linux 2.6.32+  | Supported                                                    |
-| kqueue                | BSD, macOS     | Supported                                                    |
-| ReadDirectoryChangesW | Windows        | Supported                                                    |
-| FSEvents              | macOS          | [Planned](https://github.com/fsnotify/fsnotify/issues/11)    |
-| FEN                   | Solaris 11     | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) |
-| fanotify              | Linux 5.9+     | [Maybe](https://github.com/fsnotify/fsnotify/issues/114)     |
-| USN Journals          | Windows        | [Maybe](https://github.com/fsnotify/fsnotify/issues/53)      |
-| Polling               | *All*          | [Maybe](https://github.com/fsnotify/fsnotify/issues/9)       |
-
-Linux and macOS should include Android and iOS, but these are currently untested.
+| Backend               | OS         | Status                                                                    |
+| :-------------------- | :--------- | :------------------------------------------------------------------------ |
+| inotify               | Linux      | Supported                                                                 |
+| kqueue                | BSD, macOS | Supported                                                                 |
+| ReadDirectoryChangesW | Windows    | Supported                                                                 |
+| FEN                   | illumos    | Supported                                                                 |
+| fanotify              | Linux 5.9+ | [Not yet](https://github.com/fsnotify/fsnotify/issues/114)                |
+| AHAFS                 | AIX        | [aix branch]; experimental due to lack of maintainer and test environment |
+| FSEvents              | macOS      | [Needs support in x/sys/unix][fsevents]                                   |
+| USN Journals          | Windows    | [Needs support in x/sys/windows][usn]                                     |
+| Polling               | *All*      | [Not yet](https://github.com/fsnotify/fsnotify/issues/9)                  |
+
+Linux and illumos should include Android and Solaris, but these are currently
+untested.
+
+[fsevents]:   https://github.com/fsnotify/fsnotify/issues/11#issuecomment-1279133120
+[usn]:        https://github.com/fsnotify/fsnotify/issues/53#issuecomment-1279829847
+[aix branch]: https://github.com/fsnotify/fsnotify/issues/353#issuecomment-1284590129
 
 Usage
 -----
@@ -83,20 +85,23 @@ run with:
 
     % go run ./cmd/fsnotify
 
+Further detailed documentation can be found in godoc:
+https://pkg.go.dev/github.com/fsnotify/fsnotify
+
 FAQ
 ---
 ### Will a file still be watched when it's moved to another directory?
 No, not unless you are watching the location it was moved to.
 
-### Are subdirectories watched too?
+### Are subdirectories watched?
 No, you must add watches for any directory you want to watch (a recursive
 watcher is on the roadmap: [#18]).
 
 [#18]: https://github.com/fsnotify/fsnotify/issues/18
 
 ### Do I have to watch the Error and Event channels in a goroutine?
-As of now, yes (you can read both channels in the same goroutine using `select`,
-you don't need a separate goroutine for both channels; see the example).
+Yes. You can read both channels in the same goroutine using `select` (you don't
+need a separate goroutine for both channels; see the example).
 
 ### Why don't notifications work with NFS, SMB, FUSE, /proc, or /sys?
 fsnotify requires support from underlying OS to work. The current NFS and SMB
@@ -107,6 +112,32 @@ This could be fixed with a polling watcher ([#9]), but it's not yet implemented.
 
 [#9]: https://github.com/fsnotify/fsnotify/issues/9
 
+### Why do I get many Chmod events?
+Some programs may generate a lot of attribute changes; for example Spotlight on
+macOS, anti-virus programs, backup applications, and some others are known to do
+this. As a rule, it's typically best to ignore Chmod events. They're often not
+useful, and tend to cause problems.
+
+Spotlight indexing on macOS can result in multiple events (see [#15]). A
+temporary workaround is to add your folder(s) to the *Spotlight Privacy
+settings* until we have a native FSEvents implementation (see [#11]).
+
+[#11]: https://github.com/fsnotify/fsnotify/issues/11
+[#15]: https://github.com/fsnotify/fsnotify/issues/15
+
+### Watching a file doesn't work well
+Watching individual files (rather than directories) is generally not recommended
+as many programs (especially editors) update files atomically: it will write to
+a temporary file which is then moved to to destination, overwriting the original
+(or some variant thereof). The watcher on the original file is now lost, as that
+no longer exists.
+
+The upshot of this is that a power failure or crash won't leave a half-written
+file.
+
+Watch the parent directory and use `Event.Name` to filter out files you're not
+interested in. There is an example of this in `cmd/fsnotify/file.go`.
+
 Platform-specific notes
 -----------------------
 ### Linux
@@ -151,11 +182,3 @@ these platforms.
 
 The sysctl variables `kern.maxfiles` and `kern.maxfilesperproc` can be used to
 control the maximum number of open files.
-
-### macOS
-Spotlight indexing on macOS can result in multiple events (see [#15]). A temporary
-workaround is to add your folder(s) to the *Spotlight Privacy settings* until we
-have a native FSEvents implementation (see [#11]).
-
-[#11]: https://github.com/fsnotify/fsnotify/issues/11
-[#15]: https://github.com/fsnotify/fsnotify/issues/15

+ 515 - 37
vendor/github.com/fsnotify/fsnotify/backend_fen.go

@@ -1,10 +1,19 @@
 //go:build solaris
 // +build solaris
 
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
 package fsnotify
 
 import (
 	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+	"sync"
+
+	"golang.org/x/sys/unix"
 )
 
 // Watcher watches a set of paths, delivering events on a channel.
@@ -17,9 +26,9 @@ import (
 // When a file is removed a Remove event won't be emitted until all file
 // descriptors are closed, and deletes will always emit a Chmod. For example:
 //
-//     fp := os.Open("file")
-//     os.Remove("file")        // Triggers Chmod
-//     fp.Close()               // Triggers Remove
+//	fp := os.Open("file")
+//	os.Remove("file")        // Triggers Chmod
+//	fp.Close()               // Triggers Remove
 //
 // This is the event that inotify sends, so not much can be changed about this.
 //
@@ -33,16 +42,16 @@ import (
 //
 // To increase them you can use sysctl or write the value to the /proc file:
 //
-//     # Default values on Linux 5.18
-//     sysctl fs.inotify.max_user_watches=124983
-//     sysctl fs.inotify.max_user_instances=128
+//	# Default values on Linux 5.18
+//	sysctl fs.inotify.max_user_watches=124983
+//	sysctl fs.inotify.max_user_instances=128
 //
 // To make the changes persist on reboot edit /etc/sysctl.conf or
 // /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
 // your distro's documentation):
 //
-//     fs.inotify.max_user_watches=124983
-//     fs.inotify.max_user_instances=128
+//	fs.inotify.max_user_watches=124983
+//	fs.inotify.max_user_instances=128
 //
 // Reaching the limit will result in a "no space left on device" or "too many open
 // files" error.
@@ -58,14 +67,20 @@ import (
 // control the maximum number of open files, as well as /etc/login.conf on BSD
 // systems.
 //
-// # macOS notes
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
 //
-// Spotlight indexing on macOS can result in multiple events (see [#15]). A
-// temporary workaround is to add your folder(s) to the "Spotlight Privacy
-// Settings" until we have a native FSEvents implementation (see [#11]).
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
 //
-// [#11]: https://github.com/fsnotify/fsnotify/issues/11
-// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
 type Watcher struct {
 	// Events sends the filesystem change events.
 	//
@@ -92,44 +107,129 @@ type Watcher struct {
 	//                      initiated by the user may show up as one or multiple
 	//                      writes, depending on when the system syncs things to
 	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, so you
-	//                      probably want to wait until you've stopped receiving
-	//                      them (see the dedup example in cmd/fsnotify).
+	//                      you may get hundreds of Write events, and you may
+	//                      want to wait until you've stopped receiving them
+	//                      (see the dedup example in cmd/fsnotify).
+	//
+	//                      Some systems may send Write event for directories
+	//                      when the directory content changes.
 	//
 	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
 	//                      when a file is removed (or more accurately, when a
 	//                      link to an inode is removed). On kqueue it's sent
-	//                      and on kqueue when a file is truncated. On Windows
-	//                      it's never sent.
+	//                      when a file is truncated. On Windows it's never
+	//                      sent.
 	Events chan Event
 
 	// Errors sends any errors.
+	//
+	// ErrEventOverflow is used to indicate there are too many events:
+	//
+	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
+	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
+	//  - kqueue, fen:  Not used.
 	Errors chan error
+
+	mu      sync.Mutex
+	port    *unix.EventPort
+	done    chan struct{}       // Channel for sending a "quit message" to the reader goroutine
+	dirs    map[string]struct{} // Explicitly watched directories
+	watches map[string]struct{} // Explicitly watched non-directories
 }
 
 // NewWatcher creates a new Watcher.
 func NewWatcher() (*Watcher, error) {
-	return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
+	return NewBufferedWatcher(0)
 }
 
-// Close removes all watches and closes the events channel.
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) {
+	w := &Watcher{
+		Events:  make(chan Event, sz),
+		Errors:  make(chan error),
+		dirs:    make(map[string]struct{}),
+		watches: make(map[string]struct{}),
+		done:    make(chan struct{}),
+	}
+
+	var err error
+	w.port, err = unix.NewEventPort()
+	if err != nil {
+		return nil, fmt.Errorf("fsnotify.NewWatcher: %w", err)
+	}
+
+	go w.readEvents()
+	return w, nil
+}
+
+// sendEvent attempts to send an event to the user, returning true if the event
+// was put in the channel successfully and false if the watcher has been closed.
+func (w *Watcher) sendEvent(name string, op Op) (sent bool) {
+	select {
+	case w.Events <- Event{Name: name, Op: op}:
+		return true
+	case <-w.done:
+		return false
+	}
+}
+
+// sendError attempts to send an error to the user, returning true if the error
+// was put in the channel successfully and false if the watcher has been closed.
+func (w *Watcher) sendError(err error) (sent bool) {
+	select {
+	case w.Errors <- err:
+		return true
+	case <-w.done:
+		return false
+	}
+}
+
+func (w *Watcher) isClosed() bool {
+	select {
+	case <-w.done:
+		return true
+	default:
+		return false
+	}
+}
+
+// Close removes all watches and closes the Events channel.
 func (w *Watcher) Close() error {
-	return nil
+	// Take the lock used by associateFile to prevent lingering events from
+	// being processed after the close
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	if w.isClosed() {
+		return nil
+	}
+	close(w.done)
+	return w.port.Close()
 }
 
 // Add starts monitoring the path for changes.
 //
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
 //
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
 //
 // Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
 // filesystems (/proc, /sys, etc.) generally don't work.
 //
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
 // # Watching directories
 //
 // All files in a directory are monitored, including new files that are created
@@ -139,15 +239,63 @@ func (w *Watcher) Close() error {
 // # Watching files
 //
 // Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
-func (w *Watcher) Add(name string) error {
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+//     other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(name string, opts ...addOpt) error {
+	if w.isClosed() {
+		return ErrClosed
+	}
+	if w.port.PathIsWatched(name) {
+		return nil
+	}
+
+	_ = getOptions(opts...)
+
+	// Currently we resolve symlinks that were explicitly requested to be
+	// watched. Otherwise we would use LStat here.
+	stat, err := os.Stat(name)
+	if err != nil {
+		return err
+	}
+
+	// Associate all files in the directory.
+	if stat.IsDir() {
+		err := w.handleDirectory(name, stat, true, w.associateFile)
+		if err != nil {
+			return err
+		}
+
+		w.mu.Lock()
+		w.dirs[name] = struct{}{}
+		w.mu.Unlock()
+		return nil
+	}
+
+	err = w.associateFile(name, stat, true)
+	if err != nil {
+		return err
+	}
+
+	w.mu.Lock()
+	w.watches[name] = struct{}{}
+	w.mu.Unlock()
 	return nil
 }
 
@@ -157,6 +305,336 @@ func (w *Watcher) Add(name string) error {
 // /tmp/dir and /tmp/dir/subdir then you will need to remove both.
 //
 // Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
 func (w *Watcher) Remove(name string) error {
+	if w.isClosed() {
+		return nil
+	}
+	if !w.port.PathIsWatched(name) {
+		return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
+	}
+
+	// The user has expressed an intent. Immediately remove this name from
+	// whichever watch list it might be in. If it's not in there the delete
+	// doesn't cause harm.
+	w.mu.Lock()
+	delete(w.watches, name)
+	delete(w.dirs, name)
+	w.mu.Unlock()
+
+	stat, err := os.Stat(name)
+	if err != nil {
+		return err
+	}
+
+	// Remove associations for every file in the directory.
+	if stat.IsDir() {
+		err := w.handleDirectory(name, stat, false, w.dissociateFile)
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+
+	err = w.port.DissociatePath(name)
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
+
+// readEvents contains the main loop that runs in a goroutine watching for events.
+func (w *Watcher) readEvents() {
+	// If this function returns, the watcher has been closed and we can close
+	// these channels
+	defer func() {
+		close(w.Errors)
+		close(w.Events)
+	}()
+
+	pevents := make([]unix.PortEvent, 8)
+	for {
+		count, err := w.port.Get(pevents, 1, nil)
+		if err != nil && err != unix.ETIME {
+			// Interrupted system call (count should be 0) ignore and continue
+			if errors.Is(err, unix.EINTR) && count == 0 {
+				continue
+			}
+			// Get failed because we called w.Close()
+			if errors.Is(err, unix.EBADF) && w.isClosed() {
+				return
+			}
+			// There was an error not caused by calling w.Close()
+			if !w.sendError(err) {
+				return
+			}
+		}
+
+		p := pevents[:count]
+		for _, pevent := range p {
+			if pevent.Source != unix.PORT_SOURCE_FILE {
+				// Event from unexpected source received; should never happen.
+				if !w.sendError(errors.New("Event from unexpected source received")) {
+					return
+				}
+				continue
+			}
+
+			err = w.handleEvent(&pevent)
+			if err != nil {
+				if !w.sendError(err) {
+					return
+				}
+			}
+		}
+	}
+}
+
+func (w *Watcher) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error {
+	files, err := os.ReadDir(path)
+	if err != nil {
+		return err
+	}
+
+	// Handle all children of the directory.
+	for _, entry := range files {
+		finfo, err := entry.Info()
+		if err != nil {
+			return err
+		}
+		err = handler(filepath.Join(path, finfo.Name()), finfo, false)
+		if err != nil {
+			return err
+		}
+	}
+
+	// And finally handle the directory itself.
+	return handler(path, stat, follow)
+}
+
+// handleEvent might need to emit more than one fsnotify event if the events
+// bitmap matches more than one event type (e.g. the file was both modified and
+// had the attributes changed between when the association was created and the
+// when event was returned)
+func (w *Watcher) handleEvent(event *unix.PortEvent) error {
+	var (
+		events     = event.Events
+		path       = event.Path
+		fmode      = event.Cookie.(os.FileMode)
+		reRegister = true
+	)
+
+	w.mu.Lock()
+	_, watchedDir := w.dirs[path]
+	_, watchedPath := w.watches[path]
+	w.mu.Unlock()
+	isWatched := watchedDir || watchedPath
+
+	if events&unix.FILE_DELETE != 0 {
+		if !w.sendEvent(path, Remove) {
+			return nil
+		}
+		reRegister = false
+	}
+	if events&unix.FILE_RENAME_FROM != 0 {
+		if !w.sendEvent(path, Rename) {
+			return nil
+		}
+		// Don't keep watching the new file name
+		reRegister = false
+	}
+	if events&unix.FILE_RENAME_TO != 0 {
+		// We don't report a Rename event for this case, because Rename events
+		// are interpreted as referring to the _old_ name of the file, and in
+		// this case the event would refer to the new name of the file. This
+		// type of rename event is not supported by fsnotify.
+
+		// inotify reports a Remove event in this case, so we simulate this
+		// here.
+		if !w.sendEvent(path, Remove) {
+			return nil
+		}
+		// Don't keep watching the file that was removed
+		reRegister = false
+	}
+
+	// The file is gone, nothing left to do.
+	if !reRegister {
+		if watchedDir {
+			w.mu.Lock()
+			delete(w.dirs, path)
+			w.mu.Unlock()
+		}
+		if watchedPath {
+			w.mu.Lock()
+			delete(w.watches, path)
+			w.mu.Unlock()
+		}
+		return nil
+	}
+
+	// If we didn't get a deletion the file still exists and we're going to have
+	// to watch it again. Let's Stat it now so that we can compare permissions
+	// and have what we need to continue watching the file
+
+	stat, err := os.Lstat(path)
+	if err != nil {
+		// This is unexpected, but we should still emit an event. This happens
+		// most often on "rm -r" of a subdirectory inside a watched directory We
+		// get a modify event of something happening inside, but by the time we
+		// get here, the sudirectory is already gone. Clearly we were watching
+		// this path but now it is gone. Let's tell the user that it was
+		// removed.
+		if !w.sendEvent(path, Remove) {
+			return nil
+		}
+		// Suppress extra write events on removed directories; they are not
+		// informative and can be confusing.
+		return nil
+	}
+
+	// resolve symlinks that were explicitly watched as we would have at Add()
+	// time. this helps suppress spurious Chmod events on watched symlinks
+	if isWatched {
+		stat, err = os.Stat(path)
+		if err != nil {
+			// The symlink still exists, but the target is gone. Report the
+			// Remove similar to above.
+			if !w.sendEvent(path, Remove) {
+				return nil
+			}
+			// Don't return the error
+		}
+	}
+
+	if events&unix.FILE_MODIFIED != 0 {
+		if fmode.IsDir() {
+			if watchedDir {
+				if err := w.updateDirectory(path); err != nil {
+					return err
+				}
+			} else {
+				if !w.sendEvent(path, Write) {
+					return nil
+				}
+			}
+		} else {
+			if !w.sendEvent(path, Write) {
+				return nil
+			}
+		}
+	}
+	if events&unix.FILE_ATTRIB != 0 && stat != nil {
+		// Only send Chmod if perms changed
+		if stat.Mode().Perm() != fmode.Perm() {
+			if !w.sendEvent(path, Chmod) {
+				return nil
+			}
+		}
+	}
+
+	if stat != nil {
+		// If we get here, it means we've hit an event above that requires us to
+		// continue watching the file or directory
+		return w.associateFile(path, stat, isWatched)
+	}
+	return nil
+}
+
+func (w *Watcher) updateDirectory(path string) error {
+	// The directory was modified, so we must find unwatched entities and watch
+	// them. If something was removed from the directory, nothing will happen,
+	// as everything else should still be watched.
+	files, err := os.ReadDir(path)
+	if err != nil {
+		return err
+	}
+
+	for _, entry := range files {
+		path := filepath.Join(path, entry.Name())
+		if w.port.PathIsWatched(path) {
+			continue
+		}
+
+		finfo, err := entry.Info()
+		if err != nil {
+			return err
+		}
+		err = w.associateFile(path, finfo, false)
+		if err != nil {
+			if !w.sendError(err) {
+				return nil
+			}
+		}
+		if !w.sendEvent(path, Create) {
+			return nil
+		}
+	}
+	return nil
+}
+
+func (w *Watcher) associateFile(path string, stat os.FileInfo, follow bool) error {
+	if w.isClosed() {
+		return ErrClosed
+	}
+	// This is primarily protecting the call to AssociatePath but it is
+	// important and intentional that the call to PathIsWatched is also
+	// protected by this mutex. Without this mutex, AssociatePath has been seen
+	// to error out that the path is already associated.
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	if w.port.PathIsWatched(path) {
+		// Remove the old association in favor of this one If we get ENOENT,
+		// then while the x/sys/unix wrapper still thought that this path was
+		// associated, the underlying event port did not. This call will have
+		// cleared up that discrepancy. The most likely cause is that the event
+		// has fired but we haven't processed it yet.
+		err := w.port.DissociatePath(path)
+		if err != nil && err != unix.ENOENT {
+			return err
+		}
+	}
+	// FILE_NOFOLLOW means we watch symlinks themselves rather than their
+	// targets.
+	events := unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW
+	if follow {
+		// We *DO* follow symlinks for explicitly watched entries.
+		events = unix.FILE_MODIFIED | unix.FILE_ATTRIB
+	}
+	return w.port.AssociatePath(path, stat,
+		events,
+		stat.Mode())
+}
+
+func (w *Watcher) dissociateFile(path string, stat os.FileInfo, unused bool) error {
+	if !w.port.PathIsWatched(path) {
+		return nil
+	}
+	return w.port.DissociatePath(path)
+}
+
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
+func (w *Watcher) WatchList() []string {
+	if w.isClosed() {
+		return nil
+	}
+
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	entries := make([]string, 0, len(w.watches)+len(w.dirs))
+	for pathname := range w.dirs {
+		entries = append(entries, pathname)
+	}
+	for pathname := range w.watches {
+		entries = append(entries, pathname)
+	}
+
+	return entries
+}

+ 256 - 121
vendor/github.com/fsnotify/fsnotify/backend_inotify.go

@@ -1,5 +1,8 @@
-//go:build linux
-// +build linux
+//go:build linux && !appengine
+// +build linux,!appengine
+
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
 
 package fsnotify
 
@@ -26,9 +29,9 @@ import (
 // When a file is removed a Remove event won't be emitted until all file
 // descriptors are closed, and deletes will always emit a Chmod. For example:
 //
-//     fp := os.Open("file")
-//     os.Remove("file")        // Triggers Chmod
-//     fp.Close()               // Triggers Remove
+//	fp := os.Open("file")
+//	os.Remove("file")        // Triggers Chmod
+//	fp.Close()               // Triggers Remove
 //
 // This is the event that inotify sends, so not much can be changed about this.
 //
@@ -42,16 +45,16 @@ import (
 //
 // To increase them you can use sysctl or write the value to the /proc file:
 //
-//     # Default values on Linux 5.18
-//     sysctl fs.inotify.max_user_watches=124983
-//     sysctl fs.inotify.max_user_instances=128
+//	# Default values on Linux 5.18
+//	sysctl fs.inotify.max_user_watches=124983
+//	sysctl fs.inotify.max_user_instances=128
 //
 // To make the changes persist on reboot edit /etc/sysctl.conf or
 // /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
 // your distro's documentation):
 //
-//     fs.inotify.max_user_watches=124983
-//     fs.inotify.max_user_instances=128
+//	fs.inotify.max_user_watches=124983
+//	fs.inotify.max_user_instances=128
 //
 // Reaching the limit will result in a "no space left on device" or "too many open
 // files" error.
@@ -67,14 +70,20 @@ import (
 // control the maximum number of open files, as well as /etc/login.conf on BSD
 // systems.
 //
-// # macOS notes
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
 //
-// Spotlight indexing on macOS can result in multiple events (see [#15]). A
-// temporary workaround is to add your folder(s) to the "Spotlight Privacy
-// Settings" until we have a native FSEvents implementation (see [#11]).
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
 //
-// [#11]: https://github.com/fsnotify/fsnotify/issues/11
-// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
 type Watcher struct {
 	// Events sends the filesystem change events.
 	//
@@ -101,36 +110,148 @@ type Watcher struct {
 	//                      initiated by the user may show up as one or multiple
 	//                      writes, depending on when the system syncs things to
 	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, so you
-	//                      probably want to wait until you've stopped receiving
-	//                      them (see the dedup example in cmd/fsnotify).
+	//                      you may get hundreds of Write events, and you may
+	//                      want to wait until you've stopped receiving them
+	//                      (see the dedup example in cmd/fsnotify).
+	//
+	//                      Some systems may send Write event for directories
+	//                      when the directory content changes.
 	//
 	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
 	//                      when a file is removed (or more accurately, when a
 	//                      link to an inode is removed). On kqueue it's sent
-	//                      and on kqueue when a file is truncated. On Windows
-	//                      it's never sent.
+	//                      when a file is truncated. On Windows it's never
+	//                      sent.
 	Events chan Event
 
 	// Errors sends any errors.
+	//
+	// ErrEventOverflow is used to indicate there are too many events:
+	//
+	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
+	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
+	//  - kqueue, fen:  Not used.
 	Errors chan error
 
 	// Store fd here as os.File.Read() will no longer return on close after
 	// calling Fd(). See: https://github.com/golang/go/issues/26439
 	fd          int
-	mu          sync.Mutex // Map access
 	inotifyFile *os.File
-	watches     map[string]*watch // Map of inotify watches (key: path)
-	paths       map[int]string    // Map of watched paths (key: watch descriptor)
-	done        chan struct{}     // Channel for sending a "quit message" to the reader goroutine
-	doneResp    chan struct{}     // Channel to respond to Close
+	watches     *watches
+	done        chan struct{} // Channel for sending a "quit message" to the reader goroutine
+	closeMu     sync.Mutex
+	doneResp    chan struct{} // Channel to respond to Close
+}
+
+type (
+	watches struct {
+		mu   sync.RWMutex
+		wd   map[uint32]*watch // wd → watch
+		path map[string]uint32 // pathname → wd
+	}
+	watch struct {
+		wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
+		flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
+		path  string // Watch path.
+	}
+)
+
+func newWatches() *watches {
+	return &watches{
+		wd:   make(map[uint32]*watch),
+		path: make(map[string]uint32),
+	}
+}
+
+func (w *watches) len() int {
+	w.mu.RLock()
+	defer w.mu.RUnlock()
+	return len(w.wd)
+}
+
+func (w *watches) add(ww *watch) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	w.wd[ww.wd] = ww
+	w.path[ww.path] = ww.wd
+}
+
+func (w *watches) remove(wd uint32) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	delete(w.path, w.wd[wd].path)
+	delete(w.wd, wd)
+}
+
+func (w *watches) removePath(path string) (uint32, bool) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	wd, ok := w.path[path]
+	if !ok {
+		return 0, false
+	}
+
+	delete(w.path, path)
+	delete(w.wd, wd)
+
+	return wd, true
+}
+
+func (w *watches) byPath(path string) *watch {
+	w.mu.RLock()
+	defer w.mu.RUnlock()
+	return w.wd[w.path[path]]
+}
+
+func (w *watches) byWd(wd uint32) *watch {
+	w.mu.RLock()
+	defer w.mu.RUnlock()
+	return w.wd[wd]
+}
+
+func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	var existing *watch
+	wd, ok := w.path[path]
+	if ok {
+		existing = w.wd[wd]
+	}
+
+	upd, err := f(existing)
+	if err != nil {
+		return err
+	}
+	if upd != nil {
+		w.wd[upd.wd] = upd
+		w.path[upd.path] = upd.wd
+
+		if upd.wd != wd {
+			delete(w.wd, wd)
+		}
+	}
+
+	return nil
 }
 
 // NewWatcher creates a new Watcher.
 func NewWatcher() (*Watcher, error) {
-	// Create inotify fd
-	// Need to set the FD to nonblocking mode in order for SetDeadline methods to work
-	// Otherwise, blocking i/o operations won't terminate on close
+	return NewBufferedWatcher(0)
+}
+
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) {
+	// Need to set nonblocking mode for SetDeadline to work, otherwise blocking
+	// I/O operations won't terminate on close.
 	fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
 	if fd == -1 {
 		return nil, errno
@@ -139,9 +260,8 @@ func NewWatcher() (*Watcher, error) {
 	w := &Watcher{
 		fd:          fd,
 		inotifyFile: os.NewFile(uintptr(fd), ""),
-		watches:     make(map[string]*watch),
-		paths:       make(map[int]string),
-		Events:      make(chan Event),
+		watches:     newWatches(),
+		Events:      make(chan Event, sz),
 		Errors:      make(chan error),
 		done:        make(chan struct{}),
 		doneResp:    make(chan struct{}),
@@ -157,8 +277,8 @@ func (w *Watcher) sendEvent(e Event) bool {
 	case w.Events <- e:
 		return true
 	case <-w.done:
+		return false
 	}
-	return false
 }
 
 // Returns true if the error was sent, or false if watcher is closed.
@@ -180,17 +300,15 @@ func (w *Watcher) isClosed() bool {
 	}
 }
 
-// Close removes all watches and closes the events channel.
+// Close removes all watches and closes the Events channel.
 func (w *Watcher) Close() error {
-	w.mu.Lock()
+	w.closeMu.Lock()
 	if w.isClosed() {
-		w.mu.Unlock()
+		w.closeMu.Unlock()
 		return nil
 	}
-
-	// Send 'close' signal to goroutine, and set the Watcher to closed.
 	close(w.done)
-	w.mu.Unlock()
+	w.closeMu.Unlock()
 
 	// Causes any blocking reads to return with an error, provided the file
 	// still supports deadline operations.
@@ -207,17 +325,21 @@ func (w *Watcher) Close() error {
 
 // Add starts monitoring the path for changes.
 //
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
 //
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
 //
 // Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
 // filesystems (/proc, /sys, etc.) generally don't work.
 //
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
 // # Watching directories
 //
 // All files in a directory are monitored, including new files that are created
@@ -227,44 +349,59 @@ func (w *Watcher) Close() error {
 // # Watching files
 //
 // Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
-func (w *Watcher) Add(name string) error {
-	name = filepath.Clean(name)
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+//     other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(name string, opts ...addOpt) error {
 	if w.isClosed() {
-		return errors.New("inotify instance already closed")
+		return ErrClosed
 	}
 
+	name = filepath.Clean(name)
+	_ = getOptions(opts...)
+
 	var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
 		unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
 		unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
 
-	w.mu.Lock()
-	defer w.mu.Unlock()
-	watchEntry := w.watches[name]
-	if watchEntry != nil {
-		flags |= watchEntry.flags | unix.IN_MASK_ADD
-	}
-	wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
-	if wd == -1 {
-		return errno
-	}
+	return w.watches.updatePath(name, func(existing *watch) (*watch, error) {
+		if existing != nil {
+			flags |= existing.flags | unix.IN_MASK_ADD
+		}
 
-	if watchEntry == nil {
-		w.watches[name] = &watch{wd: uint32(wd), flags: flags}
-		w.paths[wd] = name
-	} else {
-		watchEntry.wd = uint32(wd)
-		watchEntry.flags = flags
-	}
+		wd, err := unix.InotifyAddWatch(w.fd, name, flags)
+		if wd == -1 {
+			return nil, err
+		}
 
-	return nil
+		if existing == nil {
+			return &watch{
+				wd:    uint32(wd),
+				path:  name,
+				flags: flags,
+			}, nil
+		}
+
+		existing.wd = uint32(wd)
+		existing.flags = flags
+		return existing, nil
+	})
 }
 
 // Remove stops monitoring the path for changes.
@@ -273,32 +410,22 @@ func (w *Watcher) Add(name string) error {
 // /tmp/dir and /tmp/dir/subdir then you will need to remove both.
 //
 // Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
 func (w *Watcher) Remove(name string) error {
-	name = filepath.Clean(name)
-
-	// Fetch the watch.
-	w.mu.Lock()
-	defer w.mu.Unlock()
-	watch, ok := w.watches[name]
+	if w.isClosed() {
+		return nil
+	}
+	return w.remove(filepath.Clean(name))
+}
 
-	// Remove it from inotify.
+func (w *Watcher) remove(name string) error {
+	wd, ok := w.watches.removePath(name)
 	if !ok {
 		return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
 	}
 
-	// We successfully removed the watch if InotifyRmWatch doesn't return an
-	// error, we need to clean up our internal state to ensure it matches
-	// inotify's kernel state.
-	delete(w.paths, int(watch.wd))
-	delete(w.watches, name)
-
-	// inotify_rm_watch will return EINVAL if the file has been deleted;
-	// the inotify will already have been removed.
-	// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
-	// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
-	// so that EINVAL means that the wd is being rm_watch()ed or its file removed
-	// by another thread and we have not received IN_IGNORE event.
-	success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
+	success, errno := unix.InotifyRmWatch(w.fd, wd)
 	if success == -1 {
 		// TODO: Perhaps it's not helpful to return an error here in every case;
 		//       The only two possible errors are:
@@ -312,28 +439,28 @@ func (w *Watcher) Remove(name string) error {
 		//         are watching is deleted.
 		return errno
 	}
-
 	return nil
 }
 
-// WatchList returns all paths added with [Add] (and are not yet removed).
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
 func (w *Watcher) WatchList() []string {
-	w.mu.Lock()
-	defer w.mu.Unlock()
+	if w.isClosed() {
+		return nil
+	}
 
-	entries := make([]string, 0, len(w.watches))
-	for pathname := range w.watches {
+	entries := make([]string, 0, w.watches.len())
+	w.watches.mu.RLock()
+	for pathname := range w.watches.path {
 		entries = append(entries, pathname)
 	}
+	w.watches.mu.RUnlock()
 
 	return entries
 }
 
-type watch struct {
-	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
-	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
-}
-
 // readEvents reads from the inotify file descriptor, converts the
 // received events into Event objects and sends them via the Events channel
 func (w *Watcher) readEvents() {
@@ -367,14 +494,11 @@ func (w *Watcher) readEvents() {
 		if n < unix.SizeofInotifyEvent {
 			var err error
 			if n == 0 {
-				// If EOF is received. This should really never happen.
-				err = io.EOF
+				err = io.EOF // If EOF is received. This should really never happen.
 			} else if n < 0 {
-				// If an error occurred while reading.
-				err = errno
+				err = errno // If an error occurred while reading.
 			} else {
-				// Read was too short.
-				err = errors.New("notify: short read in readEvents()")
+				err = errors.New("notify: short read in readEvents()") // Read was too short.
 			}
 			if !w.sendError(err) {
 				return
@@ -403,18 +527,29 @@ func (w *Watcher) readEvents() {
 			// doesn't append the filename to the event, but we would like to always fill the
 			// the "Name" field with a valid filename. We retrieve the path of the watch from
 			// the "paths" map.
-			w.mu.Lock()
-			name, ok := w.paths[int(raw.Wd)]
-			// IN_DELETE_SELF occurs when the file/directory being watched is removed.
-			// This is a sign to clean up the maps, otherwise we are no longer in sync
-			// with the inotify kernel state which has already deleted the watch
-			// automatically.
-			if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
-				delete(w.paths, int(raw.Wd))
-				delete(w.watches, name)
+			watch := w.watches.byWd(uint32(raw.Wd))
+
+			// inotify will automatically remove the watch on deletes; just need
+			// to clean our state here.
+			if watch != nil && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
+				w.watches.remove(watch.wd)
+			}
+			// We can't really update the state when a watched path is moved;
+			// only IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove
+			// the watch.
+			if watch != nil && mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
+				err := w.remove(watch.path)
+				if err != nil && !errors.Is(err, ErrNonExistentWatch) {
+					if !w.sendError(err) {
+						return
+					}
+				}
 			}
-			w.mu.Unlock()
 
+			var name string
+			if watch != nil {
+				name = watch.path
+			}
 			if nameLen > 0 {
 				// Point "bytes" at the first byte of the filename
 				bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]

+ 185 - 110
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go

@@ -1,12 +1,14 @@
 //go:build freebsd || openbsd || netbsd || dragonfly || darwin
 // +build freebsd openbsd netbsd dragonfly darwin
 
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
 package fsnotify
 
 import (
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"sync"
@@ -24,9 +26,9 @@ import (
 // When a file is removed a Remove event won't be emitted until all file
 // descriptors are closed, and deletes will always emit a Chmod. For example:
 //
-//     fp := os.Open("file")
-//     os.Remove("file")        // Triggers Chmod
-//     fp.Close()               // Triggers Remove
+//	fp := os.Open("file")
+//	os.Remove("file")        // Triggers Chmod
+//	fp.Close()               // Triggers Remove
 //
 // This is the event that inotify sends, so not much can be changed about this.
 //
@@ -40,16 +42,16 @@ import (
 //
 // To increase them you can use sysctl or write the value to the /proc file:
 //
-//     # Default values on Linux 5.18
-//     sysctl fs.inotify.max_user_watches=124983
-//     sysctl fs.inotify.max_user_instances=128
+//	# Default values on Linux 5.18
+//	sysctl fs.inotify.max_user_watches=124983
+//	sysctl fs.inotify.max_user_instances=128
 //
 // To make the changes persist on reboot edit /etc/sysctl.conf or
 // /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
 // your distro's documentation):
 //
-//     fs.inotify.max_user_watches=124983
-//     fs.inotify.max_user_instances=128
+//	fs.inotify.max_user_watches=124983
+//	fs.inotify.max_user_instances=128
 //
 // Reaching the limit will result in a "no space left on device" or "too many open
 // files" error.
@@ -65,14 +67,20 @@ import (
 // control the maximum number of open files, as well as /etc/login.conf on BSD
 // systems.
 //
-// # macOS notes
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
 //
-// Spotlight indexing on macOS can result in multiple events (see [#15]). A
-// temporary workaround is to add your folder(s) to the "Spotlight Privacy
-// Settings" until we have a native FSEvents implementation (see [#11]).
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
 //
-// [#11]: https://github.com/fsnotify/fsnotify/issues/11
-// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
 type Watcher struct {
 	// Events sends the filesystem change events.
 	//
@@ -99,18 +107,27 @@ type Watcher struct {
 	//                      initiated by the user may show up as one or multiple
 	//                      writes, depending on when the system syncs things to
 	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, so you
-	//                      probably want to wait until you've stopped receiving
-	//                      them (see the dedup example in cmd/fsnotify).
+	//                      you may get hundreds of Write events, and you may
+	//                      want to wait until you've stopped receiving them
+	//                      (see the dedup example in cmd/fsnotify).
+	//
+	//                      Some systems may send Write event for directories
+	//                      when the directory content changes.
 	//
 	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
 	//                      when a file is removed (or more accurately, when a
 	//                      link to an inode is removed). On kqueue it's sent
-	//                      and on kqueue when a file is truncated. On Windows
-	//                      it's never sent.
+	//                      when a file is truncated. On Windows it's never
+	//                      sent.
 	Events chan Event
 
 	// Errors sends any errors.
+	//
+	// ErrEventOverflow is used to indicate there are too many events:
+	//
+	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
+	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
+	//  - kqueue, fen:  Not used.
 	Errors chan error
 
 	done         chan struct{}
@@ -133,6 +150,18 @@ type pathInfo struct {
 
 // NewWatcher creates a new Watcher.
 func NewWatcher() (*Watcher, error) {
+	return NewBufferedWatcher(0)
+}
+
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) {
 	kq, closepipe, err := newKqueue()
 	if err != nil {
 		return nil, err
@@ -147,7 +176,7 @@ func NewWatcher() (*Watcher, error) {
 		paths:        make(map[int]pathInfo),
 		fileExists:   make(map[string]struct{}),
 		userWatches:  make(map[string]struct{}),
-		Events:       make(chan Event),
+		Events:       make(chan Event, sz),
 		Errors:       make(chan error),
 		done:         make(chan struct{}),
 	}
@@ -197,8 +226,8 @@ func (w *Watcher) sendEvent(e Event) bool {
 	case w.Events <- e:
 		return true
 	case <-w.done:
+		return false
 	}
-	return false
 }
 
 // Returns true if the error was sent, or false if watcher is closed.
@@ -207,11 +236,11 @@ func (w *Watcher) sendError(err error) bool {
 	case w.Errors <- err:
 		return true
 	case <-w.done:
+		return false
 	}
-	return false
 }
 
-// Close removes all watches and closes the events channel.
+// Close removes all watches and closes the Events channel.
 func (w *Watcher) Close() error {
 	w.mu.Lock()
 	if w.isClosed {
@@ -239,17 +268,21 @@ func (w *Watcher) Close() error {
 
 // Add starts monitoring the path for changes.
 //
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
 //
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
 //
 // Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
 // filesystems (/proc, /sys, etc.) generally don't work.
 //
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
 // # Watching directories
 //
 // All files in a directory are monitored, including new files that are created
@@ -259,15 +292,28 @@ func (w *Watcher) Close() error {
 // # Watching files
 //
 // Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
-func (w *Watcher) Add(name string) error {
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+//     other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(name string, opts ...addOpt) error {
+	_ = getOptions(opts...)
+
 	w.mu.Lock()
 	w.userWatches[name] = struct{}{}
 	w.mu.Unlock()
@@ -281,9 +327,19 @@ func (w *Watcher) Add(name string) error {
 // /tmp/dir and /tmp/dir/subdir then you will need to remove both.
 //
 // Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
 func (w *Watcher) Remove(name string) error {
+	return w.remove(name, true)
+}
+
+func (w *Watcher) remove(name string, unwatchFiles bool) error {
 	name = filepath.Clean(name)
 	w.mu.Lock()
+	if w.isClosed {
+		w.mu.Unlock()
+		return nil
+	}
 	watchfd, ok := w.watches[name]
 	w.mu.Unlock()
 	if !ok {
@@ -315,7 +371,7 @@ func (w *Watcher) Remove(name string) error {
 	w.mu.Unlock()
 
 	// Find all watched paths that are in this directory that are not external.
-	if isDir {
+	if unwatchFiles && isDir {
 		var pathsToRemove []string
 		w.mu.Lock()
 		for fd := range w.watchesByDir[name] {
@@ -326,20 +382,25 @@ func (w *Watcher) Remove(name string) error {
 		}
 		w.mu.Unlock()
 		for _, name := range pathsToRemove {
-			// Since these are internal, not much sense in propagating error
-			// to the user, as that will just confuse them with an error about
-			// a path they did not explicitly watch themselves.
+			// Since these are internal, not much sense in propagating error to
+			// the user, as that will just confuse them with an error about a
+			// path they did not explicitly watch themselves.
 			w.Remove(name)
 		}
 	}
-
 	return nil
 }
 
-// WatchList returns all paths added with [Add] (and are not yet removed).
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
 func (w *Watcher) WatchList() []string {
 	w.mu.Lock()
 	defer w.mu.Unlock()
+	if w.isClosed {
+		return nil
+	}
 
 	entries := make([]string, 0, len(w.userWatches))
 	for pathname := range w.userWatches {
@@ -352,18 +413,18 @@ func (w *Watcher) WatchList() []string {
 // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
 const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
 
-// addWatch adds name to the watched file set.
-// The flags are interpreted as described in kevent(2).
-// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
+// addWatch adds name to the watched file set; the flags are interpreted as
+// described in kevent(2).
+//
+// Returns the real path to the file which was added, with symlinks resolved.
 func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
 	var isDir bool
-	// Make ./name and name equivalent
 	name = filepath.Clean(name)
 
 	w.mu.Lock()
 	if w.isClosed {
 		w.mu.Unlock()
-		return "", errors.New("kevent instance already closed")
+		return "", ErrClosed
 	}
 	watchfd, alreadyWatching := w.watches[name]
 	// We already have a watch, but we can still override flags.
@@ -383,27 +444,30 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
 			return "", nil
 		}
 
-		// Follow Symlinks
-		//
-		// Linux can add unresolvable symlinks to the watch list without issue,
-		// and Windows can't do symlinks period. To maintain consistency, we
-		// will act like everything is fine if the link can't be resolved.
-		// There will simply be no file events for broken symlinks. Hence the
-		// returns of nil on errors.
+		// Follow Symlinks.
 		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
-			name, err = filepath.EvalSymlinks(name)
+			link, err := os.Readlink(name)
 			if err != nil {
+				// Return nil because Linux can add unresolvable symlinks to the
+				// watch list without problems, so maintain consistency with
+				// that. There will be no file events for broken symlinks.
+				// TODO: more specific check; returns os.PathError; ENOENT?
 				return "", nil
 			}
 
 			w.mu.Lock()
-			_, alreadyWatching = w.watches[name]
+			_, alreadyWatching = w.watches[link]
 			w.mu.Unlock()
 
 			if alreadyWatching {
-				return name, nil
+				// Add to watches so we don't get spurious Create events later
+				// on when we diff the directories.
+				w.watches[name] = 0
+				w.fileExists[name] = struct{}{}
+				return link, nil
 			}
 
+			name = link
 			fi, err = os.Lstat(name)
 			if err != nil {
 				return "", nil
@@ -411,7 +475,7 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
 		}
 
 		// Retry on EINTR; open() can return EINTR in practice on macOS.
-		// See #354, and go issues 11180 and 39237.
+		// See #354, and Go issues 11180 and 39237.
 		for {
 			watchfd, err = unix.Open(name, openMode, 0)
 			if err == nil {
@@ -444,14 +508,13 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
 			w.watchesByDir[parentName] = watchesByDir
 		}
 		watchesByDir[watchfd] = struct{}{}
-
 		w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
 		w.mu.Unlock()
 	}
 
 	if isDir {
-		// Watch the directory if it has not been watched before,
-		// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
+		// Watch the directory if it has not been watched before, or if it was
+		// watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
 		w.mu.Lock()
 
 		watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
@@ -473,13 +536,10 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
 // Event values that it sends down the Events channel.
 func (w *Watcher) readEvents() {
 	defer func() {
-		err := unix.Close(w.kq)
-		if err != nil {
-			w.Errors <- err
-		}
-		unix.Close(w.closepipe[0])
 		close(w.Events)
 		close(w.Errors)
+		_ = unix.Close(w.kq)
+		unix.Close(w.closepipe[0])
 	}()
 
 	eventBuffer := make([]unix.Kevent_t, 10)
@@ -513,18 +573,8 @@ func (w *Watcher) readEvents() {
 
 			event := w.newEvent(path.name, mask)
 
-			if path.isDir && !event.Has(Remove) {
-				// Double check to make sure the directory exists. This can
-				// happen when we do a rm -fr on a recursively watched folders
-				// and we receive a modification event first but the folder has
-				// been deleted and later receive the delete event.
-				if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
-					event.Op |= Remove
-				}
-			}
-
 			if event.Has(Rename) || event.Has(Remove) {
-				w.Remove(event.Name)
+				w.remove(event.Name, false)
 				w.mu.Lock()
 				delete(w.fileExists, event.Name)
 				w.mu.Unlock()
@@ -540,26 +590,30 @@ func (w *Watcher) readEvents() {
 			}
 
 			if event.Has(Remove) {
-				// Look for a file that may have overwritten this.
-				// For example, mv f1 f2 will delete f2, then create f2.
+				// Look for a file that may have overwritten this; for example,
+				// mv f1 f2 will delete f2, then create f2.
 				if path.isDir {
 					fileDir := filepath.Clean(event.Name)
 					w.mu.Lock()
 					_, found := w.watches[fileDir]
 					w.mu.Unlock()
 					if found {
-						// make sure the directory exists before we watch for changes. When we
-						// do a recursive watch and perform rm -fr, the parent directory might
-						// have gone missing, ignore the missing directory and let the
-						// upcoming delete event remove the watch from the parent directory.
-						if _, err := os.Lstat(fileDir); err == nil {
-							w.sendDirectoryChangeEvents(fileDir)
+						err := w.sendDirectoryChangeEvents(fileDir)
+						if err != nil {
+							if !w.sendError(err) {
+								closed = true
+							}
 						}
 					}
 				} else {
 					filePath := filepath.Clean(event.Name)
-					if fileInfo, err := os.Lstat(filePath); err == nil {
-						w.sendFileCreatedEventIfNew(filePath, fileInfo)
+					if fi, err := os.Lstat(filePath); err == nil {
+						err := w.sendFileCreatedEventIfNew(filePath, fi)
+						if err != nil {
+							if !w.sendError(err) {
+								closed = true
+							}
+						}
 					}
 				}
 			}
@@ -582,21 +636,31 @@ func (w *Watcher) newEvent(name string, mask uint32) Event {
 	if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
 		e.Op |= Chmod
 	}
+	// No point sending a write and delete event at the same time: if it's gone,
+	// then it's gone.
+	if e.Op.Has(Write) && e.Op.Has(Remove) {
+		e.Op &^= Write
+	}
 	return e
 }
 
 // watchDirectoryFiles to mimic inotify when adding a watch on a directory
 func (w *Watcher) watchDirectoryFiles(dirPath string) error {
 	// Get all files
-	files, err := ioutil.ReadDir(dirPath)
+	files, err := os.ReadDir(dirPath)
 	if err != nil {
 		return err
 	}
 
-	for _, fileInfo := range files {
-		path := filepath.Join(dirPath, fileInfo.Name())
+	for _, f := range files {
+		path := filepath.Join(dirPath, f.Name())
+
+		fi, err := f.Info()
+		if err != nil {
+			return fmt.Errorf("%q: %w", path, err)
+		}
 
-		cleanPath, err := w.internalWatch(path, fileInfo)
+		cleanPath, err := w.internalWatch(path, fi)
 		if err != nil {
 			// No permission to read the file; that's not a problem: just skip.
 			// But do add it to w.fileExists to prevent it from being picked up
@@ -606,7 +670,7 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error {
 			case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
 				cleanPath = filepath.Clean(path)
 			default:
-				return fmt.Errorf("%q: %w", filepath.Join(dirPath, fileInfo.Name()), err)
+				return fmt.Errorf("%q: %w", path, err)
 			}
 		}
 
@@ -622,26 +686,37 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error {
 //
 // This functionality is to have the BSD watcher match the inotify, which sends
 // a create event for files created in a watched directory.
-func (w *Watcher) sendDirectoryChangeEvents(dir string) {
-	// Get all files
-	files, err := ioutil.ReadDir(dir)
+func (w *Watcher) sendDirectoryChangeEvents(dir string) error {
+	files, err := os.ReadDir(dir)
 	if err != nil {
-		if !w.sendError(fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)) {
-			return
+		// Directory no longer exists: we can ignore this safely. kqueue will
+		// still give us the correct events.
+		if errors.Is(err, os.ErrNotExist) {
+			return nil
 		}
+		return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
 	}
 
-	// Search for new files
-	for _, fi := range files {
-		err := w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi)
+	for _, f := range files {
+		fi, err := f.Info()
 		if err != nil {
-			return
+			return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
+		}
+
+		err = w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi)
+		if err != nil {
+			// Don't need to send an error if this file isn't readable.
+			if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) {
+				return nil
+			}
+			return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
 		}
 	}
+	return nil
 }
 
 // sendFileCreatedEvent sends a create event if the file isn't already being tracked.
-func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
+func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fi os.FileInfo) (err error) {
 	w.mu.Lock()
 	_, doesExist := w.fileExists[filePath]
 	w.mu.Unlock()
@@ -652,7 +727,7 @@ func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInf
 	}
 
 	// like watchDirectoryFiles (but without doing another ReadDir)
-	filePath, err = w.internalWatch(filePath, fileInfo)
+	filePath, err = w.internalWatch(filePath, fi)
 	if err != nil {
 		return err
 	}
@@ -664,10 +739,10 @@ func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInf
 	return nil
 }
 
-func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
-	if fileInfo.IsDir() {
-		// mimic Linux providing delete events for subdirectories
-		// but preserve the flags used if currently watching subdirectory
+func (w *Watcher) internalWatch(name string, fi os.FileInfo) (string, error) {
+	if fi.IsDir() {
+		// mimic Linux providing delete events for subdirectories, but preserve
+		// the flags used if currently watching subdirectory
 		w.mu.Lock()
 		flags := w.dirFlags[name]
 		w.mu.Unlock()

+ 172 - 33
vendor/github.com/fsnotify/fsnotify/backend_other.go

@@ -1,39 +1,169 @@
-//go:build !darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows
-// +build !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
+//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
+// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
+
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
 
 package fsnotify
 
-import (
-	"fmt"
-	"runtime"
-)
+import "errors"
 
-// Watcher watches a set of files, delivering events to a channel.
-type Watcher struct{}
+// Watcher watches a set of paths, delivering events on a channel.
+//
+// A watcher should not be copied (e.g. pass it by pointer, rather than by
+// value).
+//
+// # Linux notes
+//
+// When a file is removed a Remove event won't be emitted until all file
+// descriptors are closed, and deletes will always emit a Chmod. For example:
+//
+//	fp := os.Open("file")
+//	os.Remove("file")        // Triggers Chmod
+//	fp.Close()               // Triggers Remove
+//
+// This is the event that inotify sends, so not much can be changed about this.
+//
+// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
+// for the number of watches per user, and fs.inotify.max_user_instances
+// specifies the maximum number of inotify instances per user. Every Watcher you
+// create is an "instance", and every path you add is a "watch".
+//
+// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
+// /proc/sys/fs/inotify/max_user_instances
+//
+// To increase them you can use sysctl or write the value to the /proc file:
+//
+//	# Default values on Linux 5.18
+//	sysctl fs.inotify.max_user_watches=124983
+//	sysctl fs.inotify.max_user_instances=128
+//
+// To make the changes persist on reboot edit /etc/sysctl.conf or
+// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
+// your distro's documentation):
+//
+//	fs.inotify.max_user_watches=124983
+//	fs.inotify.max_user_instances=128
+//
+// Reaching the limit will result in a "no space left on device" or "too many open
+// files" error.
+//
+// # kqueue notes (macOS, BSD)
+//
+// kqueue requires opening a file descriptor for every file that's being watched;
+// so if you're watching a directory with five files then that's six file
+// descriptors. You will run in to your system's "max open files" limit faster on
+// these platforms.
+//
+// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
+// control the maximum number of open files, as well as /etc/login.conf on BSD
+// systems.
+//
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
+//
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
+//
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
+type Watcher struct {
+	// Events sends the filesystem change events.
+	//
+	// fsnotify can send the following events; a "path" here can refer to a
+	// file, directory, symbolic link, or special file like a FIFO.
+	//
+	//   fsnotify.Create    A new path was created; this may be followed by one
+	//                      or more Write events if data also gets written to a
+	//                      file.
+	//
+	//   fsnotify.Remove    A path was removed.
+	//
+	//   fsnotify.Rename    A path was renamed. A rename is always sent with the
+	//                      old path as Event.Name, and a Create event will be
+	//                      sent with the new name. Renames are only sent for
+	//                      paths that are currently watched; e.g. moving an
+	//                      unmonitored file into a monitored directory will
+	//                      show up as just a Create. Similarly, renaming a file
+	//                      to outside a monitored directory will show up as
+	//                      only a Rename.
+	//
+	//   fsnotify.Write     A file or named pipe was written to. A Truncate will
+	//                      also trigger a Write. A single "write action"
+	//                      initiated by the user may show up as one or multiple
+	//                      writes, depending on when the system syncs things to
+	//                      disk. For example when compiling a large Go program
+	//                      you may get hundreds of Write events, and you may
+	//                      want to wait until you've stopped receiving them
+	//                      (see the dedup example in cmd/fsnotify).
+	//
+	//                      Some systems may send Write event for directories
+	//                      when the directory content changes.
+	//
+	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
+	//                      when a file is removed (or more accurately, when a
+	//                      link to an inode is removed). On kqueue it's sent
+	//                      when a file is truncated. On Windows it's never
+	//                      sent.
+	Events chan Event
+
+	// Errors sends any errors.
+	//
+	// ErrEventOverflow is used to indicate there are too many events:
+	//
+	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
+	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
+	//  - kqueue, fen:  Not used.
+	Errors chan error
+}
 
 // NewWatcher creates a new Watcher.
 func NewWatcher() (*Watcher, error) {
-	return nil, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
+	return nil, errors.New("fsnotify not supported on the current platform")
 }
 
-// Close removes all watches and closes the events channel.
-func (w *Watcher) Close() error {
-	return nil
-}
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) { return NewWatcher() }
+
+// Close removes all watches and closes the Events channel.
+func (w *Watcher) Close() error { return nil }
+
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
+func (w *Watcher) WatchList() []string { return nil }
 
 // Add starts monitoring the path for changes.
 //
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
 //
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
 //
 // Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
 // filesystems (/proc, /sys, etc.) generally don't work.
 //
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
 // # Watching directories
 //
 // All files in a directory are monitored, including new files that are created
@@ -43,17 +173,26 @@ func (w *Watcher) Close() error {
 // # Watching files
 //
 // Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
-func (w *Watcher) Add(name string) error {
-	return nil
-}
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(name string) error { return nil }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+//     other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(name string, opts ...addOpt) error { return nil }
 
 // Remove stops monitoring the path for changes.
 //
@@ -61,6 +200,6 @@ func (w *Watcher) Add(name string) error {
 // /tmp/dir and /tmp/dir/subdir then you will need to remove both.
 //
 // Removing a path that has not yet been added returns [ErrNonExistentWatch].
-func (w *Watcher) Remove(name string) error {
-	return nil
-}
+//
+// Returns nil if [Watcher.Close] was called.
+func (w *Watcher) Remove(name string) error { return nil }

+ 164 - 83
vendor/github.com/fsnotify/fsnotify/backend_windows.go

@@ -1,6 +1,13 @@
 //go:build windows
 // +build windows
 
+// Windows backend based on ReadDirectoryChangesW()
+//
+// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
+//
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
 package fsnotify
 
 import (
@@ -27,9 +34,9 @@ import (
 // When a file is removed a Remove event won't be emitted until all file
 // descriptors are closed, and deletes will always emit a Chmod. For example:
 //
-//     fp := os.Open("file")
-//     os.Remove("file")        // Triggers Chmod
-//     fp.Close()               // Triggers Remove
+//	fp := os.Open("file")
+//	os.Remove("file")        // Triggers Chmod
+//	fp.Close()               // Triggers Remove
 //
 // This is the event that inotify sends, so not much can be changed about this.
 //
@@ -43,16 +50,16 @@ import (
 //
 // To increase them you can use sysctl or write the value to the /proc file:
 //
-//     # Default values on Linux 5.18
-//     sysctl fs.inotify.max_user_watches=124983
-//     sysctl fs.inotify.max_user_instances=128
+//	# Default values on Linux 5.18
+//	sysctl fs.inotify.max_user_watches=124983
+//	sysctl fs.inotify.max_user_instances=128
 //
 // To make the changes persist on reboot edit /etc/sysctl.conf or
 // /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
 // your distro's documentation):
 //
-//     fs.inotify.max_user_watches=124983
-//     fs.inotify.max_user_instances=128
+//	fs.inotify.max_user_watches=124983
+//	fs.inotify.max_user_instances=128
 //
 // Reaching the limit will result in a "no space left on device" or "too many open
 // files" error.
@@ -68,14 +75,20 @@ import (
 // control the maximum number of open files, as well as /etc/login.conf on BSD
 // systems.
 //
-// # macOS notes
+// # Windows notes
 //
-// Spotlight indexing on macOS can result in multiple events (see [#15]). A
-// temporary workaround is to add your folder(s) to the "Spotlight Privacy
-// Settings" until we have a native FSEvents implementation (see [#11]).
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
 //
-// [#11]: https://github.com/fsnotify/fsnotify/issues/11
-// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
+//
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
 type Watcher struct {
 	// Events sends the filesystem change events.
 	//
@@ -102,31 +115,52 @@ type Watcher struct {
 	//                      initiated by the user may show up as one or multiple
 	//                      writes, depending on when the system syncs things to
 	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, so you
-	//                      probably want to wait until you've stopped receiving
-	//                      them (see the dedup example in cmd/fsnotify).
+	//                      you may get hundreds of Write events, and you may
+	//                      want to wait until you've stopped receiving them
+	//                      (see the dedup example in cmd/fsnotify).
+	//
+	//                      Some systems may send Write event for directories
+	//                      when the directory content changes.
 	//
 	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
 	//                      when a file is removed (or more accurately, when a
 	//                      link to an inode is removed). On kqueue it's sent
-	//                      and on kqueue when a file is truncated. On Windows
-	//                      it's never sent.
+	//                      when a file is truncated. On Windows it's never
+	//                      sent.
 	Events chan Event
 
 	// Errors sends any errors.
+	//
+	// ErrEventOverflow is used to indicate there are too many events:
+	//
+	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
+	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
+	//  - kqueue, fen:  Not used.
 	Errors chan error
 
 	port  windows.Handle // Handle to completion port
 	input chan *input    // Inputs to the reader are sent on this channel
 	quit  chan chan<- error
 
-	mu       sync.Mutex // Protects access to watches, isClosed
-	watches  watchMap   // Map of watches (key: i-number)
-	isClosed bool       // Set to true when Close() is first called
+	mu      sync.Mutex // Protects access to watches, closed
+	watches watchMap   // Map of watches (key: i-number)
+	closed  bool       // Set to true when Close() is first called
 }
 
 // NewWatcher creates a new Watcher.
 func NewWatcher() (*Watcher, error) {
+	return NewBufferedWatcher(50)
+}
+
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) {
 	port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
 	if err != nil {
 		return nil, os.NewSyscallError("CreateIoCompletionPort", err)
@@ -135,7 +169,7 @@ func NewWatcher() (*Watcher, error) {
 		port:    port,
 		watches: make(watchMap),
 		input:   make(chan *input, 1),
-		Events:  make(chan Event, 50),
+		Events:  make(chan Event, sz),
 		Errors:  make(chan error),
 		quit:    make(chan chan<- error, 1),
 	}
@@ -143,6 +177,12 @@ func NewWatcher() (*Watcher, error) {
 	return w, nil
 }
 
+func (w *Watcher) isClosed() bool {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	return w.closed
+}
+
 func (w *Watcher) sendEvent(name string, mask uint64) bool {
 	if mask == 0 {
 		return false
@@ -167,14 +207,14 @@ func (w *Watcher) sendError(err error) bool {
 	return false
 }
 
-// Close removes all watches and closes the events channel.
+// Close removes all watches and closes the Events channel.
 func (w *Watcher) Close() error {
-	w.mu.Lock()
-	if w.isClosed {
-		w.mu.Unlock()
+	if w.isClosed() {
 		return nil
 	}
-	w.isClosed = true
+
+	w.mu.Lock()
+	w.closed = true
 	w.mu.Unlock()
 
 	// Send "quit" message to the reader goroutine
@@ -188,17 +228,21 @@ func (w *Watcher) Close() error {
 
 // Add starts monitoring the path for changes.
 //
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
 //
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
 //
 // Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
 // filesystems (/proc, /sys, etc.) generally don't work.
 //
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
 // # Watching directories
 //
 // All files in a directory are monitored, including new files that are created
@@ -208,27 +252,41 @@ func (w *Watcher) Close() error {
 // # Watching files
 //
 // Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
-func (w *Watcher) Add(name string) error {
-	w.mu.Lock()
-	if w.isClosed {
-		w.mu.Unlock()
-		return errors.New("watcher already closed")
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+//     other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(name string, opts ...addOpt) error {
+	if w.isClosed() {
+		return ErrClosed
+	}
+
+	with := getOptions(opts...)
+	if with.bufsize < 4096 {
+		return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes")
 	}
-	w.mu.Unlock()
 
 	in := &input{
-		op:    opAddWatch,
-		path:  filepath.Clean(name),
-		flags: sysFSALLEVENTS,
-		reply: make(chan error),
+		op:      opAddWatch,
+		path:    filepath.Clean(name),
+		flags:   sysFSALLEVENTS,
+		reply:   make(chan error),
+		bufsize: with.bufsize,
 	}
 	w.input <- in
 	if err := w.wakeupReader(); err != nil {
@@ -243,7 +301,13 @@ func (w *Watcher) Add(name string) error {
 // /tmp/dir and /tmp/dir/subdir then you will need to remove both.
 //
 // Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
 func (w *Watcher) Remove(name string) error {
+	if w.isClosed() {
+		return nil
+	}
+
 	in := &input{
 		op:    opRemoveWatch,
 		path:  filepath.Clean(name),
@@ -256,8 +320,15 @@ func (w *Watcher) Remove(name string) error {
 	return <-in.reply
 }
 
-// WatchList returns all paths added with [Add] (and are not yet removed).
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
 func (w *Watcher) WatchList() []string {
+	if w.isClosed() {
+		return nil
+	}
+
 	w.mu.Lock()
 	defer w.mu.Unlock()
 
@@ -279,7 +350,6 @@ func (w *Watcher) WatchList() []string {
 // This should all be removed at some point, and just use windows.FILE_NOTIFY_*
 const (
 	sysFSALLEVENTS  = 0xfff
-	sysFSATTRIB     = 0x4
 	sysFSCREATE     = 0x100
 	sysFSDELETE     = 0x200
 	sysFSDELETESELF = 0x400
@@ -305,9 +375,6 @@ func (w *Watcher) newEvent(name string, mask uint32) Event {
 	if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
 		e.Op |= Rename
 	}
-	if mask&sysFSATTRIB == sysFSATTRIB {
-		e.Op |= Chmod
-	}
 	return e
 }
 
@@ -321,10 +388,11 @@ const (
 )
 
 type input struct {
-	op    int
-	path  string
-	flags uint32
-	reply chan error
+	op      int
+	path    string
+	flags   uint32
+	bufsize int
+	reply   chan error
 }
 
 type inode struct {
@@ -334,13 +402,14 @@ type inode struct {
 }
 
 type watch struct {
-	ov     windows.Overlapped
-	ino    *inode            // i-number
-	path   string            // Directory path
-	mask   uint64            // Directory itself is being watched with these notify flags
-	names  map[string]uint64 // Map of names being watched and their notify flags
-	rename string            // Remembers the old name while renaming a file
-	buf    [65536]byte       // 64K buffer
+	ov      windows.Overlapped
+	ino     *inode            // i-number
+	recurse bool              // Recursive watch?
+	path    string            // Directory path
+	mask    uint64            // Directory itself is being watched with these notify flags
+	names   map[string]uint64 // Map of names being watched and their notify flags
+	rename  string            // Remembers the old name while renaming a file
+	buf     []byte            // buffer, allocated later
 }
 
 type (
@@ -413,7 +482,10 @@ func (m watchMap) set(ino *inode, watch *watch) {
 }
 
 // Must run within the I/O thread.
-func (w *Watcher) addWatch(pathname string, flags uint64) error {
+func (w *Watcher) addWatch(pathname string, flags uint64, bufsize int) error {
+	//pathname, recurse := recursivePath(pathname)
+	recurse := false
+
 	dir, err := w.getDir(pathname)
 	if err != nil {
 		return err
@@ -433,9 +505,11 @@ func (w *Watcher) addWatch(pathname string, flags uint64) error {
 			return os.NewSyscallError("CreateIoCompletionPort", err)
 		}
 		watchEntry = &watch{
-			ino:   ino,
-			path:  dir,
-			names: make(map[string]uint64),
+			ino:     ino,
+			path:    dir,
+			names:   make(map[string]uint64),
+			recurse: recurse,
+			buf:     make([]byte, bufsize),
 		}
 		w.mu.Lock()
 		w.watches.set(ino, watchEntry)
@@ -465,6 +539,8 @@ func (w *Watcher) addWatch(pathname string, flags uint64) error {
 
 // Must run within the I/O thread.
 func (w *Watcher) remWatch(pathname string) error {
+	pathname, recurse := recursivePath(pathname)
+
 	dir, err := w.getDir(pathname)
 	if err != nil {
 		return err
@@ -478,6 +554,10 @@ func (w *Watcher) remWatch(pathname string) error {
 	watch := w.watches.get(ino)
 	w.mu.Unlock()
 
+	if recurse && !watch.recurse {
+		return fmt.Errorf("can't use \\... with non-recursive watch %q", pathname)
+	}
+
 	err = windows.CloseHandle(ino.handle)
 	if err != nil {
 		w.sendError(os.NewSyscallError("CloseHandle", err))
@@ -535,8 +615,11 @@ func (w *Watcher) startRead(watch *watch) error {
 		return nil
 	}
 
-	rdErr := windows.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
-		uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
+	// We need to pass the array, rather than the slice.
+	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&watch.buf))
+	rdErr := windows.ReadDirectoryChanges(watch.ino.handle,
+		(*byte)(unsafe.Pointer(hdr.Data)), uint32(hdr.Len),
+		watch.recurse, mask, nil, &watch.ov, 0)
 	if rdErr != nil {
 		err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
 		if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
@@ -563,9 +646,8 @@ func (w *Watcher) readEvents() {
 	runtime.LockOSThread()
 
 	for {
+		// This error is handled after the watch == nil check below.
 		qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
-		// This error is handled after the watch == nil check below. NOTE: this
-		// seems odd, note sure if it's correct.
 
 		watch := (*watch)(unsafe.Pointer(ov))
 		if watch == nil {
@@ -595,7 +677,7 @@ func (w *Watcher) readEvents() {
 			case in := <-w.input:
 				switch in.op {
 				case opAddWatch:
-					in.reply <- w.addWatch(in.path, uint64(in.flags))
+					in.reply <- w.addWatch(in.path, uint64(in.flags), in.bufsize)
 				case opRemoveWatch:
 					in.reply <- w.remWatch(in.path)
 				}
@@ -605,6 +687,8 @@ func (w *Watcher) readEvents() {
 		}
 
 		switch qErr {
+		case nil:
+			// No error
 		case windows.ERROR_MORE_DATA:
 			if watch == nil {
 				w.sendError(errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer"))
@@ -626,13 +710,12 @@ func (w *Watcher) readEvents() {
 		default:
 			w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr))
 			continue
-		case nil:
 		}
 
 		var offset uint32
 		for {
 			if n == 0 {
-				w.sendError(errors.New("short read in readEvents()"))
+				w.sendError(ErrEventOverflow)
 				break
 			}
 
@@ -703,8 +786,9 @@ func (w *Watcher) readEvents() {
 
 			// Error!
 			if offset >= n {
+				//lint:ignore ST1005 Windows should be capitalized
 				w.sendError(errors.New(
-					"Windows system assumed buffer larger than it is, events have likely been missed."))
+					"Windows system assumed buffer larger than it is, events have likely been missed"))
 				break
 			}
 		}
@@ -720,9 +804,6 @@ func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
 	if mask&sysFSMODIFY != 0 {
 		m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
 	}
-	if mask&sysFSATTRIB != 0 {
-		m |= windows.FILE_NOTIFY_CHANGE_ATTRIBUTES
-	}
 	if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
 		m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME
 	}

+ 78 - 13
vendor/github.com/fsnotify/fsnotify/fsnotify.go

@@ -1,13 +1,18 @@
-//go:build !plan9
-// +build !plan9
-
 // Package fsnotify provides a cross-platform interface for file system
 // notifications.
+//
+// Currently supported systems:
+//
+//	Linux 2.6.32+    via inotify
+//	BSD, macOS       via kqueue
+//	Windows          via ReadDirectoryChangesW
+//	illumos          via FEN
 package fsnotify
 
 import (
 	"errors"
 	"fmt"
+	"path/filepath"
 	"strings"
 )
 
@@ -33,34 +38,52 @@ type Op uint32
 // The operations fsnotify can trigger; see the documentation on [Watcher] for a
 // full description, and check them with [Event.Has].
 const (
+	// A new pathname was created.
 	Create Op = 1 << iota
+
+	// The pathname was written to; this does *not* mean the write has finished,
+	// and a write can be followed by more writes.
 	Write
+
+	// The path was removed; any watches on it will be removed. Some "remove"
+	// operations may trigger a Rename if the file is actually moved (for
+	// example "remove to trash" is often a rename).
 	Remove
+
+	// The path was renamed to something else; any watched on it will be
+	// removed.
 	Rename
+
+	// File attributes were changed.
+	//
+	// It's generally not recommended to take action on this event, as it may
+	// get triggered very frequently by some software. For example, Spotlight
+	// indexing on macOS, anti-virus software, backup software, etc.
 	Chmod
 )
 
-// Common errors that can be reported by a watcher
+// Common errors that can be reported.
 var (
-	ErrNonExistentWatch = errors.New("can't remove non-existent watcher")
-	ErrEventOverflow    = errors.New("fsnotify queue overflow")
+	ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch")
+	ErrEventOverflow    = errors.New("fsnotify: queue or buffer overflow")
+	ErrClosed           = errors.New("fsnotify: watcher already closed")
 )
 
-func (op Op) String() string {
+func (o Op) String() string {
 	var b strings.Builder
-	if op.Has(Create) {
+	if o.Has(Create) {
 		b.WriteString("|CREATE")
 	}
-	if op.Has(Remove) {
+	if o.Has(Remove) {
 		b.WriteString("|REMOVE")
 	}
-	if op.Has(Write) {
+	if o.Has(Write) {
 		b.WriteString("|WRITE")
 	}
-	if op.Has(Rename) {
+	if o.Has(Rename) {
 		b.WriteString("|RENAME")
 	}
-	if op.Has(Chmod) {
+	if o.Has(Chmod) {
 		b.WriteString("|CHMOD")
 	}
 	if b.Len() == 0 {
@@ -70,7 +93,7 @@ func (op Op) String() string {
 }
 
 // Has reports if this operation has the given operation.
-func (o Op) Has(h Op) bool { return o&h == h }
+func (o Op) Has(h Op) bool { return o&h != 0 }
 
 // Has reports if this event has the given operation.
 func (e Event) Has(op Op) bool { return e.Op.Has(op) }
@@ -79,3 +102,45 @@ func (e Event) Has(op Op) bool { return e.Op.Has(op) }
 func (e Event) String() string {
 	return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
 }
+
+type (
+	addOpt   func(opt *withOpts)
+	withOpts struct {
+		bufsize int
+	}
+)
+
+var defaultOpts = withOpts{
+	bufsize: 65536, // 64K
+}
+
+func getOptions(opts ...addOpt) withOpts {
+	with := defaultOpts
+	for _, o := range opts {
+		o(&with)
+	}
+	return with
+}
+
+// WithBufferSize sets the [ReadDirectoryChangesW] buffer size.
+//
+// This only has effect on Windows systems, and is a no-op for other backends.
+//
+// The default value is 64K (65536 bytes) which is the highest value that works
+// on all filesystems and should be enough for most applications, but if you
+// have a large burst of events it may not be enough. You can increase it if
+// you're hitting "queue or buffer overflow" errors ([ErrEventOverflow]).
+//
+// [ReadDirectoryChangesW]: https://learn.microsoft.com/en-gb/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
+func WithBufferSize(bytes int) addOpt {
+	return func(opt *withOpts) { opt.bufsize = bytes }
+}
+
+// Check if this path is recursive (ends with "/..." or "\..."), and return the
+// path with the /... stripped.
+func recursivePath(path string) (string, bool) {
+	if filepath.Base(path) == "..." {
+		return filepath.Dir(path), true
+	}
+	return path, false
+}

+ 88 - 37
vendor/github.com/fsnotify/fsnotify/mkdoc.zsh

@@ -2,8 +2,8 @@
 [ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1
 setopt err_exit no_unset pipefail extended_glob
 
-# Simple script to update the godoc comments on all watchers. Probably took me
-# more time to write this than doing it manually, but ah well 🙃
+# Simple script to update the godoc comments on all watchers so you don't need
+# to update the same comment 5 times.
 
 watcher=$(<<EOF
 // Watcher watches a set of paths, delivering events on a channel.
@@ -16,9 +16,9 @@ watcher=$(<<EOF
 // When a file is removed a Remove event won't be emitted until all file
 // descriptors are closed, and deletes will always emit a Chmod. For example:
 //
-//     fp := os.Open("file")
-//     os.Remove("file")        // Triggers Chmod
-//     fp.Close()               // Triggers Remove
+//	fp := os.Open("file")
+//	os.Remove("file")        // Triggers Chmod
+//	fp.Close()               // Triggers Remove
 //
 // This is the event that inotify sends, so not much can be changed about this.
 //
@@ -32,16 +32,16 @@ watcher=$(<<EOF
 //
 // To increase them you can use sysctl or write the value to the /proc file:
 //
-//     # Default values on Linux 5.18
-//     sysctl fs.inotify.max_user_watches=124983
-//     sysctl fs.inotify.max_user_instances=128
+//	# Default values on Linux 5.18
+//	sysctl fs.inotify.max_user_watches=124983
+//	sysctl fs.inotify.max_user_instances=128
 //
 // To make the changes persist on reboot edit /etc/sysctl.conf or
 // /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
 // your distro's documentation):
 //
-//     fs.inotify.max_user_watches=124983
-//     fs.inotify.max_user_instances=128
+//	fs.inotify.max_user_watches=124983
+//	fs.inotify.max_user_instances=128
 //
 // Reaching the limit will result in a "no space left on device" or "too many open
 // files" error.
@@ -57,14 +57,20 @@ watcher=$(<<EOF
 // control the maximum number of open files, as well as /etc/login.conf on BSD
 // systems.
 //
-// # macOS notes
+// # Windows notes
 //
-// Spotlight indexing on macOS can result in multiple events (see [#15]). A
-// temporary workaround is to add your folder(s) to the "Spotlight Privacy
-// Settings" until we have a native FSEvents implementation (see [#11]).
+// Paths can be added as "C:\\path\\to\\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
 //
-// [#11]: https://github.com/fsnotify/fsnotify/issues/11
-// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
+//
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
 EOF
 )
 
@@ -73,20 +79,36 @@ new=$(<<EOF
 EOF
 )
 
+newbuffered=$(<<EOF
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+EOF
+)
+
 add=$(<<EOF
 // Add starts monitoring the path for changes.
 //
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
 //
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
 //
 // Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
 // filesystems (/proc, /sys, etc.) generally don't work.
 //
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
 // # Watching directories
 //
 // All files in a directory are monitored, including new files that are created
@@ -96,14 +118,27 @@ add=$(<<EOF
 // # Watching files
 //
 // Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+EOF
+)
+
+addwith=$(<<EOF
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+//     other platforms. The default is 64K (65536 bytes).
 EOF
 )
 
@@ -114,16 +149,21 @@ remove=$(<<EOF
 // /tmp/dir and /tmp/dir/subdir then you will need to remove both.
 //
 // Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
 EOF
 )
 
 close=$(<<EOF
-// Close removes all watches and closes the events channel.
+// Close removes all watches and closes the Events channel.
 EOF
 )
 
 watchlist=$(<<EOF
-// WatchList returns all paths added with [Add] (and are not yet removed).
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
 EOF
 )
 
@@ -153,20 +193,29 @@ events=$(<<EOF
 	//                      initiated by the user may show up as one or multiple
 	//                      writes, depending on when the system syncs things to
 	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, so you
-	//                      probably want to wait until you've stopped receiving
-	//                      them (see the dedup example in cmd/fsnotify).
+	//                      you may get hundreds of Write events, and you may
+	//                      want to wait until you've stopped receiving them
+	//                      (see the dedup example in cmd/fsnotify).
+	//
+	//                      Some systems may send Write event for directories
+	//                      when the directory content changes.
 	//
 	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
 	//                      when a file is removed (or more accurately, when a
 	//                      link to an inode is removed). On kqueue it's sent
-	//                      and on kqueue when a file is truncated. On Windows
-	//                      it's never sent.
+	//                      when a file is truncated. On Windows it's never
+	//                      sent.
 EOF
 )
 
 errors=$(<<EOF
 	// Errors sends any errors.
+	//
+	// ErrEventOverflow is used to indicate there are too many events:
+	//
+	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
+	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
+	//  - kqueue, fen:  Not used.
 EOF
 )
 
@@ -200,7 +249,9 @@ set-cmt() {
 
 set-cmt '^type Watcher struct '             $watcher
 set-cmt '^func NewWatcher('                 $new
+set-cmt '^func NewBufferedWatcher('         $newbuffered
 set-cmt '^func (w \*Watcher) Add('          $add
+set-cmt '^func (w \*Watcher) AddWith('      $addwith
 set-cmt '^func (w \*Watcher) Remove('       $remove
 set-cmt '^func (w \*Watcher) Close('        $close
 set-cmt '^func (w \*Watcher) WatchList('    $watchlist

+ 1 - 0
vendor/github.com/go-gl/gl/v2.1/gl/KHR/dummy.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 // Package dummy prevents go tooling from stripping the c dependencies.

+ 1 - 0
vendor/github.com/go-gl/gl/v2.1/gl/build_cgo_hack.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 package gl

+ 3 - 3
vendor/github.com/go-gl/gl/v2.1/gl/conversions.go

@@ -17,9 +17,9 @@ import "C"
 //
 // For example:
 //
-// 	var data []uint8
-// 	...
-// 	gl.TexImage2D(gl.TEXTURE_2D, ..., gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
+//	var data []uint8
+//	...
+//	gl.TexImage2D(gl.TEXTURE_2D, ..., gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
 func Ptr(data interface{}) unsafe.Pointer {
 	if data == nil {
 		return unsafe.Pointer(nil)

+ 1 - 1
vendor/github.com/go-gl/gl/v2.1/gl/package.go

@@ -12,8 +12,8 @@
 // Package gl implements Go bindings to OpenGL.
 //
 // This package was automatically generated using Glow:
-//  https://github.com/go-gl/glow
 //
+//	https://github.com/go-gl/glow
 package gl
 
 // #cgo !gles2,darwin        LDFLAGS: -framework OpenGL

+ 5 - 5
vendor/github.com/go-gl/gl/v2.1/gl/procaddr.go

@@ -33,7 +33,7 @@ package gl
 #if defined(TAG_EGL)
 	#include <stdlib.h>
 	#include <EGL/egl.h>
-	void* GlowGetProcAddress_gl21(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		return eglGetProcAddress(name);
 	}
 #elif defined(TAG_WINDOWS)
@@ -41,7 +41,7 @@ package gl
 	#include <windows.h>
 	#include <stdlib.h>
 	static HMODULE ogl32dll = NULL;
-	void* GlowGetProcAddress_gl21(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		void* pf = wglGetProcAddress((LPCSTR) name);
 		if (pf) {
 			return pf;
@@ -54,13 +54,13 @@ package gl
 #elif defined(TAG_DARWIN)
 	#include <stdlib.h>
 	#include <dlfcn.h>
-	void* GlowGetProcAddress_gl21(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		return dlsym(RTLD_DEFAULT, name);
 	}
 #elif defined(TAG_POSIX)
 	#include <stdlib.h>
 	#include <GL/glx.h>
-	void* GlowGetProcAddress_gl21(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		return glXGetProcAddress((const GLubyte *) name);
 	}
 #endif
@@ -71,5 +71,5 @@ import "unsafe"
 func getProcAddress(namea string) unsafe.Pointer {
 	cname := C.CString(namea)
 	defer C.free(unsafe.Pointer(cname))
-	return C.GlowGetProcAddress_gl21(cname)
+	return C.GlowGetProcAddress(cname)
 }

+ 1 - 0
vendor/github.com/go-gl/gl/v3.1/gles2/KHR/dummy.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 // Package dummy prevents go tooling from stripping the c dependencies.

+ 1 - 0
vendor/github.com/go-gl/gl/v3.1/gles2/build_cgo_hack.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 package gles2

+ 3 - 3
vendor/github.com/go-gl/gl/v3.1/gles2/conversions.go

@@ -17,9 +17,9 @@ import "C"
 //
 // For example:
 //
-// 	var data []uint8
-// 	...
-// 	gl.TexImage2D(gl.TEXTURE_2D, ..., gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
+//	var data []uint8
+//	...
+//	gl.TexImage2D(gl.TEXTURE_2D, ..., gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
 func Ptr(data interface{}) unsafe.Pointer {
 	if data == nil {
 		return unsafe.Pointer(nil)

+ 1 - 1
vendor/github.com/go-gl/gl/v3.1/gles2/package.go

@@ -12,8 +12,8 @@
 // Package gles2 implements Go bindings to OpenGL.
 //
 // This package was automatically generated using Glow:
-//  https://github.com/go-gl/glow
 //
+//	https://github.com/go-gl/glow
 package gles2
 
 // #cgo !gles2,darwin        LDFLAGS: -framework OpenGL

+ 5 - 5
vendor/github.com/go-gl/gl/v3.1/gles2/procaddr.go

@@ -33,7 +33,7 @@ package gles2
 #if defined(TAG_EGL)
 	#include <stdlib.h>
 	#include <EGL/egl.h>
-	void* GlowGetProcAddress_gles231(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		return eglGetProcAddress(name);
 	}
 #elif defined(TAG_WINDOWS)
@@ -41,7 +41,7 @@ package gles2
 	#include <windows.h>
 	#include <stdlib.h>
 	static HMODULE ogl32dll = NULL;
-	void* GlowGetProcAddress_gles231(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		void* pf = wglGetProcAddress((LPCSTR) name);
 		if (pf) {
 			return pf;
@@ -54,13 +54,13 @@ package gles2
 #elif defined(TAG_DARWIN)
 	#include <stdlib.h>
 	#include <dlfcn.h>
-	void* GlowGetProcAddress_gles231(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		return dlsym(RTLD_DEFAULT, name);
 	}
 #elif defined(TAG_POSIX)
 	#include <stdlib.h>
 	#include <GL/glx.h>
-	void* GlowGetProcAddress_gles231(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		return glXGetProcAddress((const GLubyte *) name);
 	}
 #endif
@@ -71,5 +71,5 @@ import "unsafe"
 func getProcAddress(namea string) unsafe.Pointer {
 	cname := C.CString(namea)
 	defer C.free(unsafe.Pointer(cname))
-	return C.GlowGetProcAddress_gles231(cname)
+	return C.GlowGetProcAddress(cname)
 }

+ 1 - 0
vendor/github.com/go-gl/gl/v3.2-core/gl/KHR/dummy.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 // Package dummy prevents go tooling from stripping the c dependencies.

+ 1 - 0
vendor/github.com/go-gl/gl/v3.2-core/gl/build_cgo_hack.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 package gl

+ 3 - 3
vendor/github.com/go-gl/gl/v3.2-core/gl/conversions.go

@@ -17,9 +17,9 @@ import "C"
 //
 // For example:
 //
-// 	var data []uint8
-// 	...
-// 	gl.TexImage2D(gl.TEXTURE_2D, ..., gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
+//	var data []uint8
+//	...
+//	gl.TexImage2D(gl.TEXTURE_2D, ..., gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
 func Ptr(data interface{}) unsafe.Pointer {
 	if data == nil {
 		return unsafe.Pointer(nil)

+ 1 - 1
vendor/github.com/go-gl/gl/v3.2-core/gl/package.go

@@ -12,8 +12,8 @@
 // Package gl implements Go bindings to OpenGL.
 //
 // This package was automatically generated using Glow:
-//  https://github.com/go-gl/glow
 //
+//	https://github.com/go-gl/glow
 package gl
 
 // #cgo !gles2,darwin        LDFLAGS: -framework OpenGL

+ 5 - 5
vendor/github.com/go-gl/gl/v3.2-core/gl/procaddr.go

@@ -33,7 +33,7 @@ package gl
 #if defined(TAG_EGL)
 	#include <stdlib.h>
 	#include <EGL/egl.h>
-	void* GlowGetProcAddress_glcore32(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		return eglGetProcAddress(name);
 	}
 #elif defined(TAG_WINDOWS)
@@ -41,7 +41,7 @@ package gl
 	#include <windows.h>
 	#include <stdlib.h>
 	static HMODULE ogl32dll = NULL;
-	void* GlowGetProcAddress_glcore32(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		void* pf = wglGetProcAddress((LPCSTR) name);
 		if (pf) {
 			return pf;
@@ -54,13 +54,13 @@ package gl
 #elif defined(TAG_DARWIN)
 	#include <stdlib.h>
 	#include <dlfcn.h>
-	void* GlowGetProcAddress_glcore32(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		return dlsym(RTLD_DEFAULT, name);
 	}
 #elif defined(TAG_POSIX)
 	#include <stdlib.h>
 	#include <GL/glx.h>
-	void* GlowGetProcAddress_glcore32(const char* name) {
+	static void* GlowGetProcAddress(const char* name) {
 		return glXGetProcAddress((const GLubyte *) name);
 	}
 #endif
@@ -71,5 +71,5 @@ import "unsafe"
 func getProcAddress(namea string) unsafe.Pointer {
 	cname := C.CString(namea)
 	defer C.free(unsafe.Pointer(cname))
-	return C.GlowGetProcAddress_glcore32(cname)
+	return C.GlowGetProcAddress(cname)
 }

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/build_cgo_hack.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 package glfw

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/c_glfw_bsd.go

@@ -1,3 +1,4 @@
+//go:build freebsd || netbsd || openbsd
 // +build freebsd netbsd openbsd
 
 package glfw

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/c_glfw_lin.go

@@ -1,3 +1,4 @@
+//go:build linux
 // +build linux
 
 package glfw

+ 1 - 2
vendor/github.com/go-gl/glfw/v3.3/glfw/error.go

@@ -145,8 +145,7 @@ func flushErrors() {
 //
 // Platform errors are always printed, for information why please see:
 //
-//  https://github.com/go-gl/glfw/issues/127
-//
+//	https://github.com/go-gl/glfw/issues/127
 func acceptError(codes ...ErrorCode) error {
 	// Grab the next error, if there is one.
 	err := fetchError()

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/dummy.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 // Package dummy prevents go tooling from stripping the c dependencies.

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/glad/dummy.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 // Package dummy prevents go tooling from stripping the c dependencies.

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/mingw/dummy.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 // Package dummy prevents go tooling from stripping the c dependencies.

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/vs2008/dummy.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 // Package dummy prevents go tooling from stripping the c dependencies.

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/include/GLFW/dummy.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 // Package dummy prevents go tooling from stripping the c dependencies.

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/dummy.go

@@ -1,3 +1,4 @@
+//go:build required
 // +build required
 
 // Package dummy prevents go tooling from stripping the c dependencies.

+ 1 - 19
vendor/github.com/go-gl/glfw/v3.3/glfw/input.go

@@ -20,7 +20,6 @@ import "C"
 
 import (
 	"image"
-	"image/draw"
 	"unsafe"
 )
 
@@ -496,24 +495,7 @@ func (w *Window) SetCursorPos(xpos, ypos float64) {
 // The cursor hotspot is specified in pixels, relative to the upper-left corner of the cursor image.
 // Like all other coordinate systems in GLFW, the X-axis points to the right and the Y-axis points down.
 func CreateCursor(img image.Image, xhot, yhot int) *Cursor {
-	var imgC C.GLFWimage
-	var pixels []uint8
-	b := img.Bounds()
-
-	switch img := img.(type) {
-	case *image.NRGBA:
-		pixels = img.Pix
-	default:
-		m := image.NewNRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
-		draw.Draw(m, m.Bounds(), img, b.Min, draw.Src)
-		pixels = m.Pix
-	}
-
-	pix, free := bytes(pixels)
-
-	imgC.width = C.int(b.Dx())
-	imgC.height = C.int(b.Dy())
-	imgC.pixels = (*C.uchar)(pix)
+	imgC, free := imageToGLFW(img)
 
 	c := C.glfwCreateCursor(&imgC, C.int(xhot), C.int(yhot))
 

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/native_linbsd_wayland.go

@@ -1,3 +1,4 @@
+//go:build (linux && wayland) || (freebsd && wayland) || (netbsd && wayland) || (openbsd && wayland)
 // +build linux,wayland freebsd,wayland netbsd,wayland openbsd,wayland
 
 package glfw

+ 1 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/native_linbsd_x11.go

@@ -1,3 +1,4 @@
+//go:build (linux && !wayland) || (freebsd && !wayland) || (netbsd && !wayland) || (openbsd && !wayland)
 // +build linux,!wayland freebsd,!wayland netbsd,!wayland openbsd,!wayland
 
 package glfw

+ 26 - 0
vendor/github.com/go-gl/glfw/v3.3/glfw/util.go

@@ -5,6 +5,11 @@ package glfw
 //#include "glfw/include/GLFW/glfw3.h"
 import "C"
 
+import (
+	"image"
+	"image/draw"
+)
+
 func glfwbool(b C.int) bool {
 	return b == C.int(True)
 }
@@ -18,3 +23,24 @@ func bytes(origin []byte) (pointer *uint8, free func()) {
 	ptr := C.CBytes(origin)
 	return (*uint8)(ptr), func() { C.free(ptr) }
 }
+
+// imageToGLFW converts img to be compatible with C.GLFWimage.
+func imageToGLFW(img image.Image) (r C.GLFWimage, free func()) {
+	b := img.Bounds()
+
+	r.width = C.int(b.Dx())
+	r.height = C.int(b.Dy())
+
+	var pixels []byte
+	if m, ok := img.(*image.NRGBA); ok && m.Stride == b.Dx()*4 {
+		pixels = m.Pix[:m.PixOffset(m.Rect.Min.X, m.Rect.Max.Y)]
+	} else {
+		m := image.NewNRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
+		draw.Draw(m, m.Bounds(), img, b.Min, draw.Src)
+		pixels = m.Pix
+	}
+
+	pix, free := bytes(pixels)
+	r.pixels = (*C.uchar)(pix)
+	return r, free
+}

+ 1 - 19
vendor/github.com/go-gl/glfw/v3.3/glfw/window.go

@@ -16,7 +16,6 @@ import "C"
 
 import (
 	"image"
-	"image/draw"
 	"sync"
 	"unsafe"
 )
@@ -412,24 +411,7 @@ func (w *Window) SetIcon(images []image.Image) {
 	freePixels := make([]func(), count)
 
 	for i, img := range images {
-		var pixels []uint8
-		b := img.Bounds()
-
-		switch img := img.(type) {
-		case *image.NRGBA:
-			pixels = img.Pix
-		default:
-			m := image.NewNRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
-			draw.Draw(m, m.Bounds(), img, b.Min, draw.Src)
-			pixels = m.Pix
-		}
-
-		pix, free := bytes(pixels)
-		freePixels[i] = free
-
-		cimages[i].width = C.int(b.Dx())
-		cimages[i].height = C.int(b.Dy())
-		cimages[i].pixels = (*C.uchar)(pix)
+		cimages[i], freePixels[i] = imageToGLFW(img)
 	}
 
 	var p *C.GLFWimage

+ 6 - 0
vendor/github.com/go-text/typesetting/opentype/api/font/variations.go

@@ -296,6 +296,12 @@ func unpackDeltas(data []byte, pointNumbersCount int) ([]int16, error) {
 			nbRead += int(count)
 			data = data[1:]
 		} else {
+			// we want to fill out[nbRead:nbRead+count-1], that is we must have
+			// nbRead+count-1 < pointNumbersCount, ie
+			// nbRead+count <= pointNumbersCount
+			if got := nbRead + int(count); got > pointNumbersCount {
+				return nil, fmt.Errorf("invalid packed deltas (expected %d point numbers, got %d)", pointNumbersCount, got)
+			}
 			isInt16 := control&deltasAreWords != 0
 			if isInt16 {
 				if len(data) < 1+2*int(count) {

+ 12 - 0
vendor/github.com/go-text/typesetting/opentype/loader/reader.go

@@ -8,6 +8,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"sort"
 )
 
 var (
@@ -161,6 +162,17 @@ func (pr *Loader) HasTable(table Tag) bool {
 	return has
 }
 
+// Tables returns all the tables found in the file,
+// as a sorted slice.
+func (ld *Loader) Tables() []Tag {
+	out := make([]Tag, 0, len(ld.tables))
+	for tag := range ld.tables {
+		out = append(out, tag)
+	}
+	sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
+	return out
+}
+
 // RawTable returns the binary content of the given table,
 // or an error if not found.
 func (pr *Loader) RawTable(tag Tag) ([]byte, error) {

+ 7 - 2
vendor/github.com/go-text/typesetting/opentype/loader/reader_otf.go

@@ -17,8 +17,13 @@ type otfEntry struct {
 	Length   uint32
 }
 
+const (
+	otfHeaderSize = 12
+	otfEntrySize  = 16
+)
+
 func readOTFHeader(r io.Reader) (flavor Tag, numTables uint16, err error) {
-	var buf [12]byte
+	var buf [otfHeaderSize]byte
 	if _, err := r.Read(buf[:]); err != nil {
 		return 0, 0, fmt.Errorf("invalid OpenType header: %s", err)
 	}
@@ -28,7 +33,7 @@ func readOTFHeader(r io.Reader) (flavor Tag, numTables uint16, err error) {
 
 func readOTFEntry(r io.Reader) (otfEntry, error) {
 	var (
-		buf   [16]byte
+		buf   [otfEntrySize]byte
 		entry otfEntry
 	)
 	if _, err := io.ReadFull(r, buf[:]); err != nil {

+ 80 - 0
vendor/github.com/go-text/typesetting/opentype/loader/writer.go

@@ -0,0 +1,80 @@
+package loader
+
+import (
+	"encoding/binary"
+	"math"
+)
+
+// Table is one opentype binary table and its tag.
+type Table struct {
+	Content []byte
+	Tag     Tag
+}
+
+// WriteTTF creates a single Truetype font file (.ttf) from the given [tables] slice,
+// which must be sorted by Tag
+func WriteTTF(tables []Table) []byte {
+	introLength := uint32(otfHeaderSize + len(tables)*otfEntrySize)
+	buffer := make([]byte, introLength)
+
+	writeTTFHeader(len(tables), buffer)
+
+	tableOffset := introLength // the actual content will start after the header + table directory
+	for i, table := range tables {
+		cs := checksum(table.Content)
+		tableLength := uint32(len(table.Content))
+
+		slice := buffer[otfHeaderSize+i*otfEntrySize:]
+		binary.BigEndian.PutUint32(slice, uint32(table.Tag))
+		binary.BigEndian.PutUint32(slice[4:], cs)
+		binary.BigEndian.PutUint32(slice[8:], tableOffset)
+		binary.BigEndian.PutUint32(slice[12:], tableLength)
+
+		// update the offset
+		tableOffset = tableOffset + tableLength
+	}
+
+	// append the actual table content :
+	// allocate only once
+	buffer = append(buffer, make([]byte, tableOffset-introLength)...)
+	tableOffset = introLength
+	for _, table := range tables {
+		copy(buffer[tableOffset:], table.Content)
+		tableOffset = tableOffset + uint32(len(table.Content))
+	}
+
+	return buffer
+}
+
+// out is assumed to have a length >= ttfHeaderSize
+func writeTTFHeader(nTables int, out []byte) {
+	log2 := math.Floor(math.Log2(float64(nTables)))
+	// Maximum power of 2 less than or equal to numTables, times 16 ((2**floor(log2(numTables))) * 16, where “**” is an exponentiation operator).
+	searchRange := math.Pow(2, log2) * 16
+	// Log2 of the maximum power of 2 less than or equal to numTables (log2(searchRange/16), which is equal to floor(log2(numTables))).
+	entrySelector := log2
+	// numTables times 16, minus searchRange ((numTables * 16) - searchRange).
+	rangeShift := nTables*16 - int(searchRange)
+
+	binary.BigEndian.PutUint32(out[:], uint32(TrueType))
+	binary.BigEndian.PutUint16(out[4:], uint16(nTables))
+	binary.BigEndian.PutUint16(out[6:], uint16(searchRange))
+	binary.BigEndian.PutUint16(out[8:], uint16(entrySelector))
+	binary.BigEndian.PutUint16(out[10:], uint16(rangeShift))
+}
+
+func checksum(table []byte) uint32 {
+	// "To accommodate data with a length that is not a multiple of four,
+	// the above algorithm must be modified to treat the data as though
+	// it contains zero padding to a length that is a multiple of four."
+	if r := len(table) % 4; r != 0 {
+		table = append(table, make([]byte, r)...)
+	}
+
+	var sum uint32
+	for i := 0; i < len(table)/4; i++ {
+		sum += binary.BigEndian.Uint32(table[i*4:])
+	}
+
+	return sum
+}

+ 23 - 0
vendor/github.com/go-text/typesetting/shaping/output.go

@@ -128,6 +128,29 @@ func (o *Output) RecomputeAdvance() {
 	o.Advance = advance
 }
 
+// advanceSpaceAware adjust the value in [Advance]
+// if a white space character ends the run.
+// TODO: should we take into account multiple spaces ?
+func (o *Output) advanceSpaceAware() fixed.Int26_6 {
+	L := len(o.Glyphs)
+	if L == 0 {
+		return o.Advance
+	}
+
+	// adjust the last to account for spaces
+	if o.Direction.IsVertical() {
+		if g := o.Glyphs[L-1]; g.Height == 0 {
+			return o.Advance - g.YAdvance
+		}
+	} else { // horizontal
+		if g := o.Glyphs[L-1]; g.Width == 0 {
+			return o.Advance - g.XAdvance
+		}
+	}
+
+	return o.Advance
+}
+
 // RecalculateAll updates the all other fields of the Output
 // to match the current contents of the Glyphs field.
 // This method will fail with UnimplementedDirectionError if the Output

+ 53 - 25
vendor/github.com/go-text/typesetting/shaping/wrapping.go

@@ -816,6 +816,55 @@ type lineConfig struct {
 	truncatedMaxWidth int
 }
 
+func (l *LineWrapper) postProcessLine(finalLine Line, done bool) (Line, int, bool) {
+	if len(finalLine) > 0 {
+		finalRun := finalLine[len(finalLine)-1]
+
+		// zero trailing whitespace advance,
+		// to be coherent with Output.advanceSpaceAware
+		if L := len(finalRun.Glyphs); L != 0 {
+			if finalRun.Direction.IsVertical() {
+				if g := &finalRun.Glyphs[L-1]; g.Height == 0 {
+					g.YAdvance = 0
+				}
+			} else { // horizontal
+				if g := finalRun.Glyphs[L-1]; g.Width == 0 {
+					g.XAdvance = 0
+				}
+			}
+			finalRun.RecomputeAdvance()
+		}
+
+		// Update the start position of the next line.
+		l.lineStartRune = finalRun.Runes.Count + finalRun.Runes.Offset
+	}
+
+	// Check whether we've exhausted the text.
+	done = done || l.lineStartRune >= l.breaker.totalRunes
+
+	// Implement truncation if needed.
+	truncated := 0
+	if l.truncating {
+		l.config.TruncateAfterLines--
+		insertTruncator := false
+		if l.config.TruncateAfterLines == 0 {
+			done = true
+			truncated = l.breaker.totalRunes - l.lineStartRune
+			insertTruncator = truncated > 0 || l.config.TextContinues
+		}
+		if insertTruncator {
+			finalLine = append(finalLine, l.config.Truncator)
+		}
+	}
+
+	// Mark the paragraph as complete if needed.
+	if done {
+		l.more = false
+	}
+
+	return finalLine, truncated, done
+}
+
 // WrapNextLine wraps the shaped glyphs of a paragraph to a particular max width.
 // It is meant to be called iteratively to wrap each line, allowing lines to
 // be wrapped to different widths within the same paragraph. When done is true,
@@ -828,32 +877,11 @@ func (l *LineWrapper) WrapNextLine(maxWidth int) (finalLine Line, truncated int,
 	if !l.more {
 		return nil, 0, true
 	}
+
 	defer func() {
-		// Update the start position of the next line.
-		if len(finalLine) > 0 {
-			finalRun := finalLine[len(finalLine)-1]
-			l.lineStartRune = finalRun.Runes.Count + finalRun.Runes.Offset
-		}
-		// Check whether we've exhausted the text.
-		done = done || l.lineStartRune >= l.breaker.totalRunes
-		// Implement truncation if needed.
-		if l.truncating {
-			l.config.TruncateAfterLines--
-			insertTruncator := false
-			if l.config.TruncateAfterLines == 0 {
-				done = true
-				truncated = l.breaker.totalRunes - l.lineStartRune
-				insertTruncator = truncated > 0 || l.config.TextContinues
-			}
-			if insertTruncator {
-				finalLine = append(finalLine, l.config.Truncator)
-			}
-		}
-		// Mark the paragraph as complete if needed.
-		if done {
-			l.more = false
-		}
+		finalLine, truncated, done = l.postProcessLine(finalLine, done)
 	}()
+
 	// If the iterator is empty, return early.
 	_, firstRun, hasFirst := l.glyphRuns.Peek()
 	if !hasFirst {
@@ -1050,7 +1078,7 @@ func (l *LineWrapper) processBreakOption(option breakOption, config lineConfig)
 		return breakInvalid, Output{}
 	}
 	candidateRun := cutRun(run, l.mapper.mapping, l.lineStartRune, option.breakAtRune)
-	candidateLineWidth := (candidateRun.Advance + l.scratch.candidateAdvance()).Ceil()
+	candidateLineWidth := (candidateRun.advanceSpaceAware() + l.scratch.candidateAdvance()).Ceil()
 	if candidateLineWidth > config.maxWidth {
 		// The run doesn't fit on the line.
 		if !l.scratch.hasBest() {

+ 2 - 2
vendor/github.com/gofiber/fiber/v2/app.go

@@ -30,7 +30,7 @@ import (
 )
 
 // Version of current fiber package
-const Version = "2.49.2"
+const Version = "2.50.0"
 
 // Handler defines a function to serve HTTP requests.
 type Handler = func(*Ctx) error
@@ -620,7 +620,7 @@ func (app *App) Name(name string) Router {
 
 	for _, routes := range app.stack {
 		for _, route := range routes {
-			if route.Path == app.latestRoute.path {
+			if route.Path == app.latestRoute.Path {
 				route.Name = name
 
 				if route.group != nil {

+ 78 - 29
vendor/github.com/gofiber/fiber/v2/ctx.go

@@ -8,7 +8,6 @@ import (
 	"bytes"
 	"context"
 	"crypto/tls"
-	"encoding/json"
 	"encoding/xml"
 	"errors"
 	"fmt"
@@ -39,22 +38,23 @@ const (
 // maxParams defines the maximum number of parameters per route.
 const maxParams = 30
 
-// Some constants for BodyParser, QueryParser and ReqHeaderParser.
+// Some constants for BodyParser, QueryParser, CookieParser and ReqHeaderParser.
 const (
 	queryTag     = "query"
 	reqHeaderTag = "reqHeader"
 	bodyTag      = "form"
 	paramsTag    = "params"
+	cookieTag    = "cookie"
 )
 
 // userContextKey define the key name for storing context.Context in *fasthttp.RequestCtx
 const userContextKey = "__local_user_context__"
 
 var (
-	// decoderPoolMap helps to improve BodyParser's, QueryParser's and ReqHeaderParser's performance
+	// decoderPoolMap helps to improve BodyParser's, QueryParser's, CookieParser's and ReqHeaderParser's performance
 	decoderPoolMap = map[string]*sync.Pool{}
 	// tags is used to classify parser's pool
-	tags = []string{queryTag, bodyTag, reqHeaderTag, paramsTag}
+	tags = []string{queryTag, bodyTag, reqHeaderTag, paramsTag, cookieTag}
 )
 
 func init() {
@@ -104,8 +104,9 @@ type TLSHandler struct {
 	clientHelloInfo *tls.ClientHelloInfo
 }
 
-// GetClientInfo Callback function to set CHI
-// TODO: Why is this a getter which sets stuff?
+// GetClientInfo Callback function to set ClientHelloInfo
+// Must comply with the method structure of https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/tls/common.go;l=554-563
+// Since we overlay the method of the tls config in the listener method
 func (t *TLSHandler) GetClientInfo(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
 	t.clientHelloInfo = info
 	return nil, nil //nolint:nilnil // Not returning anything useful here is probably fine
@@ -396,7 +397,7 @@ func (c *Ctx) BodyParser(out interface{}) error {
 				k, err = parseParamSquareBrackets(k)
 			}
 
-			if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
+			if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, bodyTag) {
 				values := strings.Split(v, ",")
 				for i := 0; i < len(values); i++ {
 					data[k] = append(data[k], values[i])
@@ -503,6 +504,40 @@ func (c *Ctx) Cookies(key string, defaultValue ...string) string {
 	return defaultString(c.app.getString(c.fasthttp.Request.Header.Cookie(key)), defaultValue)
 }
 
+// CookieParser is used to bind cookies to a struct
+func (c *Ctx) CookieParser(out interface{}) error {
+	data := make(map[string][]string)
+	var err error
+
+	// loop through all cookies
+	c.fasthttp.Request.Header.VisitAllCookie(func(key, val []byte) {
+		if err != nil {
+			return
+		}
+
+		k := c.app.getString(key)
+		v := c.app.getString(val)
+
+		if strings.Contains(k, "[") {
+			k, err = parseParamSquareBrackets(k)
+		}
+
+		if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, cookieTag) {
+			values := strings.Split(v, ",")
+			for i := 0; i < len(values); i++ {
+				data[k] = append(data[k], values[i])
+			}
+		} else {
+			data[k] = append(data[k], v)
+		}
+	})
+	if err != nil {
+		return err
+	}
+
+	return c.parseToStruct(cookieTag, out, data)
+}
+
 // Download transfers the file from path as an attachment.
 // Typically, browsers will prompt the user for download.
 // By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog).
@@ -651,10 +686,11 @@ func (c *Ctx) GetRespHeader(key string, defaultValue ...string) string {
 // GetReqHeaders returns the HTTP request headers.
 // Returned value is only valid within the handler. Do not store any references.
 // Make copies or use the Immutable setting instead.
-func (c *Ctx) GetReqHeaders() map[string]string {
-	headers := make(map[string]string)
+func (c *Ctx) GetReqHeaders() map[string][]string {
+	headers := make(map[string][]string)
 	c.Request().Header.VisitAll(func(k, v []byte) {
-		headers[c.app.getString(k)] = c.app.getString(v)
+		key := c.app.getString(k)
+		headers[key] = append(headers[key], c.app.getString(v))
 	})
 
 	return headers
@@ -663,10 +699,11 @@ func (c *Ctx) GetReqHeaders() map[string]string {
 // GetRespHeaders returns the HTTP response headers.
 // Returned value is only valid within the handler. Do not store any references.
 // Make copies or use the Immutable setting instead.
-func (c *Ctx) GetRespHeaders() map[string]string {
-	headers := make(map[string]string)
+func (c *Ctx) GetRespHeaders() map[string][]string {
+	headers := make(map[string][]string)
 	c.Response().Header.VisitAll(func(k, v []byte) {
-		headers[c.app.getString(k)] = c.app.getString(v)
+		key := c.app.getString(k)
+		headers[key] = append(headers[key], c.app.getString(v))
 	})
 
 	return headers
@@ -749,7 +786,7 @@ iploop:
 			j++
 		}
 
-		for i < j && headerValue[i] == ' ' {
+		for i < j && (headerValue[i] == ' ' || headerValue[i] == ',') {
 			i++
 		}
 
@@ -861,9 +898,9 @@ func (c *Ctx) JSON(data interface{}) error {
 // This method is identical to JSON, except that it opts-in to JSONP callback support.
 // By default, the callback name is simply callback.
 func (c *Ctx) JSONP(data interface{}, callback ...string) error {
-	raw, err := json.Marshal(data)
+	raw, err := c.app.config.JSONEncoder(data)
 	if err != nil {
-		return fmt.Errorf("failed to marshal: %w", err)
+		return err
 	}
 
 	var result, cb string
@@ -927,17 +964,24 @@ func (c *Ctx) Location(path string) {
 	c.setCanonical(HeaderLocation, path)
 }
 
-// Method contains a string corresponding to the HTTP method of the request: GET, POST, PUT and so on.
+// Method returns the HTTP request method for the context, optionally overridden by the provided argument.
+// If no override is given or if the provided override is not a valid HTTP method, it returns the current method from the context.
+// Otherwise, it updates the context's method and returns the overridden method as a string.
 func (c *Ctx) Method(override ...string) string {
-	if len(override) > 0 {
-		method := utils.ToUpper(override[0])
-		mINT := c.app.methodInt(method)
-		if mINT == -1 {
-			return c.method
-		}
-		c.method = method
-		c.methodINT = mINT
+	if len(override) == 0 {
+		// Nothing to override, just return current method from context
+		return c.method
 	}
+
+	method := utils.ToUpper(override[0])
+	mINT := c.app.methodInt(method)
+	if mINT == -1 {
+		// Provided override does not valid HTTP method, no override, return current method
+		return c.method
+	}
+
+	c.method = method
+	c.methodINT = mINT
 	return c.method
 }
 
@@ -1220,7 +1264,7 @@ func (c *Ctx) QueryParser(out interface{}) error {
 			k, err = parseParamSquareBrackets(k)
 		}
 
-		if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
+		if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, queryTag) {
 			values := strings.Split(v, ",")
 			for i := 0; i < len(values); i++ {
 				data[k] = append(data[k], values[i])
@@ -1269,7 +1313,7 @@ func (c *Ctx) ReqHeaderParser(out interface{}) error {
 		k := c.app.getString(key)
 		v := c.app.getString(val)
 
-		if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
+		if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, reqHeaderTag) {
 			values := strings.Split(v, ",")
 			for i := 0; i < len(values); i++ {
 				data[k] = append(data[k], values[i])
@@ -1300,7 +1344,7 @@ func (*Ctx) parseToStruct(aliasTag string, out interface{}, data map[string][]st
 	return nil
 }
 
-func equalFieldType(out interface{}, kind reflect.Kind, key string) bool {
+func equalFieldType(out interface{}, kind reflect.Kind, key, tag string) bool {
 	// Get type of interface
 	outTyp := reflect.TypeOf(out).Elem()
 	key = utils.ToLower(key)
@@ -1327,7 +1371,7 @@ func equalFieldType(out interface{}, kind reflect.Kind, key string) bool {
 			continue
 		}
 		// Get tag from field if exist
-		inputFieldName := typeField.Tag.Get(queryTag)
+		inputFieldName := typeField.Tag.Get(tag)
 		if inputFieldName == "" {
 			inputFieldName = typeField.Name
 		} else {
@@ -1496,6 +1540,11 @@ func (c *Ctx) Render(name string, bind interface{}, layouts ...string) error {
 	buf := bytebufferpool.Get()
 	defer bytebufferpool.Put(buf)
 
+	// Initialize empty bind map if bind is nil
+	if bind == nil {
+		bind = make(Map)
+	}
+
 	// Pass-locals-to-views, bind, appListKeys
 	c.renderExtensions(bind)
 

+ 1 - 1
vendor/github.com/gofiber/fiber/v2/helpers.go

@@ -625,7 +625,7 @@ const (
 	MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
 )
 
-// HTTP status codes were copied from https://github.com/nginx/nginx/blob/67d2a9541826ecd5db97d604f23460210fd3e517/conf/mime.types with the following updates:
+// HTTP status codes were copied from net/http with the following updates:
 // - Rename StatusNonAuthoritativeInfo to StatusNonAuthoritativeInformation
 // - Add StatusSwitchProxy (306)
 // NOTE: Keep this list in sync with statusMessage

+ 2 - 1
vendor/github.com/gofiber/fiber/v2/listen.go

@@ -19,10 +19,11 @@ import (
 	"strings"
 	"text/tabwriter"
 
-	"github.com/gofiber/fiber/v2/log"
 	"github.com/mattn/go-colorable"
 	"github.com/mattn/go-isatty"
 	"github.com/mattn/go-runewidth"
+
+	"github.com/gofiber/fiber/v2/log"
 )
 
 const (

+ 5 - 1
vendor/github.com/gofiber/fiber/v2/log/default.go

@@ -188,7 +188,11 @@ func (l *defaultLogger) Panicw(msg string, keysAndValues ...interface{}) {
 }
 
 func (l *defaultLogger) WithContext(_ context.Context) CommonLogger {
-	return l
+	return &defaultLogger{
+		stdlog: l.stdlog,
+		level:  l.level,
+		depth:  l.depth - 1,
+	}
 }
 
 func (l *defaultLogger) SetLevel(level Level) {

+ 65 - 0
vendor/github.com/gofiber/fiber/v2/middleware/compress/compress.go

@@ -0,0 +1,65 @@
+package compress
+
+import (
+	"github.com/gofiber/fiber/v2"
+
+	"github.com/valyala/fasthttp"
+)
+
+// New creates a new middleware handler
+func New(config ...Config) fiber.Handler {
+	// Set default config
+	cfg := configDefault(config...)
+
+	// Setup request handlers
+	var (
+		fctx       = func(c *fasthttp.RequestCtx) {}
+		compressor fasthttp.RequestHandler
+	)
+
+	// Setup compression algorithm
+	switch cfg.Level {
+	case LevelDefault:
+		// LevelDefault
+		compressor = fasthttp.CompressHandlerBrotliLevel(fctx,
+			fasthttp.CompressBrotliDefaultCompression,
+			fasthttp.CompressDefaultCompression,
+		)
+	case LevelBestSpeed:
+		// LevelBestSpeed
+		compressor = fasthttp.CompressHandlerBrotliLevel(fctx,
+			fasthttp.CompressBrotliBestSpeed,
+			fasthttp.CompressBestSpeed,
+		)
+	case LevelBestCompression:
+		// LevelBestCompression
+		compressor = fasthttp.CompressHandlerBrotliLevel(fctx,
+			fasthttp.CompressBrotliBestCompression,
+			fasthttp.CompressBestCompression,
+		)
+	default:
+		// LevelDisabled
+		return func(c *fiber.Ctx) error {
+			return c.Next()
+		}
+	}
+
+	// Return new handler
+	return func(c *fiber.Ctx) error {
+		// Don't execute middleware if Next returns true
+		if cfg.Next != nil && cfg.Next(c) {
+			return c.Next()
+		}
+
+		// Continue stack
+		if err := c.Next(); err != nil {
+			return err
+		}
+
+		// Compress response
+		compressor(c.Context())
+
+		// Return from handler
+		return nil
+	}
+}

+ 56 - 0
vendor/github.com/gofiber/fiber/v2/middleware/compress/config.go

@@ -0,0 +1,56 @@
+package compress
+
+import (
+	"github.com/gofiber/fiber/v2"
+)
+
+// Config defines the config for middleware.
+type Config struct {
+	// Next defines a function to skip this middleware when returned true.
+	//
+	// Optional. Default: nil
+	Next func(c *fiber.Ctx) bool
+
+	// Level determines the compression algorithm
+	//
+	// Optional. Default: LevelDefault
+	// LevelDisabled:         -1
+	// LevelDefault:          0
+	// LevelBestSpeed:        1
+	// LevelBestCompression:  2
+	Level Level
+}
+
+// Level is numeric representation of compression level
+type Level int
+
+// Represents compression level that will be used in the middleware
+const (
+	LevelDisabled        Level = -1
+	LevelDefault         Level = 0
+	LevelBestSpeed       Level = 1
+	LevelBestCompression Level = 2
+)
+
+// ConfigDefault is the default config
+var ConfigDefault = Config{
+	Next:  nil,
+	Level: LevelDefault,
+}
+
+// Helper function to set default values
+func configDefault(config ...Config) Config {
+	// Return default config if nothing provided
+	if len(config) < 1 {
+		return ConfigDefault
+	}
+
+	// Override default config
+	cfg := config[0]
+
+	// Set default values
+	if cfg.Level < LevelDisabled || cfg.Level > LevelBestCompression {
+		cfg.Level = ConfigDefault.Level
+	}
+	return cfg
+}

+ 2 - 1
vendor/github.com/gofiber/fiber/v2/path.go

@@ -13,8 +13,9 @@ import (
 	"time"
 	"unicode"
 
-	"github.com/gofiber/fiber/v2/utils"
 	"github.com/google/uuid"
+
+	"github.com/gofiber/fiber/v2/utils"
 )
 
 // routeParser holds the path segments and param names

+ 2 - 1
vendor/github.com/gofiber/fiber/v2/prefork.go

@@ -12,8 +12,9 @@ import (
 	"sync/atomic"
 	"time"
 
-	"github.com/gofiber/fiber/v2/log"
 	"github.com/valyala/fasthttp/reuseport"
+
+	"github.com/gofiber/fiber/v2/log"
 )
 
 const (

+ 1 - 1
vendor/github.com/gofiber/fiber/v2/utils/assertions.go

@@ -53,7 +53,7 @@ func AssertEqual(tb testing.TB, expected, actual interface{}, description ...str
 	_, _ = fmt.Fprintf(w, "\nExpect:\t%v\t(%s)", expected, aType)
 	_, _ = fmt.Fprintf(w, "\nResult:\t%v\t(%s)", actual, bType)
 
-	result := ""
+	var result string
 	if err := w.Flush(); err != nil {
 		result = err.Error()
 	} else {

+ 0 - 1
vendor/github.com/gofiber/fiber/v2/utils/convert_b2s_new.go

@@ -1,5 +1,4 @@
 //go:build go1.20
-// +build go1.20
 
 package utils
 

+ 0 - 1
vendor/github.com/gofiber/fiber/v2/utils/convert_b2s_old.go

@@ -1,5 +1,4 @@
 //go:build !go1.20
-// +build !go1.20
 
 package utils
 

+ 0 - 1
vendor/github.com/gofiber/fiber/v2/utils/convert_s2b_new.go

@@ -1,5 +1,4 @@
 //go:build go1.20
-// +build go1.20
 
 package utils
 

+ 0 - 1
vendor/github.com/gofiber/fiber/v2/utils/convert_s2b_old.go

@@ -1,5 +1,4 @@
 //go:build !go1.20
-// +build !go1.20
 
 package utils
 

+ 33 - 0
vendor/github.com/gofiber/template/.gitignore

@@ -0,0 +1,33 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+*.tmp
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# IDE files
+.vscode
+.DS_Store
+.idea
+
+# Misc
+*.fiber.gz
+*.fasthttp.gz
+*.pprof
+*.workspace
+
+# Dependencies
+/vendor/
+vendor/
+vendor
+/Godeps/
+
+# test files
+*/views/ShouldReload.*

+ 284 - 0
vendor/github.com/gofiber/template/.golangci.yml

@@ -0,0 +1,284 @@
+# Created based on v1.52.2
+# NOTE: Keep this in sync with the version in .github/workflows/lint_golangci-lint.yml
+
+run:
+  timeout: 5m
+  modules-download-mode: readonly
+  skip-dirs-use-default: false
+  skip-dirs:
+    - internal
+
+output:
+  sort-results: true
+
+linters-settings:
+  depguard:
+    include-go-root: true
+    packages:
+      - flag
+      - io/ioutil
+      - reflect
+      - unsafe
+    packages-with-error-message:
+      - flag: '`flag` package is only allowed in main.go'
+      - io/ioutil: '`io/ioutil` package is deprecated, use the `io` and `os` package instead'
+      - reflect: '`reflect` package is dangerous to use'
+      - unsafe: '`unsafe` package is dangerous to use'
+
+  errcheck:
+    check-type-assertions: true
+    check-blank: true
+    disable-default-exclusions: true
+
+  errchkjson:
+    report-no-exported: true
+
+  exhaustive:
+    check-generated: true
+    default-signifies-exhaustive: true
+
+  forbidigo:
+    forbid:
+      - ^(fmt\.Print(|f|ln)|print|println)$
+      # - 'http\.Default(Client|Transport)'
+      # - 'time\.Sleep'
+      # - 'panic'
+
+  gci:
+    sections:
+      - standard
+      - prefix(github.com/gofiber/fiber)
+      - default
+      - blank
+      - dot
+    custom-order: true
+
+  goconst:
+    numbers: true
+
+  gocritic:
+    enabled-tags:
+      - diagnostic
+      - style
+      - performance
+      - experimental
+      - opinionated
+    disabled-checks:
+      - hugeParam
+      - rangeValCopy
+
+  gofumpt:
+    module-path: github.com/gofiber/template
+    extra-rules: true
+
+  gosec:
+    config:
+      global:
+        audit: true
+
+  govet:
+    enable-all: true
+    disable:
+      - fieldalignment
+      - shadow
+
+  grouper:
+    import-require-single-import: true
+    import-require-grouping: true
+
+  misspell:
+    locale: US
+
+  nolintlint:
+    require-explanation: true
+    require-specific: true
+
+  nonamedreturns:
+    report-error-in-defer: true
+
+  predeclared:
+    q: true
+
+  promlinter:
+    strict: true
+
+  reassign:
+    patterns:
+      - '.*'
+
+  revive:
+    enable-all-rules: true
+    rules:
+      # Provided by gomnd linter
+      - name: add-constant
+        disabled: true
+      - name: argument-limit
+        disabled: true
+      # Provided by bidichk
+      - name: banned-characters
+        disabled: true
+      - name: cognitive-complexity
+        disabled: true
+      - name: comment-spacings
+        arguments:
+          - nolint
+          - msgp
+      - name: cyclomatic
+        disabled: true
+      - name: exported
+        disabled: true
+      - name: file-header
+        disabled: true
+      - name: function-result-limit
+        arguments: [3]
+      - name: function-length
+        disabled: true
+      - name: line-length-limit
+        disabled: true
+      - name: nested-structs
+        disabled: true
+      - name: max-public-structs
+        disabled: true
+      - name: modifies-parameter
+        disabled: true
+      - name: package-comments
+        disabled: true
+      - name: use-any
+        disabled: true # some tests still use go 1.17
+
+  stylecheck:
+    checks:
+      - all
+      - -ST1000
+      - -ST1020
+      - -ST1021
+      - -ST1022
+
+  tagliatelle:
+    case:
+      rules:
+        json: snake
+
+  tenv:
+    all: true
+
+  #unparam:
+  #  check-exported: true
+
+  wrapcheck:
+    ignorePackageGlobs:
+      - github.com/gofiber/fiber/*
+      - github.com/valyala/fasthttp
+
+issues:
+  exclude-use-default: false
+  exclude-rules:
+    - linters:
+      - goerr113
+      text: 'do not define dynamic errors, use wrapped static errors instead*'
+
+linters:
+  enable:
+    - asasalint
+    - asciicheck
+    - bidichk
+    - bodyclose
+    - containedctx
+    - contextcheck
+    # - cyclop
+    - deadcode
+    # - decorder
+    - depguard
+    - dogsled
+    # - dupl
+    - dupword
+    - durationcheck
+    - errcheck
+    - errchkjson
+    - errname
+    - errorlint
+    - execinquery
+    - exhaustive
+    # - exhaustivestruct
+    # - exhaustruct
+    - exportloopref
+    - forbidigo
+    - forcetypeassert
+    # - funlen
+    - gci
+    - ginkgolinter
+    - gocheckcompilerdirectives
+    - gochecknoglobals # Enabled
+    - gochecknoinits # Enabled
+    # - gocognit
+    - goconst
+    - gocritic
+    # - gocyclo
+    # - godot
+    # - godox
+    - goerr113
+    - gofmt
+    - gofumpt
+    # - goheader
+    # - goimports
+    # - golint
+    - gomnd # Enabled
+    - gomoddirectives
+    # - gomodguard
+    - goprintffuncname
+    - gosec
+    - gosimple
+    - govet
+    - grouper
+    # - ifshort
+    # - importas
+    - ineffassign
+    # - interfacebloat
+    # - interfacer
+    # - ireturn
+    # - lll
+    - loggercheck
+    # - maintidx
+    # - makezero
+    # - maligned
+    - misspell
+    - musttag
+    - nakedret
+    # - nestif
+    - nilerr
+    - nilnil
+    # - nlreturn
+    - noctx
+    - nolintlint
+    - nonamedreturns
+    - nosnakecase
+    - nosprintfhostport
+    - paralleltest
+    # - prealloc
+    - predeclared
+    - promlinter
+    - reassign
+    - revive
+    - rowserrcheck
+    - scopelint
+    - sqlclosecheck
+    - staticcheck
+    - structcheck
+    - stylecheck
+    - tagliatelle
+    - tenv
+    - testableexamples
+    # - testpackage
+    - thelper
+    - tparallel
+    - typecheck
+    - unconvert
+    - unparam
+    - unused
+    - usestdlibvars
+    - varcheck
+    # - varnamelen
+    - wastedassign
+    - whitespace
+    # - wrapcheck # disabled
+    # - wsl

+ 21 - 0
vendor/github.com/gofiber/template/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Fiber
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 250 - 0
vendor/github.com/gofiber/template/README.md

@@ -0,0 +1,250 @@
+<p align="center">
+  <a href="https://gofiber.io">
+      <picture>
+        <source height="125" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/gofiber/template/master/.github/logo-dark.svg">
+        <img height="125" alt="Fiber" src="https://raw.githubusercontent.com/gofiber/template/master/.github/logo.svg">
+      </picture>
+  </a>
+  <br>
+
+  <a href="https://pkg.go.dev/github.com/gofiber/template/html?tab=doc">
+    <img src="https://img.shields.io/badge/%F0%9F%93%9A%20godoc-pkg-00ACD7.svg?color=00ACD7&style=flat">
+  </a>
+  <a href="https://goreportcard.com/report/github.com/gofiber/template">
+    <img src="https://img.shields.io/badge/%F0%9F%93%9D%20goreport-A%2B-75C46B">
+  </a>
+  <a href="https://gofiber.io/discord">
+    <img src="https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7">
+  </a>
+</p>
+
+This package provides universal methods to use multiple template engines with the [Fiber web framework](https://github.com/gofiber/fiber) using the new [Views](https://godoc.org/github.com/gofiber/fiber#Views) interface that is available from `> v1.11.1`. Special thanks to @bdtomlin & @arsmn for helping!
+
+9 template engines are supported:
+- [html](https://github.com/gofiber/template/tree/master/html) <a href="https://github.com/gofiber/template/actions?query=workflow%3A%22Tests+Html%22">
+  <img src="https://img.shields.io/github/actions/workflow/status/gofiber/template/test-html.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B">
+  </a>
+- [ace](https://github.com/gofiber/template/tree/master/ace) <a href="https://github.com/gofiber/template/actions?query=workflow%3A%22Tests+Ace%22">
+  <img src="https://img.shields.io/github/actions/workflow/status/gofiber/template/test-ace.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B"></a>
+- [amber](https://github.com/gofiber/template/tree/master/amber) <a href="https://github.com/gofiber/template/actions?query=workflow%3A%22Tests+Amber%22">
+  <img src="https://img.shields.io/github/actions/workflow/status/gofiber/template/test-amber.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B"></a>
+- [django](https://github.com/gofiber/template/tree/master/django) <a href="https://github.com/gofiber/template/actions?query=workflow%3A%22Tests+Django%22">
+  <img src="https://img.shields.io/github/actions/workflow/status/gofiber/template/test-django.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B"></a>
+- [handlebars](https://github.com/gofiber/template/tree/master/handlebars) <a href="https://github.com/gofiber/template/actions?query=workflow%3A%22Tests+Handlebars%22">
+  <img src="https://img.shields.io/github/actions/workflow/status/gofiber/template/test-handlebars.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B"></a>
+- [jet](https://github.com/gofiber/template/tree/master/jet) <a href="https://github.com/gofiber/template/actions?query=workflow%3A%22Tests+Jet%22">
+  <img src="https://img.shields.io/github/actions/workflow/status/gofiber/template/test-jet.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B"></a>
+- [mustache](https://github.com/gofiber/template/tree/master/mustache) <a href="https://github.com/gofiber/template/actions?query=workflow%3A%22Tests+Mustache%22">
+  <img src="https://img.shields.io/github/actions/workflow/status/gofiber/template/test-mustache.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B"></a>
+- [pug](https://github.com/gofiber/template/tree/master/pug) <a href="https://github.com/gofiber/template/actions?query=workflow%3A%22Tests+Pug%22">
+  <img src="https://img.shields.io/github/actions/workflow/status/gofiber/template/test-pug.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B"></a>
+- [slim](https://github.com/gofiber/template/tree/master/slim) <a href="https://github.com/gofiber/template/actions?query=workflow%3A%22Tests+Slim%22">
+  <img src="https://img.shields.io/github/actions/workflow/status/gofiber/template/test-slim.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B"></a>
+
+### Installation
+> Go version `1.17` or higher is required.
+
+```
+go get -u github.com/gofiber/fiber/v2
+go get -u github.com/gofiber/template/any_template_engine/vX
+```
+
+### Example
+```go
+package main
+
+import (
+	"log"
+
+	"github.com/gofiber/fiber/v2"
+
+	// To use a specific template engine, import as shown below:
+	// "github.com/gofiber/template/pug"
+	// "github.com/gofiber/template/mustache"
+	// etc..
+
+	// In this example we use the html template engine
+	"github.com/gofiber/template/html/v2"
+)
+
+func main() {
+	// Create a new engine by passing the template folder
+	// and template extension using <engine>.New(dir, ext string)
+	engine := html.New("./views", ".html")
+
+  	// We also support the http.FileSystem interface
+	// See examples below to load templates from embedded files
+	engine := html.NewFileSystem(http.Dir("./views"), ".html")
+
+	// Reload the templates on each render, good for development
+	engine.Reload(true) // Optional. Default: false
+
+	// Debug will print each template that is parsed, good for debugging
+	engine.Debug(true) // Optional. Default: false
+
+	// Layout defines the variable name that is used to yield templates within layouts
+	engine.Layout("embed") // Optional. Default: "embed"
+
+	// Delims sets the action delimiters to the specified strings
+	engine.Delims("{{", "}}") // Optional. Default: engine delimiters
+
+	// AddFunc adds a function to the template's global function map.
+	engine.AddFunc("greet", func(name string) string {
+		return "Hello, " + name + "!"
+	})
+
+	// After you created your engine, you can pass it to Fiber's Views Engine
+	app := fiber.New(fiber.Config{
+		Views: engine,
+	})
+
+	// To render a template, you can call the ctx.Render function
+	// Render(tmpl string, values interface{}, layout ...string)
+	app.Get("/", func(c *fiber.Ctx) error {
+		return c.Render("index", fiber.Map{
+			"Title": "Hello, World!",
+		})
+	})
+
+	// Render with layout example
+	app.Get("/layout", func(c *fiber.Ctx) error {
+		return c.Render("index", fiber.Map{
+			"Title": "Hello, World!",
+		}, "layouts/main")
+	})
+
+	log.Fatal(app.Listen(":3000"))
+}
+
+```
+
+### More Examples
+
+To view more specific examples, you could visit each engine folder to learn more
+- [html](https://github.com/gofiber/template/tree/master/html)
+- [ace](https://github.com/gofiber/template/tree/master/ace)
+- [amber](https://github.com/gofiber/template/tree/master/amber)
+- [django](https://github.com/gofiber/template/tree/master/django)
+- [handlebars](https://github.com/gofiber/template/tree/master/handlebars)
+- [jet](https://github.com/gofiber/template/tree/master/jet)
+- [mustache](https://github.com/gofiber/template/tree/master/mustache)
+- [pug](https://github.com/gofiber/template/tree/master/pug)
+- [slim](https://github.com/gofiber/template/tree/master/slim)
+
+
+### embedded Systems
+
+We support the `http.FileSystem` interface, so you can use different libraries to load the templates from embedded binaries.
+
+#### pkger
+Read documentation: https://github.com/markbates/pkger
+
+```go
+package main
+
+import (
+	"log"
+
+	"github.com/gofiber/fiber/v2"
+	"github.com/gofiber/template/html"
+
+	"github.com/markbates/pkger"
+)
+
+func main() {
+	engine := html.NewFileSystem(pkger.Dir("/views"), ".html")
+
+	app := fiber.New(fiber.Config{
+		Views: engine,
+	})
+
+	// run pkger && go build
+}
+```
+#### packr
+Read documentation: https://github.com/gobuffalo/packr
+
+```go
+package main
+
+import (
+	"log"
+
+	"github.com/gofiber/fiber/v2"
+	"github.com/gofiber/template/html"
+
+	"github.com/gobuffalo/packr/v2"
+)
+
+func main() {
+	engine := html.NewFileSystem(packr.New("Templates", "/views"), ".html")
+
+	app := fiber.New(fiber.Config{
+		Views: engine,
+	})
+
+	// run packr && go build
+}
+```
+#### go.rice
+Read documentation: https://github.com/GeertJohan/go.rice
+
+```go
+package main
+
+import (
+	"log"
+
+	"github.com/gofiber/fiber/v2"
+	"github.com/gofiber/template/html"
+
+	"github.com/GeertJohan/go.rice"
+)
+
+func main() {
+	engine := html.NewFileSystem(rice.MustFindBox("views").HTTPBox(), ".html")
+
+	app := fiber.New(fiber.Config{
+		Views: engine,
+	})
+
+	// run rice embed-go && go build
+}
+
+```
+#### fileb0x
+Read documentation: https://github.com/UnnoTed/fileb0x
+
+```go
+package main
+
+import (
+	"log"
+
+	"github.com/gofiber/fiber/v2"
+	"github.com/gofiber/template/html"
+	// your generated package
+	"github.com/<user>/<repo>/static"
+)
+
+func main() {
+	engine := html.NewFileSystem(static.HTTP, ".html")
+
+	app := fiber.New(fiber.Config{
+		Views: engine,
+	})
+
+	// Read the documentation on how to use fileb0x
+}
+```
+
+
+### Benchmarks
+
+#### Simple
+![](.github/data/Simple-TimeperOperation.png)
+
+#### Extended
+![](.github/data/Extended-TimeperOperation.png)
+
+Benchmarks were ran on Apple Macbook M1. Each engine was benchmarked 20 times and the results averaged into a single xlsx file. Mustache was excluded from the extended benchmark

+ 21 - 0
vendor/github.com/gofiber/template/html/v2/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Fiber
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 203 - 0
vendor/github.com/gofiber/template/html/v2/README.md

@@ -0,0 +1,203 @@
+---
+id: html
+title: HTML
+---
+
+![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=django*)
+[![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord)
+![Test](https://github.com/gofiber/template/workflows/Tests/badge.svg)
+![Security](https://github.com/gofiber/template/workflows/Security/badge.svg)
+![Linter](https://github.com/gofiber/template/workflows/Linter/badge.svg)
+
+HTML is the official Go template engine [html/template](https://golang.org/pkg/html/template/), to see the original syntax documentation please [click here](TEMPLATES_CHEATSHEET.md)
+
+**Info:**
+
+All templates within the specified view directory are analyzed and compiled at the beginning to increase the performance when using them.
+Thus it should be noted that no `definition` with the same name should exist, otherwise they will overwrite each other.
+For templating the `{{embed}}` tag should be used
+
+### Basic Example
+
+_**./views/index.html**_
+
+```html
+{{template "partials/header" .}}
+
+<h1>{{.Title}}</h1>
+
+{{template "partials/footer" .}}
+```
+
+_**./views/partials/header.html**_
+
+```html
+<h2>Header</h2>
+```
+
+_**./views/partials/footer.html**_
+
+```html
+<h2>Footer</h2>
+```
+
+_**./views/layouts/main.html**_
+
+```html
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Main</title>
+  </head>
+
+  <body>
+    {{embed}}
+  </body>
+</html>
+```
+
+```go
+package main
+
+import (
+	"log"
+
+	"github.com/gofiber/fiber/v2"
+	"github.com/gofiber/template/html/v2"
+)
+
+func main() {
+	// Create a new engine
+	engine := html.New("./views", ".html")
+
+	// Or from an embedded system
+	// See github.com/gofiber/embed for examples
+	// engine := html.NewFileSystem(http.Dir("./views", ".html"))
+
+	// Pass the engine to the Views
+	app := fiber.New(fiber.Config{
+		Views: engine,
+	})
+
+	app.Get("/", func(c *fiber.Ctx) error {
+		// Render index
+		return c.Render("index", fiber.Map{
+			"Title": "Hello, World!",
+		})
+	})
+
+	app.Get("/layout", func(c *fiber.Ctx) error {
+		// Render index within layouts/main
+		return c.Render("index", fiber.Map{
+			"Title": "Hello, World!",
+		}, "layouts/main")
+	})
+
+	log.Fatal(app.Listen(":3000"))
+}
+
+```
+
+### Example with embed.FS
+
+```go
+package main
+
+import (
+    "log"
+    "net/http"
+    "embed"
+
+    "github.com/gofiber/fiber/v2"
+    "github.com/gofiber/template/html"
+)
+
+//go:embed views/*
+var viewsfs embed.FS
+
+func main() {
+    engine := html.NewFileSystem(http.FS(viewsfs), ".html")
+
+    // Pass the engine to the Views
+    app := fiber.New(fiber.Config{
+        Views: engine,
+    })
+
+
+    app.Get("/", func(c *fiber.Ctx) error {
+        // Render index - start with views directory
+        return c.Render("views/index", fiber.Map{
+            "Title": "Hello, World!",
+        })
+    })
+
+    log.Fatal(app.Listen(":3000"))
+}
+```
+
+and change the starting point to the views directory
+
+_**./views/index.html**_
+
+```html
+{{template "views/partials/header" .}}
+
+<h1>{{.Title}}</h1>
+
+{{template "views/partials/footer" .}}
+```
+
+### Example with innerHTML
+
+```go
+package main
+
+import (
+    "embed"
+    "html/template"
+    "log"
+    "net/http"
+
+    "github.com/gofiber/fiber/v2"
+    "github.com/gofiber/template/html"
+)
+
+//go:embed views/*
+var viewsfs embed.FS
+
+func main() {
+    engine := html.NewFileSystem(http.FS(viewsfs), ".html")
+    engine.AddFunc(
+        // add unescape function
+        "unescape", func(s string) template.HTML {
+            return template.HTML(s)
+        },
+    )
+
+    // Pass the engine to the Views
+    app := fiber.New(fiber.Config{Views: engine})
+
+    app.Get("/", func(c *fiber.Ctx) error {
+        // Render index
+        return c.Render("views/index", fiber.Map{
+            "Title": "Hello, <b>World</b>!",
+        })
+    })
+
+    log.Fatal(app.Listen(":3000"))
+}
+```
+
+and change the starting point to the views directory
+
+_**./views/index.html**_
+
+```html
+<p>{{ unescape .Title}}</p>
+```
+
+**html output**
+
+```html
+<p>Hello, <b>World</b>!</p>
+```

+ 582 - 0
vendor/github.com/gofiber/template/html/v2/TEMPLATES_CHEATSHEET.md

@@ -0,0 +1,582 @@
+# Golang Templates Cheatsheet
+
+The Go standard library provides a set of packages to generate output. The [text/template](https://archive.is/o/2HksZ/https://golang.org/pkg/text/template/) package implements templates for generating text output, while the [html/template](https://archive.is/o/2HksZ/https://golang.org/pkg/html/template/) package implements templates for generating HTML output that is safe against certain attacks. Both packages use the same interface but the following examples of the core features are directed towards HTML applications.
+
+---
+
+## Table of Contents
+
+- [Parsing and Creating Templates](#parsing-and-creating-templates)
+- [Executing Templates](#executing-templates)
+- [Template Encoding and HTML](#template-encoding-and-html)
+- [Template Variables](#template-variables)
+- [Template Actions](#template-actions)
+- [Template Functions](#template-functions)
+- [Template Comparison Functions](#template-comparison-functions)
+- [Nested Templates and Layouts](#nested-templates-and-layouts)
+- [Templates Calling Functions](#templates-calling-functions)
+
+---
+
+## Parsing and Creating Templates
+
+#### Naming Templates
+
+There is no defined file extension for Go templates. One of the most popular is `.tmpl` supported by vim-go and [referenced in the text/template godocs](https://archive.is/o/2HksZ/golang.org/pkg/text/template/%23example_Template_helpers). The extension `.gohtml` supports syntax highlighting in both Atom and GoSublime editors. Finally analysis of large Go codebases finds that `.tpl` is often used by developers. While the extension is not important it is still good to be consistent within a project for clarity.
+
+---
+
+#### Creating a Template
+
+`tpl, err := template.Parse(filename)` will get the template at filename and store it in tpl. tpl can then be executed to show the template.
+
+---
+
+#### Parsing Multiple Templates
+
+`template.ParseFiles(filenames)` takes a list of filenames and stores all templates. `template.ParseGlob(pattern)` will find all templates matching the pattern and store the templates.
+
+---
+
+## Executing Templates
+
+#### Execute a Single Template
+
+Once a template has been parsed there are two options to execute them. A single template `tpl` can be executed using `tpl.Execute(io.Writer, data)`. The content of tpl will be written to the io.Writer. Data is an interface passed to the template that will be useable in the template.
+
+---
+
+#### Executing a Named Template
+
+`tpl.ExecuteTemplate(io.Writer, name, data)` works the same as execute but allows for a string name of the template the user wants to execute.
+
+---
+
+## Template Encoding and HTML
+
+#### Contextual Encoding
+
+Go’s html/template package does encoding based on the context of the code. As a result, html/template encodes any characters that need encoding to be rendered correctly.
+
+For example the < and > in `"<h1>A header!</h1>"` will be encoded as `&lt;h1&gt;A header!&lt;/h1&gt;` .
+
+Type `template.HTML` can be used to skip encoding by telling Go the string is safe. `template.HTML("<h1>A Safe header</h1>")` will then be `<h1>A Safe header</h1>` . Using this type with user input is dangerous and leaves the application vulnerable.
+
+The go `html/template` package is aware of attributes within the template and will encode values differently based on the attribute.
+
+Go templates can also be used with javascript. Structs and maps will be expanded into JSON objects and quotes will be added to strings for use in function parameters and as variable values.
+
+```go
+    // Go
+    type Cat struct {
+    	Name string
+    	Age int
+    }
+
+    kitten := Cat{"Sam", 12}
+```
+
+```html
+// Template
+<script>
+  var cat = {{.kitten}}
+</script>
+```
+
+```js
+    // Javascript
+    var cat = {"Name":"Sam", "Age" 12}
+```
+
+---
+
+#### Safe Strings and HTML Comments
+
+The `html/template` package will remove any comments from a template by default. This can cause issues when comments are necessary such as detecting internet explorer.
+
+```html
+<!--[if IE]>
+  Place content here to target all Internet Explorer users.
+<![endif]-->
+```
+
+We can use the Custom Functions method (Globally) to create a function that returns html preserving comments. Define a function `htmlSafe` in the FuncMap of the template.
+
+```go
+    testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{
+    	"htmlSafe": func(html string) template.HTML {
+    		return template.HTML(html)
+        },
+    }).ParseFiles("hello.gohtml")
+```
+
+This function takes a string and produces the unaltered HTML code. This function can be used in a template like so to preserve the comments `<!--[if IE 6]>` and `<![endif]-->` :
+
+```go
+    {{htmlSafe "<!--[if IE 6]>" }}
+    <meta http-equiv="Content-Type" content="text/html; charset=Unicode">
+    {{ htmlSafe "<![endif]-->" }}
+```
+
+---
+
+## Template Variables
+
+#### The dot character (.)
+
+A template variable can be a boolean, string, character, integer, floating-point, imaginary, or complex constant in Go syntax. Data passed to the template can be accessed using dot `{{ . }}`.
+
+If the data is a complex type then it’s fields can be accessed using the dot with the field name `{{ .FieldName }}`.
+
+Dots can be chained together if the data contains multiple complex structures. `{{ .Struct.StructTwo.Field }}`
+
+---
+
+#### Variables in Templates
+
+Data passed to the template can be saved in a variable and used throughout the template. `{{$number := .}}` We use the `$number` to create a variable then initialize it with the value passed to the template. To use the variable we call it in the template with `{{$number}}`.
+
+```go
+    {{$number := .}}
+    <h1> It is day number {{$number}} of the month </h1>
+```
+
+```go
+    var tpl *template.Template
+
+    tpl = template.Must(template.ParseFiles("templateName"))
+
+    err := tpl.ExecuteTemplate(os.Stdout, "templateName", 23)
+```
+
+In this example we pass 23 to the template and stored in the `$number` variable which can be used anywhere in the template
+
+---
+
+## Template Actions
+
+#### If/Else Statements
+
+Go templates support if/else statements like many programming languages. We can use the if statement to check for values, if it doesn’t exist we can use an else value. The empty values are false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero.
+
+```html
+<h1>Hello, {{if .Name}} {{.Name}} {{else}} Anonymous {{end}}!</h1>
+```
+
+If .Name exists then `Hello, Name` will be printed (replaced with the name value) otherwise it will print `Hello, Anonymous`.
+
+Templates also provide the else if statment `{{else if .Name2 }}` which can be used to evaluate other options after an if.
+
+---
+
+#### Removing Whitespace
+
+Adding different values to a template can add various amounts of whitespace. We can either change our template to better handle it, by ignoring or minimizing effects, or we can use the minus sign `-` within out template.
+
+`<h1>Hello, {{if .Name}} {{.Name}} {{- else}} Anonymous {{- end}}!</h1>`
+
+Here we are telling the template to remove all spaces between the `Name` variable and whatever comes after it. We are doing the same with the end keyword. This allows us to have whitespace within the template for easier reading but remove it in production.
+
+---
+
+#### Range Blocks
+
+Go templates have a `range` keyword to iterate over all objects in a structure. Suppose we had the Go structures:
+
+```go
+    type Item struct {
+    	Name  string
+    	Price int
+    }
+
+    type ViewData struct {
+    	Name  string
+    	Items []Item
+    }
+```
+
+We have an Item, with a name and price, then a ViewData which is the structure sent to the template. Consider the template containing the following:
+
+```html
+{{range .Items}}
+<div class="item">
+  <h3 class="name">{{.Name}}</h3>
+  <span class="price">${{.Price}}</span>
+</div>
+{{end}}
+```
+
+For each Item in the range of Items (in the ViewData structure) get the Name and Price of that item and create html for each Item automatically. Within a range each Item becomes the `{{.}}` and the item properties therefore become `{{.Name}}` or `{{.Price}}` in this example.
+
+---
+
+## Template Functions
+
+The template package provides a list of predefined global functions. Below are some of the most used.
+
+---
+
+#### Indexing structures in Templates
+
+If the data passed to the template is a map, slice, or array it can be indexed from the template. We use `{{index x number}}` where index is the keyword, x is the data and number is a integer for the index value. If we had `{{index names 2}}` it is equivalent to `names[2]`. We can add more integers to index deeper into data. `{{index names 2 3 4}}` is equivalent to `names[2][3][4]`.
+
+```html
+<body>
+  <h1>{{index .FavNums 2 }}</h1>
+</body>
+```
+
+```go
+    type person struct {
+    	Name    string
+    	FavNums []int
+    }
+
+    func main() {
+
+    	tpl := template.Must(template.ParseGlob("*.gohtml"))
+    	tpl.Execute(os.Stdout, &person{"Curtis", []int{7, 11, 94}})
+    }
+```
+
+This code example passes a person structure and gets the 3rd favourite number from the FavNums slice.
+
+---
+
+#### The `and` Function
+
+The and function returns the boolean AND of its arguments by returning the first empty argument or the last argument. `and x y` behaves logically as `if x then y else x` . Consider the following go code
+
+```go
+    type User struct {
+      Admin bool
+    }
+
+    type ViewData struct {
+      *User
+    }
+```
+
+Pass a ViewData with a User that has Admin set true to the following template
+
+```go
+
+    {{if and .User .User.Admin}}
+      You are an admin user!
+    {{else}}
+      Access denied!
+    {{end}}
+```
+
+The result will be `You are an admin user!`. However if the ViewData did not include a \*User object or Admin was set as false then the result will be `Access denied!`.
+
+---
+
+#### The `or` Function
+
+The or function operates similarly to the and function however will stop at the first true. `or x y` is equivalent to `if x then x else y` so y will never be evaluated if x is not empty.
+
+---
+
+#### The `not` Function
+
+The not function returns the boolean negation of the argument.
+
+```go
+    {{ if not .Authenticated}}
+      Access Denied!
+    {{ end }}
+```
+
+---
+
+## Template Comparison Functions
+
+#### Comparisons
+
+The `html/template` package provides a variety of functions to do comparisons between operators. The operators may only be basic types or named basic types such as `type Temp float32` Remember that template functions take the form `{{ function arg1 arg2 }}`.
+
+- `eq` Returns the result of arg1 == arg2
+- `ne` Returns the result of arg1 != arg2
+- `lt` Returns the result of arg1 < arg2
+- `le` Returns the result of arg1 <= arg2
+- `gt` Returns the result of arg1 > arg2
+- `ge` Returns the result of arg1 >= arg2
+
+Of special note `eq` can be used with two or more arguments by comparing all arguments to the first. `{{ eq arg1 arg2 arg3 arg4}}` will result in the following logical expression:
+
+`arg1==arg2 || arg1==arg3 || arg1==arg4`
+
+---
+
+## Nested Templates and Layouts
+
+#### Nesting Templates
+
+Nested templates can be used for parts of code frequently used across templates, a footer or header for example. Rather than updating each template separately we can use a nested template that all other templates can use. You can define a template as follows:
+
+```go
+    {{define "footer"}}
+    <footer>
+    	<p>Here is the footer</p>
+    </footer>
+    {{end}}
+```
+
+A template named “footer” is defined which can be used in other templates like so to add the footer template content into the other template:
+
+```go
+    {{template "footer"}}
+```
+
+---
+
+#### Passing Variables between Templates
+
+The `template` action used to include nested templates also allows a second parameter to pass data to the nested template.
+
+```html
+// Define a nested template called header 
+{{define "header"}}
+<h1>{{.}}</h1>
+{{end}}
+
+// Call template and pass a name parameter 
+{{range .Items}}
+<div class="item">
+  {{template "header" .Name}}
+  <span class="price">${{.Price}}</span>
+</div>
+{{end}}
+```
+
+We use the same range to loop through Items as before but we pass the name to the header template each time in this simple example.
+
+---
+
+#### Creating Layouts
+
+Glob patterns specify sets of filenames with wildcard characters. The `template.ParseGlob(pattern string)` function will parse all templates that match the string pattern. `template.ParseFiles(files...)` can also be used with a list of file names.
+
+The templates are named by default based on the base names of the argument files. This mean `views/layouts/hello.gohtml` will have the name `hello.gohtml` . If the template has a ``{{define “templateName”}}` within it then that name will be usable.
+
+A specific template can be executed using `t.ExecuteTemplate(w, "templateName", nil)` . `t` is an object of type Template, `w` is type io.Writer such as an `http.ResponseWriter`, Then there is the name of the template to execute, and finally passing any data to the template, in this case a nil value.
+
+Example main.go file
+
+```go
+    // Omitted imports & package
+
+    var LayoutDir string = "views/layouts"
+    var bootstrap *template.Template
+
+    func main() {
+    	var err error
+    	bootstrap, err = template.ParseGlob(LayoutDir + "/*.gohtml")
+    	if err != nil {
+    		panic(err)
+    	}
+
+    	http.HandleFunc("/", handler)
+    	http.ListenAndServe(":8080", nil)
+    }
+
+    func handler(w http.ResponseWriter, r *http.Request) {
+    	bootstrap.ExecuteTemplate(w, "bootstrap", nil)
+    }
+```
+
+All `.gohtml` files are parsed in main. When route `/` is reached the template defined as `bootstrap` is executed using the handler function.
+
+Example views/layouts/bootstrap.gohtml file
+
+```html
+    {{define "bootstrap"}}
+    <!DOCTYPE html>
+    <html lang="en">
+      <head>
+        <title>Go Templates</title>
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
+    	rel="stylesheet">
+      </head>
+      <body>
+        <div class="container-fluid">
+          <h1>Filler header</h1>
+    	  <p>Filler paragraph</p>
+        </div>
+        <!-- jquery & Bootstrap JS -->
+        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"
+        </script>
+        <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js">
+        </script>
+      </body>
+    </html>
+    {{end}}
+```
+
+## Templates Calling Functions
+
+#### Function Variables (calling struct methods)
+
+We can use templates to call the methods of objects in the template to return data. Consider the User struct with the following method.
+
+```go
+    type User struct {
+      ID    int
+      Email string
+    }
+
+    func (u User) HasPermission(feature string) bool {
+      if feature == "feature-a" {
+        return true
+      } else {
+        return false
+      }
+    }
+```
+
+When a type User has been passed to the template we can then call this method from the template.
+
+```html
+{{if .User.HasPermission "feature-a"}}
+<div class="feature">
+  <h3>Feature A</h3>
+  <p>Some other stuff here...</p>
+</div>
+{{else}}
+<div class="feature disabled">
+  <h3>Feature A</h3>
+  <p>To enable Feature A please upgrade your plan</p>
+</div>
+{{end}}
+```
+
+The template checks if the User HasPermission for the feature and renders depending on the result.
+
+---
+
+#### Function Variables (call)
+
+If the Method HasPermission has to change at times then the Function Variables (Methods) implementation may not fit the design. Instead a `HasPermission func(string) bool` attribute can be added on the `User` type. This can then have a function assigned to it at creation.
+
+```go
+    // Structs
+    type ViewData struct {
+      User User
+    }
+
+    type User struct {
+      ID            int
+      Email         string
+      HasPermission func(string) bool
+    }
+
+    // Example of creating a ViewData
+    vd := ViewData{
+    		User: User{
+    			ID:    1,
+    			Email: "curtis.vermeeren@gmail.com",
+    			// Create the HasPermission function
+    			HasPermission: func(feature string) bool {
+    				if feature == "feature-b" {
+    					return true
+    				}
+    				return false
+    			},
+    		},
+    	}
+
+    // Executing the ViewData with the template
+    err := testTemplate.Execute(w, vd)
+```
+
+We need to tell the Go template that we want to call this function so we must change the template from the Function Variables (Methods) implementation to do this. We use the `call` keyword supplied by the go `html/template` package. Changing the previous template to use `call` results in:
+
+```html
+{{if (call .User.HasPermission "feature-b")}}
+<div class="feature">
+  <h3>Feature B</h3>
+  <p>Some other stuff here...</p>
+</div>
+{{else}}
+<div class="feature disabled">
+  <h3>Feature B</h3>
+  <p>To enable Feature B please upgrade your plan</p>
+</div>
+{{end}}
+```
+
+---
+
+#### Custom Functions
+
+Another way to call functions is to create custom functions with `template.FuncMap` . This method creates global methods that can be used throughout the entire application. FuncMap has type `map[string]interface{}` mapping a string, the function name, to a function. The mapped functions must have either a single return value, or two return values where the second has type error.
+
+```go
+    // Creating a template with function hasPermission
+    testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{
+        "hasPermission": func(user User, feature string) bool {
+          if user.ID == 1 && feature == "feature-a" {
+            return true
+          }
+          return false
+        },
+      }).ParseFiles("hello.gohtml")
+```
+
+Here the function to check if a user has permission for a feature is mapped to the string `"hasPermission"` and stored in the FuncMap. Note that the custom functions must be created before calling `ParseFiles()`
+
+The function could be executed in the template as follows:
+
+```go
+    {{ if hasPermission .User "feature-a" }}
+```
+
+The `.User` and string `"feature-a"` are both passed to `hasPermission` as arguments.
+
+---
+
+#### Custom Functions (Globally)
+
+The previous two methods of custom functions rely on `.User` being passed to the template. This works in many cases but in a large application passing too many objects to a template can become difficult to maintain across many templates. We can change the implementation of the custom function to work without the .User being passed.
+
+Using a similar feature example as the other 2 sections first you would have to create a default `hasPermission` function and define it in the template’s function map.
+
+```go
+      testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{
+        "hasPermission": func(feature string) bool {
+          return false
+        },
+      }).ParseFiles("hello.gohtml")
+```
+
+This function could be placed in `main()` or somewhere that ensures the default `hasPermission` is created in the `hello.gohtml` function map. The default function just returns false but it defines the function and implementation that doesn’t require `User` .
+
+Next a closure could be used to redefine the `hasPermission` function. It would use the `User` data available when it is created in a handler rather than having `User` data passed to it. Within the handler for the template you can redefine any functions to use the information available.
+
+```go
+    func handler(w http.ResponseWriter, r *http.Request) {
+    	w.Header().Set("Content-Type", "text/html")
+
+    	user := User{
+    		ID:    1,
+    		Email: "Curtis.vermeeren@gmail.com",
+    	}
+    	vd := ViewData{}
+    	err := testTemplate.Funcs(template.FuncMap{
+    		"hasPermission": func(feature string) bool {
+    			if user.ID == 1 && feature == "feature-a" {
+    				return true
+    			}
+    			return false
+    		},
+    	}).Execute(w, vd)
+    	if err != nil {
+    		http.Error(w, err.Error(), http.StatusInternalServerError)
+    	}
+    }
+```
+
+In this handler a `User` is created with ID and Email, Then a `ViewData` is created without passing the user to it. The `hasPermission` function is redefined using `user.ID` which is available when the function is created. `{{if hasPermission "feature-a"}}` can be used in a template without having to pass a `User` to the template as the User object in the handler is used instead.
+
+---

+ 156 - 0
vendor/github.com/gofiber/template/html/v2/html.go

@@ -0,0 +1,156 @@
+package html
+
+import (
+	"fmt"
+	"html/template"
+	"io"
+	"log"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+
+	core "github.com/gofiber/template"
+	"github.com/gofiber/utils"
+)
+
+// Engine struct
+type Engine struct {
+	core.Engine
+	// templates
+	Templates *template.Template
+}
+
+// New returns an HTML render engine for Fiber
+func New(directory, extension string) *Engine {
+	engine := &Engine{
+		Engine: core.Engine{
+			Left:       "{{",
+			Right:      "}}",
+			Directory:  directory,
+			Extension:  extension,
+			LayoutName: "embed",
+			Funcmap:    make(map[string]interface{}),
+		},
+	}
+	engine.AddFunc(engine.LayoutName, func() error {
+		return fmt.Errorf("layoutName called unexpectedly")
+	})
+	return engine
+}
+
+// NewFileSystem returns an HTML render engine for Fiber with file system
+func NewFileSystem(fs http.FileSystem, extension string) *Engine {
+	engine := &Engine{
+		Engine: core.Engine{
+			Left:       "{{",
+			Right:      "}}",
+			Directory:  "/",
+			FileSystem: fs,
+			Extension:  extension,
+			LayoutName: "embed",
+			Funcmap:    make(map[string]interface{}),
+		},
+	}
+	engine.AddFunc(engine.LayoutName, func() error {
+		return fmt.Errorf("layoutName called unexpectedly")
+	})
+	return engine
+}
+
+// Load parses the templates to the engine.
+func (e *Engine) Load() error {
+	if e.Loaded {
+		return nil
+	}
+	// race safe
+	e.Mutex.Lock()
+	defer e.Mutex.Unlock()
+	e.Templates = template.New(e.Directory)
+
+	// Set template settings
+	e.Templates.Delims(e.Left, e.Right)
+	e.Templates.Funcs(e.Funcmap)
+
+	walkFn := func(path string, info os.FileInfo, err error) error {
+		// Return error if exist
+		if err != nil {
+			return err
+		}
+		// Skip file if it's a directory or has no file info
+		if info == nil || info.IsDir() {
+			return nil
+		}
+		// Skip file if it does not equal the given template Extension
+		if len(e.Extension) >= len(path) || path[len(path)-len(e.Extension):] != e.Extension {
+			return nil
+		}
+		// Get the relative file path
+		// ./views/html/index.tmpl -> index.tmpl
+		rel, err := filepath.Rel(e.Directory, path)
+		if err != nil {
+			return err
+		}
+		// Reverse slashes '\' -> '/' and
+		// partials\footer.tmpl -> partials/footer.tmpl
+		name := filepath.ToSlash(rel)
+		// Remove ext from name 'index.tmpl' -> 'index'
+		name = strings.TrimSuffix(name, e.Extension)
+		// name = strings.Replace(name, e.Extension, "", -1)
+		// Read the file
+		// #gosec G304
+		buf, err := utils.ReadFile(path, e.FileSystem)
+		if err != nil {
+			return err
+		}
+		// Create new template associated with the current one
+		// This enable use to invoke other templates {{ template .. }}
+		_, err = e.Templates.New(name).Parse(string(buf))
+		if err != nil {
+			return err
+		}
+		// Debugging
+		if e.Verbose {
+			log.Printf("views: parsed template: %s\n", name)
+		}
+		return err
+	}
+	// notify engine that we parsed all templates
+	e.Loaded = true
+	if e.FileSystem != nil {
+		return utils.Walk(e.FileSystem, e.Directory, walkFn)
+	}
+	return filepath.Walk(e.Directory, walkFn)
+}
+
+// Render will execute the template name along with the given values.
+func (e *Engine) Render(out io.Writer, name string, binding interface{}, layout ...string) error {
+	if !e.Loaded || e.ShouldReload {
+		if e.ShouldReload {
+			e.Loaded = false
+		}
+		if err := e.Load(); err != nil {
+			return err
+		}
+	}
+
+	tmpl := e.Templates.Lookup(name)
+	if tmpl == nil {
+		return fmt.Errorf("render: template %s does not exist", name)
+	}
+	if len(layout) > 0 && layout[0] != "" {
+		lay := e.Templates.Lookup(layout[0])
+		if lay == nil {
+			return fmt.Errorf("render: LayoutName %s does not exist", layout[0])
+		}
+		e.Mutex.Lock()
+		defer e.Mutex.Unlock()
+		lay.Funcs(map[string]interface{}{
+			e.LayoutName: func() error {
+				return tmpl.Execute(out, binding)
+			},
+		})
+		return lay.Execute(out, binding)
+	}
+	return tmpl.Execute(out, binding)
+}

+ 104 - 0
vendor/github.com/gofiber/template/template.go

@@ -0,0 +1,104 @@
+package template
+
+import (
+	"io"
+	"net/http"
+	"sync"
+)
+
+// IEngine interface, to be implemented for any templating engine added to the repository
+type IEngine interface {
+	IEngineCore
+	Load() error
+	Render(out io.Writer, template string, binding interface{}, layout ...string) error
+}
+
+// IEngineCore interface
+type IEngineCore interface {
+	AddFunc(name string, fn interface{}) IEngineCore
+	AddFuncMap(m map[string]interface{}) IEngineCore
+	Debug(enabled bool) IEngineCore
+	Delims(left, right string) IEngineCore
+	FuncMap() map[string]interface{}
+	Layout(key string) IEngineCore
+	Reload(enabled bool) IEngineCore
+}
+
+// Engine engine struct
+type Engine struct {
+	IEngineCore
+	// delimiters
+	Left  string
+	Right string
+	// views folder
+	Directory string
+	// http.FileSystem supports embedded files
+	FileSystem http.FileSystem
+	// views extension
+	Extension string
+	// layout variable name that incapsulates the template
+	LayoutName string
+	// determines if the engine parsed all templates
+	Loaded bool
+	// reload on each render
+	ShouldReload bool
+	// debug prints the parsed templates
+	Verbose bool
+	// lock for funcmap and templates
+	Mutex sync.RWMutex
+	// template funcmap
+	Funcmap map[string]interface{}
+}
+
+// AddFunc adds the function to the template's function map.
+// It is legal to overwrite elements of the default actions
+func (e *Engine) AddFunc(name string, fn interface{}) *Engine {
+	e.Mutex.Lock()
+	e.Funcmap[name] = fn
+	e.Mutex.Unlock()
+	return e
+}
+
+// AddFuncMap adds the functions from a map to the template's function map.
+// It is legal to overwrite elements of the default actions
+func (e *Engine) AddFuncMap(m map[string]interface{}) *Engine {
+	e.Mutex.Lock()
+	for name, fn := range m {
+		e.Funcmap[name] = fn
+	}
+	e.Mutex.Unlock()
+	return e
+}
+
+// Debug will print the parsed templates when Load is triggered.
+func (e *Engine) Debug(enabled bool) *Engine {
+	e.Verbose = enabled
+	return e
+}
+
+// Delims sets the action delimiters to the specified strings, to be used in
+// templates. An empty delimiter stands for the
+// corresponding default: "{{" and "}}".
+func (e *Engine) Delims(left, right string) *Engine {
+	e.Left, e.Right = left, right
+	return e
+}
+
+// FuncMap returns the template's function map.
+func (e *Engine) FuncMap() map[string]interface{} {
+	return e.Funcmap
+}
+
+// Layout defines the variable name that will incapsulate the template
+func (e *Engine) Layout(key string) *Engine {
+	e.LayoutName = key
+	return e
+}
+
+// Reload if set to true the templates are reloading on each render,
+// use it when you're in development and you don't want to restart
+// the application when you edit a template file.
+func (e *Engine) Reload(enabled bool) *Engine {
+	e.ShouldReload = enabled
+	return e
+}

+ 21 - 0
vendor/github.com/gofiber/utils/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Fiber
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 87 - 0
vendor/github.com/gofiber/utils/README.md

@@ -0,0 +1,87 @@
+A collection of common functions but with better performance, less allocations and no dependencies created for [Fiber](https://github.com/gofiber/fiber).
+
+```go
+// go test -benchmem -run=^$ -bench=Benchmark_ -count=2
+
+Benchmark_ToLowerBytes/fiber-16                 42847654                25.7 ns/op             0 B/op          0 allocs/op
+Benchmark_ToLowerBytes/fiber-16                 46143196                25.7 ns/op             0 B/op          0 allocs/op
+Benchmark_ToLowerBytes/default-16               17387322                67.4 ns/op            48 B/op          1 allocs/op
+Benchmark_ToLowerBytes/default-16               17906491                67.4 ns/op            48 B/op          1 allocs/op
+
+Benchmark_ToUpperBytes/fiber-16                 46143729                25.7 ns/op             0 B/op          0 allocs/op
+Benchmark_ToUpperBytes/fiber-16                 47989250                25.6 ns/op             0 B/op          0 allocs/op
+Benchmark_ToUpperBytes/default-16               15580854                76.7 ns/op            48 B/op          1 allocs/op
+Benchmark_ToUpperBytes/default-16               15381202                76.9 ns/op            48 B/op          1 allocs/op
+
+Benchmark_TrimRightBytes/fiber-16               70572459                16.3 ns/op             8 B/op          1 allocs/op
+Benchmark_TrimRightBytes/fiber-16               74983597                16.3 ns/op             8 B/op          1 allocs/op
+Benchmark_TrimRightBytes/default-16             16212578                74.1 ns/op            40 B/op          2 allocs/op
+Benchmark_TrimRightBytes/default-16             16434686                74.1 ns/op            40 B/op          2 allocs/op
+
+Benchmark_TrimLeftBytes/fiber-16                74983128                16.3 ns/op             8 B/op          1 allocs/op
+Benchmark_TrimLeftBytes/fiber-16                74985002                16.3 ns/op             8 B/op          1 allocs/op
+Benchmark_TrimLeftBytes/default-16              21047868                56.5 ns/op            40 B/op          2 allocs/op
+Benchmark_TrimLeftBytes/default-16              21048015                56.5 ns/op            40 B/op          2 allocs/op
+
+Benchmark_TrimBytes/fiber-16                    54533307                21.9 ns/op            16 B/op          1 allocs/op
+Benchmark_TrimBytes/fiber-16                    54532812                21.9 ns/op            16 B/op          1 allocs/op
+Benchmark_TrimBytes/default-16                  14282517                84.6 ns/op            48 B/op          2 allocs/op
+Benchmark_TrimBytes/default-16                  14114508                84.7 ns/op            48 B/op          2 allocs/op
+
+Benchmark_EqualFolds/fiber-16                   36355153                32.6 ns/op             0 B/op          0 allocs/op
+Benchmark_EqualFolds/fiber-16                   36355593                32.6 ns/op             0 B/op          0 allocs/op
+Benchmark_EqualFolds/default-16                 15186220                78.1 ns/op             0 B/op          0 allocs/op
+Benchmark_EqualFolds/default-16                 15186412                78.3 ns/op             0 B/op          0 allocs/op
+
+Benchmark_UUID/fiber-16                         23994625                49.8 ns/op            48 B/op          1 allocs/op
+Benchmark_UUID/fiber-16                         23994768                50.1 ns/op            48 B/op          1 allocs/op
+Benchmark_UUID/default-16                        3233772                 371 ns/op           208 B/op          6 allocs/op
+Benchmark_UUID/default-16                        3251295                 370 ns/op           208 B/op          6 allocs/op
+
+Benchmark_GetString/unsafe-16                 1000000000               0.709 ns/op             0 B/op          0 allocs/op
+Benchmark_GetString/unsafe-16                 1000000000               0.713 ns/op             0 B/op          0 allocs/op
+Benchmark_GetString/default-16                  59986202                19.0 ns/op            16 B/op          1 allocs/op
+Benchmark_GetString/default-16                  63142939                19.0 ns/op            16 B/op          1 allocs/op
+
+Benchmark_GetBytes/unsafe-16                   508360195                2.36 ns/op             0 B/op          0 allocs/op
+Benchmark_GetBytes/unsafe-16                   508359979                2.35 ns/op             0 B/op          0 allocs/op
+Benchmark_GetBytes/default-16                   46143019                25.7 ns/op            16 B/op          1 allocs/op
+Benchmark_GetBytes/default-16                   44434734                25.6 ns/op            16 B/op          1 allocs/op
+
+Benchmark_GetMIME/fiber-16                      21423750                56.3 ns/op             0 B/op          0 allocs/op
+Benchmark_GetMIME/fiber-16                      21423559                55.4 ns/op             0 B/op          0 allocs/op
+Benchmark_GetMIME/default-16                     6735282                 173 ns/op             0 B/op          0 allocs/op
+Benchmark_GetMIME/default-16                     6895002                 172 ns/op             0 B/op          0 allocs/op
+
+Benchmark_StatusMessage/fiber-16              1000000000               0.766 ns/op             0 B/op          0 allocs/op
+Benchmark_StatusMessage/fiber-16              1000000000               0.767 ns/op             0 B/op          0 allocs/op
+Benchmark_StatusMessage/default-16             159538528                7.50 ns/op             0 B/op          0 allocs/op
+Benchmark_StatusMessage/default-16             159750830                7.51 ns/op             0 B/op          0 allocs/op
+
+Benchmark_ToUpper/fiber-16                      22217408                53.3 ns/op            48 B/op          1 allocs/op
+Benchmark_ToUpper/fiber-16                      22636554                53.2 ns/op            48 B/op          1 allocs/op
+Benchmark_ToUpper/default-16                    11108600                 108 ns/op            48 B/op          1 allocs/op
+Benchmark_ToUpper/default-16                    11108580                 108 ns/op            48 B/op          1 allocs/op
+
+Benchmark_ToLower/fiber-16                      23994720                49.8 ns/op            48 B/op          1 allocs/op
+Benchmark_ToLower/fiber-16                      23994768                50.1 ns/op            48 B/op          1 allocs/op
+Benchmark_ToLower/default-16                    10808376                 110 ns/op            48 B/op          1 allocs/op
+Benchmark_ToLower/default-16                    10617034                 110 ns/op            48 B/op          1 allocs/op
+
+Benchmark_TrimRight/fiber-16                   413699521                2.94 ns/op             0 B/op          0 allocs/op
+Benchmark_TrimRight/fiber-16                   415131687                2.91 ns/op             0 B/op          0 allocs/op
+Benchmark_TrimRight/default-16                  23994577                49.1 ns/op            32 B/op          1 allocs/op
+Benchmark_TrimRight/default-16                  24484249                49.4 ns/op            32 B/op          1 allocs/op
+
+Benchmark_TrimLeft/fiber-16                    379661170                3.13 ns/op             0 B/op          0 allocs/op
+Benchmark_TrimLeft/fiber-16                    382079941                3.16 ns/op             0 B/op          0 allocs/op
+Benchmark_TrimLeft/default-16                   27900877                41.9 ns/op            32 B/op          1 allocs/op
+Benchmark_TrimLeft/default-16                   28564898                42.0 ns/op            32 B/op          1 allocs/op
+
+Benchmark_Trim/fiber-16                        236632856                 4.96 ns/op            0 B/op          0 allocs/op
+Benchmark_Trim/fiber-16                        237570085                 4.93 ns/op            0 B/op          0 allocs/op
+Benchmark_Trim/default-16                       18457221                 66.0 ns/op           32 B/op          1 allocs/op
+Benchmark_Trim/default-16                       18177328                 65.9 ns/op           32 B/op          1 allocs/op
+Benchmark_Trim/default.trimspace-16            188933770                 6.33 ns/op            0 B/op          0 allocs/op
+Benchmark_Trim/default.trimspace-16            184007649                 6.42 ns/op            0 B/op          0 allocs/op
+```

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor