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,154 @@
1
+ .pragma library
2
+
3
+ .import "BackReturn.js" as BackReturn
4
+ .import "DirSizes.js" as DirSizes
5
+ .import "Nav.js" as Nav
6
+ .import "Search.js" as Search
7
+ .import "Thumbs.js" as Thumbs
8
+
9
+ // Recent lists the history's paths under "/", newest first via mtime desc.
10
+ var OFF = ""
11
+ var RESULTS = "results"
12
+
13
+ // One refusal for both paste routes: the menu reaches past key dispatch, so it shares this.
14
+ function refusePaste(pane) {
15
+ if (pane.recentMode.length > 0) {
16
+ pane.message("Recent cannot take a paste. Open a folder first", false)
17
+ return true
18
+ }
19
+ return false
20
+ }
21
+
22
+ // Open keeps where the pane stood and asks the history's paths after the bounded jump; spanning mounts leaves fsinfo unknown.
23
+ function run(pane, paths, visits) {
24
+ // The history takes the place of a listing out; its answer is dropped by the folder it names, see ui/js/Swap.js onListed.
25
+ if (pane.listInFlight) {
26
+ pane.swap.stopWaiting()
27
+ BackReturn.dropOwed(pane)
28
+ }
29
+ // The standing order is kept for the way back, not the mtime it replaces.
30
+ if (pane.recentMode.length === 0 && pane.backend) {
31
+ pane.recentSortBy = pane.backend.sortBy
32
+ pane.recentSortDesc = pane.backend.sortDesc === true
33
+ }
34
+ if (pane.recentFrom.length === 0) {
35
+ pane.recentFrom = Nav.place(pane)
36
+ }
37
+ // A history replaces the standing listing, so a live session closes first.
38
+ Search.leaveWalk(pane)
39
+ // The paths this listing stands on, so a refresh with the rail hidden still re-asks them.
40
+ pane.recentPaths = paths || []
41
+ pane.recentVisits = visits || ({})
42
+ pane.listInFlight = true
43
+ pane.listedSeen = false
44
+ pane.path = "/"
45
+ pane.recentMode = RESULTS
46
+ pane.listingPath = "/"
47
+ pane.storageClass = ""
48
+ // Recent rows span filesystems, so the Tags row stays and the backend refuses each filesystem that cannot hold tags by name.
49
+ pane.canTag = true
50
+ pane.fsName = ""
51
+ pane.fsFree = 0
52
+ // Recent asks for no figures, so its empty slot is an answer.
53
+ pane.fsAnswered = true
54
+ var ask = {}
55
+ // A walk closed above keeps its rows and its strip until the history lands.
56
+ if (pane.searchLeaving === true) ask.holdWalk = true
57
+ Nav.holdOrForget(pane, ask)
58
+ pane.appliedListingPreferences = pane.listingPreferences
59
+ pane.backend.listPaths(pane.recentPaths, pane.windowSize)
60
+ // A deep anchored refresh asks its window only after the history's listpaths request.
61
+ var anchor = pane.wire ? pane.wire.anchor : null
62
+ if (anchor && anchor.path === pane.path && anchor.start > 0)
63
+ pane.backend.window(anchor.start, pane.windowSize)
64
+ pane.backend.sortBy = "mtime"
65
+ pane.backend.sortDesc = true
66
+ }
67
+
68
+ // Leaving Recent re-lists the folder it was opened over, pushing no history entry.
69
+ function close(pane) {
70
+ var back = pane.recentFrom.length > 0 ? pane.recentFrom : "/"
71
+ leave(pane)
72
+ pane.openWithoutHistory(back, { supersede: true })
73
+ }
74
+
75
+ // The order run() replaced, handed back before the folder re-lists.
76
+ function restoreSort(pane) {
77
+ if (pane.backend && typeof pane.recentSortBy === "string" && pane.recentSortBy.length > 0) {
78
+ pane.backend.sortBy = pane.recentSortBy
79
+ pane.backend.sortDesc = pane.recentSortDesc === true
80
+ }
81
+ pane.recentSortBy = ""
82
+ pane.recentSortDesc = false
83
+ }
84
+
85
+ // Every way out of Recent, a plain hop, Escape, a tab switch and reveal, leaves through this one step, so each restores the same order.
86
+ function leave(pane) {
87
+ if (pane.recentMode.length > 0) {
88
+ restoreSort(pane)
89
+ }
90
+ pane.recentMode = OFF
91
+ pane.recentFrom = ""
92
+ pane.recentPaths = []
93
+ pane.recentVisits = ({})
94
+ }
95
+
96
+ // Refresh re-reads history after an operation, never re-listing "/" over it; an unloaded sidebar re-asks the standing paths.
97
+ function refresh(pane, selectPath) {
98
+ // A re-ask of the history is no navigation, so it never takes the place of a listing out.
99
+ if (pane.listInFlight) {
100
+ pane.message("A directory is already loading.", false)
101
+ return
102
+ }
103
+ pane.pendingSelect = selectPath ? selectPath : ""
104
+ pane.pendingMenu = false
105
+ if (pane.sidebar) {
106
+ pane.sidebar.readRecent(pane)
107
+ return
108
+ }
109
+ run(pane, pane.recentPaths || [], pane.recentVisits)
110
+ }
111
+
112
+ // Only the held viewport rows gain a Used stamp; filesystem metadata remains intact.
113
+ function stampRows(pane, rows) {
114
+ if (pane.recentMode !== RESULTS) return rows
115
+ return rows.map(function (row) {
116
+ var visit = (pane.recentVisits || {})[pane.join(pane.path, row.n)]
117
+ return Object.assign({}, row, { used: visit === undefined ? null : visit })
118
+ })
119
+ }
120
+
121
+ // The folder a tab records on a history, "" elsewhere, so it lands where it stood.
122
+ function restingPath(pane) {
123
+ return pane.recentMode === RESULTS && pane.recentFrom.length > 0 ? pane.recentFrom : ""
124
+ }
125
+
126
+ // A tab switch drops the mode and says whether it dropped one.
127
+ function dropOverlay(pane) {
128
+ if (pane.recentMode.length === 0) {
129
+ return false
130
+ }
131
+ leave(pane)
132
+ return true
133
+ }
134
+
135
+ // o opens the row's own folder; Enter opens the file itself, so the two cannot disagree.
136
+ function reveal(pane) {
137
+ if (pane.listInFlight) {
138
+ pane.message("A directory is already loading.", false)
139
+ return
140
+ }
141
+ var row = pane.rowFor(pane.cursorIndex)
142
+ if (!row) {
143
+ return
144
+ }
145
+ var full = pane.join(pane.path, row.n)
146
+ var cut = full.lastIndexOf("/")
147
+ if (cut < 0) {
148
+ return
149
+ }
150
+ var dir = cut === 0 ? "/" : full.substring(0, cut)
151
+ leave(pane)
152
+ pane.pendingSelect = full
153
+ pane.openWithoutHistory(dir)
154
+ }
@@ -0,0 +1,396 @@
1
+ .pragma library
2
+ .import "Input.js" as Input
3
+ .import "Format.js" as Format
4
+
5
+ // The status bar's two search lines, each filled by Format.fill.
6
+ var WORDS = {
7
+ searching: "%1 found · Searching, %2 scanned",
8
+ landed: "%1 found in %2 s"
9
+ }
10
+
11
+ // Live subtree search: the strip opens a session, each keystroke sends its query, rows replace folder rows with no Enter.
12
+ var OFF = ""
13
+ var TYPING = "typing"
14
+ var RESULTS = "results"
15
+
16
+ // f opens the session with no rows touched; from Recent it leaves first, so the scope and rows are the real folder.
17
+ function start(root) {
18
+ if (root.searchMode === RESULTS) {
19
+ root.searchMode = TYPING
20
+ return
21
+ }
22
+ if ((root.searchMode || "").length > 0) {
23
+ return
24
+ }
25
+ // The cursor's file rides pendingSelect until Esc; mid-session landings skip it while the query stands.
26
+ var under = root.rowFor(root.cursorIndex)
27
+ root.pendingSelect = under && under.n ? root.join(root.path, under.n) : ""
28
+ var fromRecent = root.recentMode.length > 0
29
+ if (fromRecent) {
30
+ // The history rows keep their own base until the folder's rows, asked below, replace them.
31
+ root.searchBase = root.path
32
+ if (root.recentFrom.length > 0) {
33
+ root.path = root.recentFrom
34
+ }
35
+ root.recentMode = ""
36
+ root.recentFrom = ""
37
+ root.recentPaths = []
38
+ root.restoreRecentSort()
39
+ }
40
+ root.searchFrom = root.path
41
+ root.searchMode = TYPING
42
+ openScope(root)
43
+ // Recent's rows name paths under "/", so the folder's own rows are asked for now and Esc re-lists it.
44
+ if (fromRecent) {
45
+ sendQuery(root)
46
+ }
47
+ }
48
+
49
+ // The backend session for the pane's scope, with the drawn state of a fresh walk; f and a tag place both open through here.
50
+ function openScope(root) {
51
+ // Gens keep rising across sessions, so a closed session's late line never matches; the first answer parks on row 0.
52
+ root.searchOpenedGen = root.searchGen
53
+ if (root.swap) { root.swap.landedGen = 0; root.swap.answeredQuery = "" }
54
+ root.searchQuery = ""
55
+ root.searchRunning = true
56
+ root.searchCancelled = false
57
+ root.searchScanned = 0
58
+ root.backend.searchOpen(scopeRoot(root.path, root.home || "", root.searchHere), root.path, root.showHidden)
59
+ }
60
+
61
+ // Universal search: home when the pane stands inside it, else the pane's directory (backend has no home notion). Issue 30: here=true stays; abort()/reveal() leave it; nothing persists.
62
+ function scopeRoot(path, home, here) {
63
+ if (here === true) {
64
+ return path
65
+ }
66
+ if (home.length === 0) {
67
+ return path
68
+ }
69
+ if (path === home || String(path).indexOf(home + "/") === 0) {
70
+ return home
71
+ }
72
+ return path
73
+ }
74
+
75
+ // Keystrokes send rising gens with old rows held and the cursor standing for the landing to keep.
76
+ function sendQuery(root) {
77
+ root.searchGen += 1
78
+ root.searchKeyAt = Date.now()
79
+ root.searchFirstRowsAt = 0
80
+ root.searchSettledAt = 0
81
+ root.backend.searchQuery(root.searchQuery, root.searchGen, root.windowSize, root.sizeWish)
82
+ }
83
+
84
+ function extend(root, character) {
85
+ root.searchQuery += character
86
+ sendQuery(root)
87
+ }
88
+
89
+ // Backspace to empty sends "" and the folder rows come back, with the strip still open.
90
+ function shrink(root) {
91
+ if (root.searchQuery.length === 0) {
92
+ return
93
+ }
94
+ root.searchQuery = root.searchQuery.substring(0, root.searchQuery.length - 1)
95
+ sendQuery(root)
96
+ }
97
+
98
+ // Enter keeps the results and moves the keys to them, with no new request.
99
+ function commit(root) {
100
+ if ((root.searchQuery || "").length === 0) {
101
+ abort(root)
102
+ return
103
+ }
104
+ root.searchMode = RESULTS
105
+ }
106
+
107
+ // A leaving session takes no query from outside, so a caller that clears its own state first asks here.
108
+ function takesQuery(root) {
109
+ return root.searchLeaving !== true
110
+ }
111
+
112
+ // A query set from outside the strip lands as results over the Home scope, from a fresh session, a standing one or a cancelled one; a leaving session takes none.
113
+ // Sample input: (pane, "tag:Red") with the pane browsing, in Recent, or typing or showing results at either scope.
114
+ function openQuery(root, query) {
115
+ if (!takesQuery(root)) {
116
+ return
117
+ }
118
+ if ((root.searchMode || "").length === 0) {
119
+ root.searchHere = false
120
+ start(root)
121
+ } else if (root.searchCancelled === true) {
122
+ // A cancelled walk answers nothing more, so the place opens a fresh Home session with the drawn state of a new walk.
123
+ root.searchHere = false
124
+ openScope(root)
125
+ } else if (root.searchHere) {
126
+ // The old query is dropped first, so the rescope sends nothing and the new query is the one request.
127
+ root.searchQuery = ""
128
+ rescope(root)
129
+ }
130
+ root.searchQuery = query
131
+ sendQuery(root)
132
+ commit(root)
133
+ }
134
+
135
+ // Tab flips the scope, opens the new scope at once and re-sends the standing query.
136
+ function rescope(root) {
137
+ root.searchHere = !root.searchHere
138
+ var scope = scopeRoot(root.path, root.home || "", root.searchHere)
139
+ root.backend.searchOpen(scope, root.path, root.showHidden)
140
+ if ((root.searchQuery || "").length > 0) {
141
+ sendQuery(root)
142
+ }
143
+ }
144
+
145
+ // The first Esc while the walk runs stops it and keeps the results on screen.
146
+ function cancel(root) {
147
+ if (root.backend) {
148
+ root.backend.searchCancel()
149
+ }
150
+ }
151
+
152
+ // Esc leaves at once when empty or landed, or re-lists the folder it came from otherwise.
153
+ function abort(root) {
154
+ // A second Esc lands nowhere: the first already closed the backend and owes the landing.
155
+ if (root.searchLeaving === true) {
156
+ return
157
+ }
158
+ if (root.searchRunning && (root.searchQuery || "").length > 0) {
159
+ cancel(root)
160
+ return
161
+ }
162
+ var back = (root.searchFrom || "").length > 0 ? root.searchFrom : root.path
163
+ if (!owesRelist(root)) {
164
+ closeSession(root)
165
+ root.pendingSelect = ""
166
+ return
167
+ }
168
+ // The backend closes at the request; the drawn state waits for the folder to land.
169
+ leaveWalk(root)
170
+ root.openWithoutHistory(back, { holdWalk: true, relist: true })
171
+ }
172
+
173
+ // Any gen this session sent may have drawn rows the folder does not own, so leaving it re-lists the folder.
174
+ function owesRelist(root) {
175
+ return (root.searchMode || "").length > 0 && root.searchGen > (root.searchOpenedGen || 0)
176
+ }
177
+
178
+ // A live re-answer keeps the cursor's file when the new window still names it, else row 0.
179
+ function keptIndex(name, items, start) {
180
+ if (name === null || name === undefined) {
181
+ return start
182
+ }
183
+ for (var i = 0; i < items.length; i++) {
184
+ if (items[i].n === name) {
185
+ return start + i
186
+ }
187
+ }
188
+ return start
189
+ }
190
+
191
+ // A navigation drops the session without re-listing, so the next listing lands on clean state.
192
+ function leaveWalk(root) {
193
+ if ((root.searchMode || "").length === 0) {
194
+ return false
195
+ }
196
+ // One close per exit: tabs close the walk before the navigation closes it again.
197
+ if (root.searchLeaving === true) {
198
+ return true
199
+ }
200
+ // Results on screen keep their strip until the next listing lands; the backend closes now.
201
+ if (live(root)) {
202
+ if (root.backend) {
203
+ root.backend.searchClose()
204
+ }
205
+ root.searchLeaving = true
206
+ return true
207
+ }
208
+ return closeSession(root)
209
+ }
210
+
211
+ // Closing the session is one searchclose; a list closes it too, so a doubled close is silence.
212
+ function closeSession(root) {
213
+ if ((root.searchMode || "").length === 0) {
214
+ return false
215
+ }
216
+ if (root.backend) {
217
+ root.backend.searchClose()
218
+ }
219
+ root.searchMode = OFF
220
+ root.searchQuery = ""
221
+ root.searchRunning = false
222
+ root.searchCancelled = false
223
+ root.searchScanned = 0
224
+ root.searchFrom = ""
225
+ root.searchBase = ""
226
+ root.listingState = listingState(root, root.total)
227
+ root.stateMessage = ""
228
+ return true
229
+ }
230
+
231
+ // A terminal line answers only the newest gen this session sent; older gens drop with anything gen-less.
232
+ function accepts(root, gen) {
233
+ return (root.searchMode || "").length > 0 && gen === root.searchGen && gen > (root.searchOpenedGen || 0)
234
+ }
235
+
236
+ // An exit landing clears the drawn search state in the same turn its rows do; the backend already closed.
237
+ function closeLeaving(root) {
238
+ root.searchLeaving = false
239
+ root.searchMode = OFF
240
+ root.searchQuery = ""
241
+ root.searchRunning = false
242
+ root.searchCancelled = false
243
+ root.searchScanned = 0
244
+ root.searchFrom = ""
245
+ root.searchBase = ""
246
+ }
247
+
248
+ // The answered counts land; the first window rides its own rows line, so nothing is asked for.
249
+ function searched(root, total, scanned, ms, cancelled, gen, walking) {
250
+ // Answers for a session closed at its exit's request never land.
251
+ if (root.searchLeaving === true) {
252
+ return
253
+ }
254
+ if (!accepts(root, gen)) {
255
+ return
256
+ }
257
+ root.searchRunning = walking === true
258
+ root.searchCancelled = cancelled
259
+ if (walking !== true) {
260
+ root.searchSettledAt = Date.now()
261
+ }
262
+ root.total = total
263
+ root.searchScanned = scanned
264
+ root.searchMs = ms
265
+ root.listingState = listingState(root, total)
266
+ root.listArea.restartSettle()
267
+ }
268
+
269
+ // Query-line keys: enter keeps, escape leaves, tab rescopes, printables extend; cursor motions run through Focus.
270
+ function typeKey(event, root) {
271
+ // Keys struck while the exit is out wait for the folder, the way any held listing holds them.
272
+ if (root.searchLeaving === true) {
273
+ return true
274
+ }
275
+ // Ctrl+V and Shift+Insert extend the query by the clipboard's first line, one generation (issue 287).
276
+ if (Input.isPasteKey(event))
277
+ return Input.pasteInto(root, function (text) { extend(root, text) })
278
+ if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
279
+ commit(root)
280
+ return true
281
+ }
282
+ if (event.key === Qt.Key_Escape) {
283
+ abort(root)
284
+ return true
285
+ }
286
+ if (event.key === Qt.Key_Backspace) {
287
+ shrink(root)
288
+ return true
289
+ }
290
+ // Issue 30's control: tab flips the scope between the whole home directory and the one the pane
291
+ // is standing in. It is the only writer, and nothing resets it, so the flip holds for the window;
292
+ // ui/SearchStrip.qml draws "in <scope>" under the caret, so every search says which one it took.
293
+ if (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab) {
294
+ rescope(root)
295
+ return true
296
+ }
297
+ if (Input.isPrintable(event.text)) {
298
+ extend(root, event.text)
299
+ return true
300
+ }
301
+ return true
302
+ }
303
+
304
+ // o on a result opens the directory that holds it and puts the cursor on the row, the design's reveal.
305
+ function reveal(root) {
306
+ var row = root.rowFor(root.cursorIndex)
307
+ if (!row) {
308
+ return
309
+ }
310
+ var full = root.join(baseOf(root), row.n)
311
+ var cut = full.lastIndexOf("/")
312
+ if (cut <= 0) {
313
+ return
314
+ }
315
+ root.recentMode = ""
316
+ root.recentFrom = ""
317
+ root.recentPaths = []
318
+ // Through the pane: importing RecentMode.js here would close a Nav import cycle.
319
+ root.restoreRecentSort()
320
+ root.pendingSelect = full
321
+ root.open(full.substring(0, cut))
322
+ }
323
+
324
+ // Read-only stopwatch for the newest gen; per-keystroke latency is measured off it.
325
+ function stamps(root) {
326
+ return { gen: root.searchGen, keyAt: root.searchKeyAt || 0,
327
+ firstRowsAt: root.searchFirstRowsAt || 0, settledAt: root.searchSettledAt || 0 }
328
+ }
329
+
330
+ // Rows on screen belong to a standing query: results in, folder out, whatever mode holds them.
331
+ function live(root) {
332
+ return (root.searchMode || "").length > 0 && (root.searchQuery || "").length > 0
333
+ }
334
+
335
+ // What activating a row means right now. On a search result it takes
336
+ // you to the file instead of opening it; the canvas keeps enter on open and o on reveal, so it is
337
+ // only the pointer that asks this. ui/js/Tap.js is the one caller.
338
+ function activateAction(root) {
339
+ return live(root) ? "reveal" : "open"
340
+ }
341
+
342
+ // A walk with no matches yet is still working, so the list area keeps the crawl rather than
343
+ // flashing the empty state at every directory that happens to hold nothing.
344
+ function listingState(root, total) {
345
+ if (total > 0) {
346
+ return "ready"
347
+ }
348
+ return root.searchRunning ? "loading" : "empty"
349
+ }
350
+
351
+ // Strip note as whole templates: the count with its crawl while the walk runs, alone landed.
352
+ var NOTE = { walking: "%1 found · still scanning", landed: "%1 found" }
353
+ function note(total, running, empty) {
354
+ if (empty === true) {
355
+ return ""
356
+ }
357
+ if (running) {
358
+ return NOTE.walking.replace("%1", Format.count(total))
359
+ }
360
+ return NOTE.landed.replace("%1", Format.count(total))
361
+ }
362
+
363
+ // Empty-state caption for the query the shown answer is for; the function replacer keeps "$&" and "$$" in it literal.
364
+ function nothingMatches(answered) {
365
+ if (answered.length === 0) {
366
+ return ""
367
+ }
368
+ return "Nothing matches %1".replace("%1", function () { return answered })
369
+ }
370
+
371
+ // Strip right edge as whole templates: walking esc stops the walk, any other esc leaves.
372
+ var WAY = { walking: "esc cancels, then returns", done: "esc returns" }
373
+ function wayOut(running, empty) {
374
+ if (running && empty !== true) {
375
+ return WAY.walking
376
+ }
377
+ return WAY.done
378
+ }
379
+
380
+ // Row facilities join a row against the listing's base, the scope or the folder, never pane.path mid-session.
381
+ function baseOf(root) {
382
+ return (root.searchBase || "").length > 0 ? root.searchBase : root.path
383
+ }
384
+
385
+ // The rule itself lives in Format.tilde, because the window chrome draws a path through the same one.
386
+ function scope(path, home) {
387
+ return Format.tilde(path, home)
388
+ }
389
+
390
+ // The status bar's own left half while a search is up, the two lines the canvas draws.
391
+ function statusLine(running, total, scanned, ms) {
392
+ if (running) {
393
+ return Format.fill(WORDS.searching, Format.count(total), Format.count(scanned))
394
+ }
395
+ return Format.fill(WORDS.landed, Format.count(total), (ms / 1000).toFixed(1))
396
+ }