buninu-browser 0.2.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/LICENSES/jsmdcui.txt +22 -0
  2. package/README.md +102 -2
  3. package/frontends/wayland/LICENSE-land +21 -0
  4. package/frontends/wayland/README.md +123 -0
  5. package/frontends/wayland/casty/LICENSE +21 -0
  6. package/frontends/wayland/casty/bookmarks.js +32 -0
  7. package/frontends/wayland/casty/click.js +96 -0
  8. package/frontends/wayland/casty/commands.js +61 -0
  9. package/frontends/wayland/casty/config.js +45 -0
  10. package/frontends/wayland/casty/download-directory.js +28 -0
  11. package/frontends/wayland/casty/download.js +49 -0
  12. package/frontends/wayland/casty/hints.js +334 -0
  13. package/frontends/wayland/casty/keys.js +56 -0
  14. package/frontends/wayland/casty/page-commands.js +80 -0
  15. package/frontends/wayland/casty/prompt-width.js +148 -0
  16. package/frontends/wayland/casty/urlbar.js +606 -0
  17. package/frontends/wayland/client.js +88 -0
  18. package/frontends/wayland/clipboard.js +80 -0
  19. package/frontends/wayland/index.js +116 -0
  20. package/frontends/wayland/input.js +56 -0
  21. package/frontends/wayland/protocol.js +55 -0
  22. package/frontends/wayland/protocols/wayland.xml +3248 -0
  23. package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
  24. package/frontends/wayland/transport.js +120 -0
  25. package/frontends/wayland/urlbar.js +158 -0
  26. package/index.js +28 -1
  27. package/lib/clipboard-command.js +10 -0
  28. package/lib/clipboard-powershell.js +22 -0
  29. package/lib/clipboard.js +57 -0
  30. package/lib/document-types.js +11 -0
  31. package/lib/headless-shell.js +78 -13
  32. package/lib/layout/text-layout.js +17 -9
  33. package/lib/paint/display-list.js +5 -1
  34. package/lib/paint/frame.js +35 -3
  35. package/lib/paint/raw-frames.js +47 -0
  36. package/lib/qml/README.md +971 -0
  37. package/lib/qml/builtins.js +700 -0
  38. package/lib/qml/canvas-dom.js +115 -0
  39. package/lib/qml/document.js +1176 -0
  40. package/lib/qml/format.js +9 -0
  41. package/lib/qml/host.js +324 -0
  42. package/lib/qml/images.js +199 -0
  43. package/lib/qml/launcher.js +132 -0
  44. package/lib/qml/loader.js +517 -0
  45. package/lib/qml/media-renderer.js +49 -0
  46. package/lib/qml/media.js +86 -0
  47. package/lib/qml/models.js +622 -0
  48. package/lib/qml/parser.js +381 -0
  49. package/lib/qml/qs +5 -0
  50. package/lib/qml/qscasty +3 -0
  51. package/lib/qml/qsheadless +3 -0
  52. package/lib/qml/qswayland +3 -0
  53. package/lib/qml/reactive.js +159 -0
  54. package/lib/qml/runtime.js +3381 -0
  55. package/lib/qml/shapes.js +243 -0
  56. package/lib/qml/text.js +109 -0
  57. package/lib/qml/textures.js +123 -0
  58. package/lib/qml/wayland-clipboard.js +32 -0
  59. package/lib/qml/worker-thread.js +70 -0
  60. package/lib/render-tree/index.js +16 -1
  61. package/lib/renderer/bindings.js +11 -0
  62. package/lib/renderer/canvas-2d.js +13 -0
  63. package/lib/renderer/compositor-channel.js +103 -0
  64. package/lib/renderer/compositor.js +25 -33
  65. package/lib/renderer/flags.js +1 -1
  66. package/lib/renderer/host.js +128 -92
  67. package/lib/renderer/main.js +38 -4
  68. package/lib/renderer/page-realm.js +32 -1
  69. package/lib/renderer/page-renderer.js +250 -21
  70. package/lib/renderer/text-input-paint.js +54 -0
  71. package/lib/renderer/text-input.js +57 -0
  72. package/lib/style/computed-style.js +2 -2
  73. package/package.json +1 -1
  74. package/test/clipboard.test.js +54 -0
  75. package/test/fixtures/browser-raw-demo.html +36 -0
  76. package/test/fixtures/flea-helpers/Dockerfile +36 -0
  77. package/test/fixtures/flea-helpers/bat.mjs +43 -0
  78. package/test/fixtures/flea-helpers/bin/bat +3 -0
  79. package/test/fixtures/flea-helpers/bin/bwrap +10 -0
  80. package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
  81. package/test/fixtures/flea-helpers/bin/glow +3 -0
  82. package/test/fixtures/flea-helpers/bin/qs +3 -0
  83. package/test/fixtures/flea-helpers/bin/unsupported +3 -0
  84. package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
  85. package/test/fixtures/flea-helpers/check-media.mjs +29 -0
  86. package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
  87. package/test/fixtures/flea-helpers/check.mjs +36 -0
  88. package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
  89. package/test/fixtures/flea-helpers/source-mime.xml +28 -0
  90. package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
  91. package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
  92. package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
  93. package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
  94. package/test/fixtures/qml/flea/LICENSE +21 -0
  95. package/test/fixtures/qml/flea/README.md +25 -0
  96. package/test/fixtures/qml/flea/Theme.qml +8 -0
  97. package/test/fixtures/qml/flea/demo.qml +24 -0
  98. package/test/fixtures/qml/flea/qmldir +3 -0
  99. package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
  100. package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
  101. package/test/fixtures/qml/flea-theme/README.md +70 -0
  102. package/test/fixtures/qml/flea-theme/button.qml +17 -0
  103. package/test/fixtures/qml/flea-theme/demo.qml +27 -0
  104. package/test/fixtures/qml/flea-theme/field.qml +21 -0
  105. package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
  106. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
  107. package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
  108. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
  109. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
  110. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
  111. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
  112. package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
  113. package/test/fixtures/qml/flea-theme/segment.qml +17 -0
  114. package/test/fixtures/qml/flea-theme/settings.qml +58 -0
  115. package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
  116. package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
  117. package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
  118. package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
  119. package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
  120. package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
  121. package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
  122. package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
  123. package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
  124. package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
  125. package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
  126. package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
  127. package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
  128. package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
  129. package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
  130. package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
  131. package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
  132. package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
  133. package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
  134. package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
  135. package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
  136. package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
  137. package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
  138. package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
  139. package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
  140. package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
  141. package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
  142. package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
  143. package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
  144. package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
  145. package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
  146. package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
  147. package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
  148. package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
  149. package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
  150. package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
  151. package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
  152. package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
  153. package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
  154. package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
  155. package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
  156. package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
  157. package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
  158. package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
  159. package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
  160. package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
  161. package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
  162. package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
  163. package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
  164. package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
  165. package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
  166. package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
  167. package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
  168. package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
  169. package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
  170. package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
  171. package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
  172. package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
  173. package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
  174. package/test/fixtures/qml/hello.qml +26 -0
  175. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
  176. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
  177. package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
  178. package/test/fixtures/qml/imports/components/Badge.qml +15 -0
  179. package/test/fixtures/qml/imports/logic/base.js +2 -0
  180. package/test/fixtures/qml/imports/logic/counter.js +4 -0
  181. package/test/fixtures/qml/imports/logic/math.js +3 -0
  182. package/test/fixtures/qml/imports/logic/shared.js +3 -0
  183. package/test/fixtures/qml/imports/main.qml +21 -0
  184. package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
  185. package/test/fixtures/qml/imports/theme/qmldir +2 -0
  186. package/test/fixtures/qml/multiply.qml +61 -0
  187. package/test/fixtures/qml-canvas-dom.js +90 -0
  188. package/test/fixtures/qml-media-audio.js +39 -0
  189. package/test/fixtures/raw-capture-benchmark.json +76 -0
  190. package/test/helpers/flea-sandbox.js +48 -0
  191. package/test/helpers/quickshell-fixture.js +125 -0
  192. package/test/html-clipboard-wayland.test.js +47 -0
  193. package/test/index.test.js +13 -0
  194. package/test/manual-casty-without-qml.js +87 -0
  195. package/test/manual-flea-application.js +49 -0
  196. package/test/manual-flea-binary.js +62 -0
  197. package/test/manual-flea-qml.js +9 -0
  198. package/test/manual-flea-settings.js +40 -0
  199. package/test/manual-flea-theme.js +40 -0
  200. package/test/manual-qml-removal.js +46 -0
  201. package/test/manual-without-qml.js +45 -0
  202. package/test/qml-canvas-dom.test.js +37 -0
  203. package/test/qml-clipboard-bridge.test.js +25 -0
  204. package/test/qml-clipboard-wayland.test.js +40 -0
  205. package/test/qml-connections-timing.test.js +73 -0
  206. package/test/qml-dynamic.test.js +401 -0
  207. package/test/qml-flea-app.test.js +146 -0
  208. package/test/qml-flea-backend.test.js +78 -0
  209. package/test/qml-flea-columns-backend.test.js +38 -0
  210. package/test/qml-flea-compile.test.js +78 -0
  211. package/test/qml-flea-launcher.test.js +88 -0
  212. package/test/qml-flea-markdown-backend.test.js +31 -0
  213. package/test/qml-flea-media.test.js +28 -0
  214. package/test/qml-flea-navigation-backend.test.js +35 -0
  215. package/test/qml-flea-navigation-timing.test.js +147 -0
  216. package/test/qml-flea-preview-backend.test.js +53 -0
  217. package/test/qml-flea-previews.test.js +163 -0
  218. package/test/qml-flea-rows.test.js +101 -0
  219. package/test/qml-flea-textures.test.js +59 -0
  220. package/test/qml-flea-view-switch.test.js +33 -0
  221. package/test/qml-flea-wayland.test.js +68 -0
  222. package/test/qml-flea.test.js +34 -0
  223. package/test/qml-host.test.js +238 -0
  224. package/test/qml-imports.test.js +102 -0
  225. package/test/qml-inherited-handlers.test.js +51 -0
  226. package/test/qml-input-selection.test.js +66 -0
  227. package/test/qml-interactions.test.js +309 -0
  228. package/test/qml-language.test.js +167 -0
  229. package/test/qml-launcher.test.js +54 -0
  230. package/test/qml-layout-text.test.js +205 -0
  231. package/test/qml-media.test.js +63 -0
  232. package/test/qml-persisted-delegates.test.js +45 -0
  233. package/test/qml-quickshell-controls.test.js +65 -0
  234. package/test/qml-quickshell-settings.test.js +62 -0
  235. package/test/qml-repeater.test.js +72 -0
  236. package/test/qml-style-updates.test.js +124 -0
  237. package/test/qml-textures.test.js +66 -0
  238. package/test/qml-views.test.js +332 -0
  239. package/test/qml-worker.test.js +69 -0
  240. package/test/qml.test.js +83 -0
  241. package/test/raw-capture.test.js +152 -0
  242. package/test/renderer-host.test.js +11 -0
  243. package/test/text-input-pointer.test.js +31 -0
  244. package/test/text-input-scroll.test.js +29 -0
  245. package/test/text-input.test.js +47 -0
  246. package/test/wayland-frontend.test.js +83 -0
  247. package/test/wayland-urlbar.test.js +38 -0
  248. package/test/window-resize.test.js +36 -0
  249. package/tools/bench-raw-capture.js +32 -0
@@ -0,0 +1,379 @@
1
+ .pragma library
2
+
3
+ .import "AnchorHold.js" as Hold
4
+
5
+ // Anchor locate IDs sit above QML's signed-int menu IDs, keeping the reply askers disjoint.
6
+ var LOCATE_ID_FLOOR = 2147483648
7
+ var locateSeq = LOCATE_ID_FLOOR
8
+
9
+ // Re-reading the open listing without moving the user off it, for a foreign change and Flea's own delete.
10
+
11
+ // A re-read renumbers every row, so it waits while an interaction owns the rows or while any paths asker resolves.
12
+ function busy(pane, anchor) {
13
+ if (!pane)
14
+ return true
15
+ if (anchor && anchor.path === pane.path)
16
+ return true
17
+ if (pane.menuActions && (pane.menuActions.pendingAction || pane.menuActions.pendingActivation === true))
18
+ return true
19
+ if (pane.dragActive === true || pane.awaitingPaths === true || pane.pointerHeld === true)
20
+ return true
21
+ if (pane.pathsPending)
22
+ return true
23
+ if (pane.clipPending !== undefined && pane.clipPending !== null)
24
+ return true
25
+ return pane.listInFlight || pane.renamingIndex >= 0 || pane.renamePending
26
+ || pane.menuVisible || (pane.menuActions && pane.menuActions.opened) || pane.filterTyping || pane.searchMode.length > 0
27
+ || pane.selectionBand !== null || (pane.collide && pane.collide.pending !== null)
28
+ }
29
+
30
+ function watched(pane, wantChanged, rowH) {
31
+ return anchoredRefresh(pane, false, selectedMarks(pane), wantChanged, rowH)
32
+ }
33
+
34
+ // Flea's own delete: the anchor is the deleted cursor row and apply()'s fallback lands on whatever took its place, which is Finder's rule. A failed delete matches by name instead.
35
+ // Sample input: afterDelete(pane, true) with pane.trashedFirst set by Ops.trash.
36
+ function afterDelete(pane, landed) {
37
+ if (landed && pane.trashedFirst >= 0)
38
+ pane.cursorIndex = pane.trashedFirst
39
+ pane.trashedFirst = -1
40
+ return anchoredRefresh(pane, true, null, false)
41
+ }
42
+
43
+ // A listing past its wait stops loading the pane: late rows are dropped as replaced.
44
+ function clearWaiting(pane) {
45
+ if (pane.listingState === "waiting") {
46
+ pane.listInFlight = false
47
+ pane.listingState = "loading"
48
+ pane.stateMessage = ""
49
+ return true
50
+ }
51
+ return false
52
+ }
53
+
54
+ // Which row a full target path names, NFC-matched the way the backend lists it.
55
+ function selectMatch(rows, target, folder) {
56
+ var base = String(folder || "")
57
+ var text = String(target || "")
58
+ var leaf = base.length <= 1 ? text.substring(1) : text.substring(base.length + 1)
59
+ if (base.length > 1 && text.substring(0, base.length + 1) !== base + "/")
60
+ return -1
61
+ var want = typeof leaf.normalize === "function" ? leaf.normalize("NFC") : leaf
62
+ for (var i = 0; i < rows.length; i++) {
63
+ if (String(rows[i].n || "") === leaf)
64
+ return i
65
+ }
66
+ for (var j = 0; j < rows.length; j++) {
67
+ var name = String(rows[j].n || "")
68
+ var norm = typeof name.normalize === "function" ? name.normalize("NFC") : name
69
+ if (norm === want)
70
+ return j
71
+ }
72
+ return -1
73
+ }
74
+
75
+ // The cursor index for a full target path, or -1 when the listing holds no such row.
76
+ function matchListed(pane, target) {
77
+ var at = selectMatch(pane.rows, target, pane.path)
78
+ return at >= 0 ? pane.held + at : -1
79
+ }
80
+
81
+ // A rename commit keeps the renamed row (source leaf mapped to destination) unless moved: a click or key took the cursor to another row.
82
+ function pointerRow(pane, request, rowH) {
83
+ var row = pane.rowFor(pane.cursorIndex)
84
+ var name = row ? String(row.n) : ""
85
+ var src = Hold.holdLeaf(request.source)
86
+ var dst = request.destination ? Hold.holdLeaf(request.destination) : ""
87
+ var moved = name.length > 0 && name !== src && name !== dst
88
+ if (name === src && dst.length > 0)
89
+ name = dst
90
+ var place = Hold.viewFields(pane, rowH)
91
+ return { name: name, index: pane.cursorIndex, start: pane.held, path: pane.path, select: true, moved: moved,
92
+ offset: place.offset, rowH: place.rowH, view: place.view, renamed: !moved && dst.length > 0 && name === dst }
93
+ }
94
+
95
+ function anchoredRefresh(pane, select, marks, wantChanged, rowH) {
96
+ if (pane.listInFlight) {
97
+ return null
98
+ }
99
+ var row = pane.rowFor(pane.cursorIndex)
100
+ var lone = pane.selection && typeof pane.selection.follows === "function" ? !!pane.selection.follows() : false
101
+ var place = Hold.viewFields(pane, rowH)
102
+ // The path rides along because the anchor can outlive one rows reply: a navigation between the two below would otherwise put this directory's cursor row onto the next directory's listing.
103
+ var anchor = { name: row ? String(row.n) : "", index: pane.cursorIndex, start: pane.held,
104
+ path: pane.path, select: select === true, wantChanged: wantChanged === true, reloadFrom: pane.total,
105
+ marks: marks || null, kept: [], hadMarks: marks !== null && marks.length > 0,
106
+ lone: lone, offset: place.offset, rowH: place.rowH, view: place.view }
107
+ // F2: names outside the held window come from one batched paths request before the swap, tagged so the reply reaches only this asker.
108
+ if (anchor.marks) {
109
+ var need = []
110
+ for (var i = 0; i < anchor.marks.length; i++) {
111
+ if (anchor.marks[i].name === null)
112
+ need.push(anchor.marks[i].index)
113
+ }
114
+ if (need.length > 0 && !pane.pathsPending && (pane.clipPending === undefined || pane.clipPending === null)
115
+ && pane.backend && pane.backend.send) {
116
+ pane.backend.send({ c: "paths", rows: need })
117
+ anchor.needPaths = need
118
+ pane.pathsPending = { kind: "anchor" }
119
+ return anchor
120
+ }
121
+ }
122
+ Hold.startList(pane, anchor)
123
+ return anchor
124
+ }
125
+
126
+ // The batched paths answer for F2: fills unheld names, then starts the list the swap holds.
127
+ function fillPaths(pane, anchor, list) {
128
+ return Hold.fillPaths(pane, anchor, list)
129
+ }
130
+
131
+ // True only for the anchor's own tagged reply; a clipboard, drag or compress reply takes nothing.
132
+ function takesPaths(pane, anchor) {
133
+ return !!(anchor && anchor.needPaths && pane.pathsPending && pane.pathsPending.kind === "anchor")
134
+ }
135
+
136
+ // A failed paths ask keeps the anchor so the shifted listing lands it by name; a refused locate ends it.
137
+ // Sample input: failAnchor(pane, { name: "b", index: 1, marks: [], kept: [] }) keeps the anchor for rows ["NEW","a","b","c"].
138
+ function failAnchor(pane, anchor, rowH) {
139
+ if (!anchor)
140
+ return null
141
+ var hadPaths = !!anchor.needPaths
142
+ anchor.needPaths = null
143
+ anchor.locateDone = true
144
+ if (pane.pathsPending && pane.pathsPending.kind === "anchor")
145
+ pane.pathsPending = null
146
+ if (hadPaths) {
147
+ if (pane.path !== anchor.path)
148
+ return null
149
+ // Land by name on the held rows before the re-list renumbers them; a miss waits for the shifted listing.
150
+ var at = indexOf(pane, anchor.name)
151
+ if (pane.total > 0 && at >= 0) {
152
+ landOn(pane, at, anchor)
153
+ Hold.restoreView(pane, anchor, rowH)
154
+ }
155
+ // A failed ask still owes the listing that draws the outside change, unless one is already out.
156
+ if (!pane.listInFlight)
157
+ Hold.startList(pane, anchor)
158
+ // Keep the target across the renumber so apply() re-lands it; ending here would strand the pre-list index.
159
+ return anchor
160
+ }
161
+ var found = indexOf(pane, anchor.name)
162
+ if (pane.total > 0) {
163
+ landOn(pane, found >= 0 ? found : Math.min(anchor.index, pane.total - 1), anchor)
164
+ Hold.restoreView(pane, anchor, rowH)
165
+ }
166
+ finishMarks(pane, anchor)
167
+ return null
168
+ }
169
+
170
+ // PaneWire's located guard consumes only this anchor's request; a refused own reply ends the anchor.
171
+ // Sample input: { directory: "/d", id: 2147483649, transferId: 0, ok: true, matches: [{ path: "/d/a", index: 3 }] }.
172
+ function takeLocated(pane, anchor, message, rowH) {
173
+ if (!anchor || !anchor.locateSent || anchor.locateDone)
174
+ return { handled: false, anchor: anchor }
175
+ if (!message || message.directory !== pane.path || message.id !== anchor.locateId || message.transferId !== 0)
176
+ return { handled: false, anchor: anchor }
177
+ if (message.ok === false)
178
+ return { handled: true, anchor: failAnchor(pane, anchor, rowH) }
179
+ return { handled: true, anchor: fillLocated(pane, anchor, message.matches || [], rowH) }
180
+ }
181
+
182
+ // The marks the re-read carries across by file identity: an index renumbers under a fresh list, so each marked row's name is recorded and looked up again when the rows land. A gone name loses its mark rather than re-pointing at another file.
183
+ function selectedMarks(pane) {
184
+ if (!pane.selectedIndices)
185
+ return []
186
+ var out = []
187
+ var indices = pane.selectedIndices()
188
+ for (var i = 0; i < indices.length; i++) {
189
+ var row = pane.rowFor(indices[i])
190
+ out.push({ index: indices[i], name: row ? String(row.n) : null })
191
+ }
192
+ return out
193
+ }
194
+
195
+ // The listing row holding a name in the window the pane holds now, or -1.
196
+ function indexOf(pane, name) {
197
+ for (var i = 0; i < pane.rows.length; i++) {
198
+ if (String(pane.rows[i].n) === name)
199
+ return pane.held + i
200
+ }
201
+ return -1
202
+ }
203
+
204
+ // Moves whatever the held window holds from pending to kept, naming nothing on screen: the take lands once, when the anchor resolves, so no rows reply in between draws a half-restored set.
205
+ function sweepMarks(pane, anchor) {
206
+ if (!anchor.marks)
207
+ return
208
+ var pending = []
209
+ for (var m = 0; m < anchor.marks.length; m++) {
210
+ var mark = anchor.marks[m]
211
+ if (mark.name === null)
212
+ continue
213
+ var at = indexOf(pane, mark.name)
214
+ if (at >= 0)
215
+ anchor.kept.push(at)
216
+ else
217
+ pending.push(mark)
218
+ }
219
+ anchor.marks = pending
220
+ }
221
+
222
+ // The one take, on the anchor resolving: the rows the re-read cleared are marked again on the same files, and whatever is gone stays gone. A lone selection restores with only(), the way Tabs.restoreSelection does, so a plain click never turns sticky behind the re-read.
223
+ function finishMarks(pane, anchor) {
224
+ var kept = anchor.kept || []
225
+ anchor.kept = []
226
+ anchor.marks = []
227
+ if (!anchor.hadMarks)
228
+ return
229
+ if (anchor.lone === true && kept.length === 1) {
230
+ pane.selection.only(kept[0])
231
+ pane.selectionAnchor = pane.cursorIndex
232
+ pane.selectionVersion += 1
233
+ return
234
+ }
235
+ for (var i = 0; i < kept.length; i++)
236
+ pane.selection.toggle(kept[i])
237
+ pane.selectionAnchor = pane.cursorIndex
238
+ if (kept.length > 0)
239
+ pane.selectionVersion += 1
240
+ }
241
+
242
+ // Runs on each rows reply while an anchor stands. A miss in the listing's first window is not yet a miss when the anchor asked for its own window above. A gone name falls back to the clamped old index, which keeps the view where the user left it.
243
+ function apply(pane, anchor, rowH) {
244
+ if (!anchor) {
245
+ return null
246
+ }
247
+ if (pane.path !== anchor.path) {
248
+ return null
249
+ }
250
+ if (anchor.needPaths)
251
+ return anchor
252
+ sweepMarks(pane, anchor)
253
+ // F3: past the first window the cursor name alone must not finish held marks past it.
254
+ if (anchor.start > 0 && pane.held !== anchor.start && pane.total > anchor.start)
255
+ return anchor
256
+ var at = indexOf(pane, anchor.name)
257
+ // F2: marks past the held window resolve through one batched locate after.
258
+ if (anchor.marks && anchor.marks.length > 0 && !anchor.locateDone && pane.backend && pane.backend.send) {
259
+ var paths = []
260
+ for (var m = 0; m < anchor.marks.length; m++) {
261
+ if (anchor.marks[m].name !== null)
262
+ paths.push(pane.join(pane.path, anchor.marks[m].name))
263
+ }
264
+ if (paths.length > 0 && !anchor.locateSent) {
265
+ anchor.locateId = ++locateSeq
266
+ pane.backend.send({ c: "locate", paths: paths, id: anchor.locateId })
267
+ anchor.locateSent = true
268
+ anchor.locatePaths = paths
269
+ // A gone cursor file still lands on the clamped old index while its marks resolve.
270
+ if (pane.total > 0) {
271
+ landOn(pane, at >= 0 ? at : Math.min(anchor.index, pane.total - 1), anchor)
272
+ Hold.restoreView(pane, anchor, rowH)
273
+ }
274
+ return anchor
275
+ }
276
+ if (anchor.locateSent && !anchor.locateDone)
277
+ return anchor
278
+ }
279
+ if (at >= 0) {
280
+ landOn(pane, at, anchor)
281
+ Hold.restoreView(pane, anchor, rowH)
282
+ finishMarks(pane, anchor)
283
+ return null
284
+ }
285
+ // Still the first window rather than the one asked for above, so keep waiting, but only while that window can still exist: a listing that shrank past the offset comes back clamped to row 0 instead.
286
+ if (anchor.start > 0 && pane.held === 0 && pane.total > anchor.start) {
287
+ return anchor
288
+ }
289
+ if (anchor.renamed && !anchor.locateSent) anchor.locateId = ++locateSeq
290
+ if (Hold.locateRenamed(pane, anchor)) return anchor
291
+ if (pane.total > 0) {
292
+ landOn(pane, Math.min(anchor.index, pane.total - 1), anchor)
293
+ Hold.restoreView(pane, anchor, rowH)
294
+ }
295
+ finishMarks(pane, anchor)
296
+ return null
297
+ }
298
+
299
+ // The batched locate answer for F2: indices outside the held window join kept, the gone stay gone, and the cursor lands found-or-clamped either way.
300
+ // Sample input: fillLocated(pane, anchor, [{ path: "/d/a", index: 3 }]) keeps 3 and lands the cursor.
301
+ function fillLocated(pane, anchor, matches, rowH) {
302
+ if (!anchor)
303
+ return null
304
+ Hold.fillLocated(pane, anchor, matches)
305
+ var at = indexOf(pane, anchor.name)
306
+ if (pane.total > 0 && !anchor.landed) {
307
+ landOn(pane, at >= 0 ? at : Math.min(anchor.index, pane.total - 1), anchor)
308
+ Hold.restoreView(pane, anchor, rowH)
309
+ }
310
+ finishMarks(pane, anchor)
311
+ return null
312
+ }
313
+
314
+ // A watch's anchor moves the cursor and re-marks the same files: the operator's marks belong to them, and a change another program made must not move those marks onto other files. A delete's anchor selects instead, because its rows no longer exist and a bare cursor would restart the keyboard.
315
+ function landOn(pane, index, anchor) {
316
+ if (anchor.select)
317
+ pane.selectOnly(index, 0)
318
+ else
319
+ pane.setCursor(index, 0)
320
+ }
321
+
322
+ // A preference re-list keeps selection and cursor by name; a mark outside the held window has no name to keep, so a partial selection clears whole rather than keeping its subset.
323
+ function preference(pane) {
324
+ var hasRowFor = !!pane.rowFor
325
+ var cursorRow = hasRowFor ? pane.rowFor(pane.cursorIndex) : null
326
+ var names = []
327
+ var indices = pane.selectedIndices ? pane.selectedIndices() : []
328
+ if (hasRowFor) {
329
+ for (var i = 0; i < indices.length; i++) {
330
+ var row = pane.rowFor(indices[i])
331
+ if (!row) { names = []; break }
332
+ names.push(String(row.n))
333
+ }
334
+ }
335
+ return { name: cursorRow ? String(cursorRow.n) : "", index: pane.cursorIndex,
336
+ start: pane.held, path: pane.path, selected: names }
337
+ }
338
+
339
+ // Resolves only on the rows reply for the asked window; a scrolled reply or a moved cursor drops the anchor instead of yanking it.
340
+ function applyPreference(pane, anchor) {
341
+ if (!anchor)
342
+ return null
343
+ if (pane.path !== anchor.path)
344
+ return null
345
+ if (pane.cursorIndex !== 0 && pane.cursorIndex !== anchor.index)
346
+ return null
347
+ if (pane.held !== anchor.start) {
348
+ if (anchor.start > 0 && pane.held === 0 && pane.total > anchor.start)
349
+ return anchor
350
+ if (!(anchor.start > 0 && pane.total <= anchor.start))
351
+ return null
352
+ }
353
+ var cursorAt = -1
354
+ for (var i = 0; i < pane.rows.length; i++) {
355
+ if (String(pane.rows[i].n) === anchor.name) {
356
+ cursorAt = pane.held + i
357
+ break
358
+ }
359
+ }
360
+ if (cursorAt < 0 && pane.total > 0)
361
+ cursorAt = Math.min(anchor.index, pane.total - 1)
362
+ var marks = []
363
+ for (var s = 0; s < anchor.selected.length; s++) {
364
+ for (var r = 0; r < pane.rows.length; r++) {
365
+ if (String(pane.rows[r].n) === anchor.selected[s]) {
366
+ marks.push(pane.held + r)
367
+ break
368
+ }
369
+ }
370
+ }
371
+ if (cursorAt >= 0)
372
+ pane.setCursor(cursorAt, 0)
373
+ pane.selection.clear()
374
+ for (var m = 0; m < marks.length; m++)
375
+ pane.selection.toggle(marks[m])
376
+ if (marks.length > 0 || cursorAt >= 0)
377
+ pane.selectionVersion++
378
+ return null
379
+ }
@@ -0,0 +1,162 @@
1
+ .pragma library
2
+
3
+ // The resolve and the viewport restore Anchor.js calls, split out at its hard cap.
4
+
5
+ function holdLeaf(path) {
6
+ var text = String(path || "")
7
+ var cut = text.lastIndexOf("/")
8
+ return cut < 0 ? text : text.substring(cut + 1)
9
+ }
10
+
11
+ // The row height the anchor measures in: a grid tile row in the grid view, a text row elsewhere.
12
+ function rowHeight(pane, rowH) {
13
+ if (pane.viewMode === "grid" && pane.listArea && typeof pane.listArea.cellHeightPx === "number")
14
+ return pane.listArea.cellHeightPx
15
+ return rowH || pane.anchorRowHeight
16
+ }
17
+
18
+ // The cursor's view position as a restorable row: a tile row in the grid view, a list row elsewhere.
19
+ function viewRow(pane) {
20
+ var view = cursorView(pane)
21
+ if (pane.viewMode === "grid" && pane.listArea && typeof pane.listArea.columns === "number" && pane.listArea.columns > 0)
22
+ return Math.floor(view / pane.listArea.columns)
23
+ return view
24
+ }
25
+
26
+ // The surface that scrolls: the active column's own list in the columns view, the pane's list area elsewhere.
27
+ function viewport(pane) {
28
+ if (pane.viewMode === "columns" && pane.columnsArea)
29
+ return pane.columnsArea.activeColumn().viewport
30
+ return pane.listArea || null
31
+ }
32
+
33
+ // Where the cursor row sits in the viewport, which restoreView puts back once a re-list has reset the view to its top.
34
+ function viewFields(pane, rowH) {
35
+ var area = viewport(pane)
36
+ var rh = rowHeight(pane, rowH)
37
+ var view = viewRow(pane)
38
+ var contentY = area && typeof area.contentY === "number" ? area.contentY : 0
39
+ var originY = area && typeof area.originY === "number" ? area.originY : 0
40
+ return { offset: view * rh + originY - contentY, rowH: rh, view: view }
41
+ }
42
+
43
+ // Lists the anchor's own directory; a navigation while the anchor waited drops the anchor and lists nothing, so the debt never travels.
44
+ function startList(pane, anchor) {
45
+ if (!anchor || pane.path !== anchor.path)
46
+ return null
47
+ if (pane.recentMode && pane.recentMode.length > 0) {
48
+ // Recent may deliver cached history synchronously, so its run must already see the anchor.
49
+ pane.wire.anchor = anchor
50
+ pane.refresh("")
51
+ return anchor
52
+ }
53
+ pane.openWithoutHistory(anchor.path, { keptQuery: pane.filterQuery, wantChanged: anchor.wantChanged === true, inPlace: true })
54
+ if (anchor.wantChanged) {
55
+ pane.reloadFrom = anchor.reloadFrom
56
+ pane.reloadChanged = -1
57
+ }
58
+ if (anchor.start > 0)
59
+ pane.backend.window(anchor.start, pane.windowSize)
60
+ return anchor
61
+ }
62
+
63
+ function fillPaths(pane, anchor, list) {
64
+ if (!anchor || !anchor.needPaths)
65
+ return anchor
66
+ var need = anchor.needPaths
67
+ for (var i = 0; i < need.length && i < list.length; i++) {
68
+ var path = String(list[i] || "")
69
+ var nm = pane.recentMode && pane.recentMode.length > 0 ? path.substring(1) : holdLeaf(path)
70
+ for (var m = 0; m < anchor.marks.length; m++) {
71
+ if (anchor.marks[m].index === need[i])
72
+ anchor.marks[m].name = nm
73
+ }
74
+ }
75
+ anchor.needPaths = null
76
+ if (pane.pathsPending && pane.pathsPending.kind === "anchor")
77
+ pane.pathsPending = null
78
+ return startList(pane, anchor)
79
+ }
80
+
81
+ function cursorView(pane) {
82
+ if (pane.shown === null || pane.shown === undefined)
83
+ return pane.cursorIndex
84
+ var at = pane.shown.indexOf(pane.cursorIndex)
85
+ return at < 0 ? 0 : at
86
+ }
87
+
88
+ // A renamed row that sorts outside the held window is asked of the backend, which alone knows where it landed; the anchor waits for the reply.
89
+ // Sample output: {"c":"locate","paths":["/d/zzz.txt"],"id":2147483649}
90
+ function locateRenamed(pane, anchor) {
91
+ if (!anchor.renamed || anchor.locateDone || pane.total <= 0)
92
+ return false
93
+ if (anchor.locateSent)
94
+ return true
95
+ if (!pane.backend || !pane.backend.send)
96
+ return false
97
+ pane.backend.send({ c: "locate", paths: [pane.join(pane.path, anchor.name)], id: anchor.locateId })
98
+ anchor.locateSent = true
99
+ return true
100
+ }
101
+
102
+ // The located renamed row takes the cursor and the selection and is revealed, with its own window asked for when the rows are not held.
103
+ function landRenamed(pane, anchor, found) {
104
+ if (found === undefined || !(found >= 0 && found < pane.total))
105
+ return
106
+ // The reveal clamps against the laid-out height, and the count that just passed through 0 has not laid out yet.
107
+ var area = viewport(pane)
108
+ if (area && typeof area.forceLayout === "function")
109
+ area.forceLayout()
110
+ pane.selectOnly(found)
111
+ if (!pane.rowFor(found))
112
+ pane.backend.window(found, pane.windowSize)
113
+ anchor.landed = true
114
+ }
115
+
116
+ function fillLocated(pane, anchor, matches) {
117
+ if (!anchor)
118
+ return null
119
+ anchor.locateDone = true
120
+ var byPath = {}
121
+ for (var i = 0; i < (matches || []).length; i++) {
122
+ var item = matches[i]
123
+ byPath[String(item.path || "")] = Number(item.index)
124
+ }
125
+ if (anchor.renamed)
126
+ landRenamed(pane, anchor, byPath[pane.join(pane.path, anchor.name)])
127
+ var pending = []
128
+ for (var m = 0; m < (anchor.marks || []).length; m++) {
129
+ var mark = anchor.marks[m]
130
+ var key = (mark.name !== null && pane.join) ? pane.join(pane.path, mark.name) : ""
131
+ var at = byPath[key]
132
+ if (at !== undefined && at >= 0)
133
+ anchor.kept.push(at)
134
+ else if (mark.name !== null)
135
+ pending.push(mark)
136
+ }
137
+ anchor.marks = pending
138
+ return anchor
139
+ }
140
+
141
+ function restoreView(pane, anchor, rowH) {
142
+ var area = viewport(pane)
143
+ if (!area || anchor.offset === undefined)
144
+ return
145
+ // The count that just passed through 0 has not laid out yet, and a clamp against its stale height pins the view to the top.
146
+ if (typeof area.forceLayout === "function")
147
+ area.forceLayout()
148
+ var rh = rowHeight(pane, rowH || anchor.rowH)
149
+ var originY = typeof area.originY === "number" ? area.originY : 0
150
+ var view = viewRow(pane)
151
+ var y = view * rh + originY - anchor.offset
152
+ // A restore past either end draws a blank strip, so clamp to the area's valid range when it names one.
153
+ if (typeof area.contentY === "number") {
154
+ if (typeof area.contentHeight === "number" && typeof area.height === "number") {
155
+ // The grid's top margin holds its resting contentY at originY less the margin, so the valid range takes both margins.
156
+ var lo = originY - (typeof area.topMargin === "number" ? area.topMargin : 0)
157
+ var hi = Math.max(lo, area.contentHeight - area.height + originY + (typeof area.bottomMargin === "number" ? area.bottomMargin : 0))
158
+ y = Math.max(lo, Math.min(hi, y))
159
+ }
160
+ area.contentY = y
161
+ }
162
+ }