@bojackduy/opencode-telescope 0.1.20 → 0.1.21
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.
- package/package.json +1 -1
- package/telescope.tsx +297 -86
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@bojackduy/opencode-telescope",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.21",
|
|
5
5
|
"description": "Fuzzy search across all OpenCode conversations — grep session and chat history, find code snippets, and jump to any chat instantly",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
package/telescope.tsx
CHANGED
|
@@ -53,7 +53,7 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
53
53
|
const MIN_RECENT_BATCH_SIZE = 15
|
|
54
54
|
const RESULT_OVERSCAN_MULTIPLIER = 2
|
|
55
55
|
const RESULT_BATCH_VIEWPORTS = 4
|
|
56
|
-
const RESULT_PREFETCH_VIEWPORTS =
|
|
56
|
+
const RESULT_PREFETCH_VIEWPORTS = 5
|
|
57
57
|
const RESULT_CACHE_BEHIND_VIEWPORTS = 6
|
|
58
58
|
const INITIAL_PREVIEW_BEFORE = 20
|
|
59
59
|
const INITIAL_PREVIEW_AFTER = 30
|
|
@@ -93,10 +93,23 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
93
93
|
let resultNavigationStarted = false
|
|
94
94
|
let resultPrefetchTimer: ReturnType<typeof setTimeout> | undefined
|
|
95
95
|
let resultPreviousTimer: ReturnType<typeof setTimeout> | undefined
|
|
96
|
+
type PreviewBeforeIntent = "passive-prefetch" | "explicit-scroll"
|
|
97
|
+
type PendingPreviewBefore = {
|
|
98
|
+
id: number
|
|
99
|
+
previousContentHeight: number
|
|
100
|
+
previousScrollTop: number
|
|
101
|
+
requestedAmount: number
|
|
102
|
+
intent: PreviewBeforeIntent
|
|
103
|
+
visibleLoad: boolean
|
|
104
|
+
}
|
|
105
|
+
let previewBeforeLoadID = 0
|
|
96
106
|
let previewBeforeTimer: ReturnType<typeof setTimeout> | undefined
|
|
97
107
|
let previewAfterTimer: ReturnType<typeof setTimeout> | undefined
|
|
98
|
-
let pendingPreviewBefore:
|
|
108
|
+
let pendingPreviewBefore: PendingPreviewBefore | undefined
|
|
99
109
|
let pendingPreviewAfterVisible = false
|
|
110
|
+
let previewBeforeAdjusting = false
|
|
111
|
+
let pendingCoalescedExplicit: { requestedAmount: number } | undefined
|
|
112
|
+
let pendingPreviewAnchorCorrection: { anchorPartID: string; visualOffset: number; createdAt: number; expiresAt: number } | undefined
|
|
100
113
|
const previewMeasuredHeights = new Map<string, number>()
|
|
101
114
|
const cancelPreviewPrefetch = () => {
|
|
102
115
|
if (previewBeforeTimer) clearTimeout(previewBeforeTimer)
|
|
@@ -105,6 +118,9 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
105
118
|
previewAfterTimer = undefined
|
|
106
119
|
pendingPreviewBefore = undefined
|
|
107
120
|
pendingPreviewAfterVisible = false
|
|
121
|
+
previewBeforeAdjusting = false
|
|
122
|
+
pendingCoalescedExplicit = undefined
|
|
123
|
+
pendingPreviewAnchorCorrection = undefined
|
|
108
124
|
setPrefetchingPreviewBefore(false)
|
|
109
125
|
setPrefetchingPreviewAfter(false)
|
|
110
126
|
setLoadingPreviewMore(false)
|
|
@@ -151,6 +167,12 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
151
167
|
return { start: base + cachedStart, end: base + cachedEnd, items: cached.slice(cachedStart, cachedEnd) }
|
|
152
168
|
})
|
|
153
169
|
|
|
170
|
+
const resultTopSpacerHeight = () =>
|
|
171
|
+
Math.max(0, resultRenderWindow().start - resultBaseOffset()) * resultRowHeight()
|
|
172
|
+
|
|
173
|
+
const resultBottomSpacerHeight = () =>
|
|
174
|
+
Math.max(0, resultBaseOffset() + results().length - resultRenderWindow().end) * resultRowHeight()
|
|
175
|
+
|
|
154
176
|
let lastResultRenderWindow = ""
|
|
155
177
|
createEffect(() => {
|
|
156
178
|
const window = resultRenderWindow()
|
|
@@ -446,6 +468,31 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
446
468
|
onCleanup(() => clearTimeout(timer))
|
|
447
469
|
})
|
|
448
470
|
|
|
471
|
+
createEffect(() => {
|
|
472
|
+
if (!resultNavigationStarted || !hasMore()) return
|
|
473
|
+
const interval = setInterval(() => {
|
|
474
|
+
if (!hasMore() || loadingMore() || loadingPreviousResults() || prefetchingResults() || busy() || loading()) return
|
|
475
|
+
const scroll = resultScroll
|
|
476
|
+
if (!scroll) return
|
|
477
|
+
const children = scroll.getChildren()
|
|
478
|
+
if (!children || children.length === 0) return
|
|
479
|
+
const lastChild = children[children.length - 1] as { y: number; height: number }
|
|
480
|
+
const contentHeight = lastChild.y + lastChild.height
|
|
481
|
+
const scrollThreshold = Math.max(2, Math.floor(scroll.height * RESULT_PREFETCH_VIEWPORTS))
|
|
482
|
+
if (scroll.scrollTop + scroll.height >= contentHeight - scrollThreshold) {
|
|
483
|
+
debug.log("results:scroll-prefetch", {
|
|
484
|
+
scrollTop: scroll.scrollTop,
|
|
485
|
+
height: scroll.height,
|
|
486
|
+
contentHeight,
|
|
487
|
+
scrollThreshold,
|
|
488
|
+
rowsAhead: resultPrefetchState().rowsAhead,
|
|
489
|
+
})
|
|
490
|
+
scheduleResultPrefetch(false)
|
|
491
|
+
}
|
|
492
|
+
}, 200)
|
|
493
|
+
onCleanup(() => clearInterval(interval))
|
|
494
|
+
})
|
|
495
|
+
|
|
449
496
|
const estimatePreviewPartHeight = (part: ConversationPreviewPart) => {
|
|
450
497
|
if (part.type === "tool") {
|
|
451
498
|
if (!part.target) return 2
|
|
@@ -459,15 +506,31 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
459
506
|
|
|
460
507
|
const previewPartHeight = (part: ConversationPreviewPart) => previewMeasuredHeights.get(part.id) ?? estimatePreviewPartHeight(part)
|
|
461
508
|
|
|
462
|
-
const
|
|
463
|
-
previewHeightVersion()
|
|
509
|
+
const buildPreviewLayout = (parts: ConversationPreviewPart[]) => {
|
|
464
510
|
let offset = 0
|
|
465
|
-
return
|
|
511
|
+
return parts.map((part) => {
|
|
466
512
|
const height = previewPartHeight(part)
|
|
467
513
|
const row = { part, top: offset, bottom: offset + height, height }
|
|
468
514
|
offset += height
|
|
469
515
|
return row
|
|
470
516
|
})
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const previewWindowForLayout = (layout: ReturnType<typeof buildPreviewLayout>, scrollTop: number, viewportHeight: number) => {
|
|
520
|
+
if (layout.length === 0) return { start: 0, end: 0 }
|
|
521
|
+
const overscan = Math.max(viewportHeight * 2, 40)
|
|
522
|
+
const from = Math.max(0, scrollTop - overscan)
|
|
523
|
+
const to = scrollTop + viewportHeight + overscan
|
|
524
|
+
const foundStart = layout.findIndex((row) => row.bottom >= from)
|
|
525
|
+
const start = foundStart === -1 ? Math.max(0, layout.length - 1) : Math.max(0, foundStart)
|
|
526
|
+
const foundEnd = layout.findIndex((row) => row.top > to)
|
|
527
|
+
const end = foundEnd === -1 ? layout.length : foundEnd
|
|
528
|
+
return { start, end: Math.min(layout.length, Math.max(start + 1, end)) }
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const previewVirtualLayout = createMemo(() => {
|
|
532
|
+
previewHeightVersion()
|
|
533
|
+
return buildPreviewLayout(previewParts())
|
|
471
534
|
})
|
|
472
535
|
|
|
473
536
|
const previewContentHeight = () => previewVirtualLayout().at(-1)?.bottom ?? 0
|
|
@@ -486,14 +549,7 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
486
549
|
return
|
|
487
550
|
}
|
|
488
551
|
|
|
489
|
-
const
|
|
490
|
-
const from = Math.max(0, scroll.scrollTop - overscan)
|
|
491
|
-
const to = scroll.scrollTop + scroll.height + overscan
|
|
492
|
-
const foundStart = layout.findIndex((row) => row.bottom >= from)
|
|
493
|
-
const start = foundStart === -1 ? Math.max(0, layout.length - 1) : Math.max(0, foundStart)
|
|
494
|
-
const foundEnd = layout.findIndex((row) => row.top > to)
|
|
495
|
-
const end = foundEnd === -1 ? layout.length : foundEnd
|
|
496
|
-
const next = { start, end: Math.min(layout.length, Math.max(start + 1, end)) }
|
|
552
|
+
const next = previewWindowForLayout(layout, scroll.scrollTop, scroll.height)
|
|
497
553
|
const current = previewWindow()
|
|
498
554
|
if (current.start !== next.start || current.end !== next.end) setPreviewWindow(next)
|
|
499
555
|
}
|
|
@@ -558,6 +614,29 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
558
614
|
totalMeasured: previewMeasuredHeights.size,
|
|
559
615
|
window: previewWindow(),
|
|
560
616
|
})
|
|
617
|
+
const anchorCorrection = pendingPreviewAnchorCorrection
|
|
618
|
+
if (anchorCorrection && scroll) {
|
|
619
|
+
const now = Date.now()
|
|
620
|
+
const shouldCorrect = now <= anchorCorrection.expiresAt && lastPreviewScrollKeyMs <= anchorCorrection.createdAt
|
|
621
|
+
if (shouldCorrect) {
|
|
622
|
+
const nextLayout = buildPreviewLayout(previewParts())
|
|
623
|
+
const anchor = nextLayout.find((row) => row.part.id === anchorCorrection.anchorPartID)
|
|
624
|
+
if (anchor) {
|
|
625
|
+
const correctedScrollTop = Math.max(0, anchor.top + anchorCorrection.visualOffset)
|
|
626
|
+
const correctedWindow = previewWindowForLayout(nextLayout, correctedScrollTop, scroll.height)
|
|
627
|
+
setPreviewWindow(correctedWindow)
|
|
628
|
+
scroll.scrollTo(correctedScrollTop)
|
|
629
|
+
debug.log("preview:measure:anchor-correct", {
|
|
630
|
+
anchorPartID: anchorCorrection.anchorPartID,
|
|
631
|
+
visualOffset: anchorCorrection.visualOffset,
|
|
632
|
+
correctedScrollTop,
|
|
633
|
+
window: correctedWindow,
|
|
634
|
+
})
|
|
635
|
+
}
|
|
636
|
+
} else {
|
|
637
|
+
pendingPreviewAnchorCorrection = undefined
|
|
638
|
+
}
|
|
639
|
+
}
|
|
561
640
|
setPreviewHeightVersion((value) => value + 1)
|
|
562
641
|
setTimeout(updatePreviewWindow, 1)
|
|
563
642
|
}
|
|
@@ -576,75 +655,139 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
576
655
|
onCleanup(() => clearTimeout(timer))
|
|
577
656
|
})
|
|
578
657
|
|
|
579
|
-
const loadPreviewBefore = (
|
|
658
|
+
const loadPreviewBefore = (pending: PendingPreviewBefore) => {
|
|
580
659
|
const item = selectedResult()
|
|
581
660
|
const first = previewParts()[0]
|
|
582
661
|
if (!item || !first || loadingPreviewMore() || prefetchingPreviewBefore()) {
|
|
583
662
|
debug.log("preview:load-before:skip", {
|
|
663
|
+
loadID: pending.id,
|
|
584
664
|
reason: !item ? "no-item" : !first ? "no-first-part" : loadingPreviewMore() ? "loading-preview-more" : "prefetching-before",
|
|
585
|
-
|
|
586
|
-
preserveScroll,
|
|
587
|
-
visibleLoad,
|
|
665
|
+
pending,
|
|
588
666
|
state: previewScrollState(),
|
|
589
667
|
})
|
|
590
668
|
return
|
|
591
669
|
}
|
|
592
670
|
const beforeState = previewScrollState()
|
|
593
|
-
|
|
671
|
+
const beforeLayout = previewVirtualLayout()
|
|
672
|
+
const scrollTop = previewScroll?.scrollTop ?? pending.previousScrollTop
|
|
673
|
+
const anchor = beforeLayout.find((row) => row.bottom >= scrollTop) ?? beforeLayout[0]
|
|
674
|
+
const anchorPartID = anchor?.part.id ?? first.id
|
|
675
|
+
const offsetInAnchor = anchor ? scrollTop - anchor.top : 0
|
|
676
|
+
pending.visibleLoad ? setLoadingPreviewMore(true) : setPrefetchingPreviewBefore(true)
|
|
594
677
|
debug.time("preview:load-before")
|
|
595
678
|
try {
|
|
596
679
|
debug.log("preview:load-before:start", {
|
|
597
|
-
|
|
680
|
+
loadID: pending.id,
|
|
681
|
+
intent: pending.intent,
|
|
598
682
|
cursor: { id: first.id, timeCreated: first.timeCreated },
|
|
599
|
-
previousContentHeight,
|
|
600
|
-
|
|
601
|
-
|
|
683
|
+
previousContentHeight: pending.previousContentHeight,
|
|
684
|
+
previousScrollTop: pending.previousScrollTop,
|
|
685
|
+
anchorPartID,
|
|
686
|
+
offsetInAnchor,
|
|
687
|
+
requestedAmount: pending.requestedAmount,
|
|
688
|
+
visibleLoad: pending.visibleLoad,
|
|
602
689
|
state: beforeState,
|
|
603
690
|
})
|
|
604
691
|
const page = loadConversationBefore(item, { id: first.id, timeCreated: first.timeCreated }, { limit: PREVIEW_PAGE_SIZE, dbPath: dbPath() })
|
|
605
|
-
debug.log("preview:load-before", {
|
|
606
|
-
|
|
692
|
+
debug.log("preview:load-before:query-done", {
|
|
693
|
+
loadID: pending.id,
|
|
607
694
|
added: page.parts.length,
|
|
608
695
|
hasMoreBefore: page.hasMoreBefore,
|
|
609
|
-
preserveScroll,
|
|
610
|
-
visibleLoad,
|
|
611
696
|
first: page.parts[0]?.id,
|
|
612
697
|
last: page.parts.at(-1)?.id,
|
|
613
698
|
})
|
|
614
699
|
if (page.parts.length > 0) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
700
|
+
const currentParts = previewParts()
|
|
701
|
+
const nextParts = [...page.parts, ...currentParts]
|
|
702
|
+
const nextLayout = buildPreviewLayout(nextParts)
|
|
703
|
+
const anchorPredicted = nextLayout.find((row) => row.part.id === anchorPartID)
|
|
704
|
+
const anchorPredictedTop = anchorPredicted?.top ?? 0
|
|
705
|
+
const visualOffset = offsetInAnchor - (pending.intent === "explicit-scroll" ? pending.requestedAmount : 0)
|
|
706
|
+
const targetScrollTop = pending.intent === "explicit-scroll"
|
|
707
|
+
? Math.max(0, anchorPredictedTop + offsetInAnchor - pending.requestedAmount)
|
|
708
|
+
: anchorPredictedTop + offsetInAnchor
|
|
709
|
+
const nextWindow = previewWindowForLayout(nextLayout, targetScrollTop, previewScroll?.height ?? Math.max(8, height() - 7))
|
|
710
|
+
|
|
711
|
+
solidBatch(() => {
|
|
712
|
+
setPreviewParts(nextParts)
|
|
713
|
+
setPreviewWindow(nextWindow)
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
previewScroll?.scrollTo(targetScrollTop)
|
|
717
|
+
const anchorCorrectionCreatedAt = Date.now()
|
|
718
|
+
pendingPreviewAnchorCorrection = {
|
|
719
|
+
anchorPartID,
|
|
720
|
+
visualOffset,
|
|
721
|
+
createdAt: anchorCorrectionCreatedAt,
|
|
722
|
+
expiresAt: anchorCorrectionCreatedAt + 300,
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
debug.log("preview:load-before:commit", {
|
|
726
|
+
loadID: pending.id,
|
|
727
|
+
anchorPartID,
|
|
728
|
+
anchorPredictedTop,
|
|
729
|
+
offsetInAnchor,
|
|
730
|
+
visualOffset,
|
|
731
|
+
targetScrollTop,
|
|
732
|
+
window: nextWindow,
|
|
733
|
+
intent: pending.intent,
|
|
734
|
+
newParts: page.parts.length,
|
|
735
|
+
totalParts: nextParts.length,
|
|
736
|
+
})
|
|
737
|
+
|
|
738
|
+
previewBeforeAdjusting = true
|
|
739
|
+
setTimeout(() => {
|
|
740
|
+
const newLayout = previewVirtualLayout()
|
|
741
|
+
const anchorNew = newLayout.find(e => e.part.id === anchorPartID)
|
|
742
|
+
const actualAnchorTop = anchorNew?.top ?? anchorPredictedTop
|
|
743
|
+
const drift = actualAnchorTop - anchorPredictedTop
|
|
744
|
+
|
|
745
|
+
if (Math.abs(drift) >= 1 && previewScroll) {
|
|
746
|
+
const correctedScrollTop = targetScrollTop + drift
|
|
747
|
+
const correctedWindow = previewWindowForLayout(newLayout, correctedScrollTop, previewScroll.height)
|
|
748
|
+
setPreviewWindow(correctedWindow)
|
|
749
|
+
previewScroll.scrollTo(correctedScrollTop)
|
|
750
|
+
debug.log("preview:load-before:drift-correct", {
|
|
751
|
+
loadID: pending.id,
|
|
752
|
+
drift,
|
|
753
|
+
anchorPredictedTop,
|
|
754
|
+
actualAnchorTop,
|
|
755
|
+
correctedScrollTop,
|
|
756
|
+
window: correctedWindow,
|
|
629
757
|
})
|
|
630
|
-
}
|
|
631
|
-
} else {
|
|
632
|
-
setTimeout(() => {
|
|
758
|
+
} else {
|
|
633
759
|
updatePreviewWindow()
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
previewBeforeAdjusting = false
|
|
763
|
+
|
|
764
|
+
const coalesced = pendingCoalescedExplicit
|
|
765
|
+
if (coalesced) {
|
|
766
|
+
pendingCoalescedExplicit = undefined
|
|
767
|
+
const scroll = previewScroll
|
|
768
|
+
if (scroll) {
|
|
769
|
+
debug.log("preview:before:coalesce-replay", {
|
|
770
|
+
requestedAmount: coalesced.requestedAmount,
|
|
771
|
+
state: previewScrollState(),
|
|
772
|
+
})
|
|
773
|
+
schedulePreviewBefore({
|
|
774
|
+
intent: "explicit-scroll",
|
|
775
|
+
visibleLoad: true,
|
|
776
|
+
requestedAmount: coalesced.requestedAmount,
|
|
777
|
+
previousScrollTop: scroll.scrollTop,
|
|
778
|
+
})
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}, 1)
|
|
641
782
|
}
|
|
642
783
|
setHasMorePreviewBefore(page.hasMoreBefore)
|
|
643
784
|
} catch (err) {
|
|
644
785
|
debug.log("preview:load-before:error", err instanceof Error ? err.message : String(err))
|
|
786
|
+
previewBeforeAdjusting = false
|
|
645
787
|
} finally {
|
|
646
788
|
debug.timeEnd("preview:load-before")
|
|
647
|
-
|
|
789
|
+
lastBeforeLoadMs = Date.now()
|
|
790
|
+
pending.visibleLoad ? setLoadingPreviewMore(false) : setPrefetchingPreviewBefore(false)
|
|
648
791
|
}
|
|
649
792
|
}
|
|
650
793
|
|
|
@@ -690,46 +833,104 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
690
833
|
}
|
|
691
834
|
}
|
|
692
835
|
|
|
693
|
-
const schedulePreviewBefore = (
|
|
836
|
+
const schedulePreviewBefore = (opts: {
|
|
837
|
+
intent: PreviewBeforeIntent
|
|
838
|
+
visibleLoad?: boolean
|
|
839
|
+
previousContentHeight?: number
|
|
840
|
+
requestedAmount?: number
|
|
841
|
+
previousScrollTop?: number
|
|
842
|
+
}) => {
|
|
843
|
+
const id = ++previewBeforeLoadID
|
|
844
|
+
const previousContentHeight = opts.previousContentHeight ?? previewContentHeight()
|
|
845
|
+
const previousScrollTop = opts.previousScrollTop ?? previewScroll?.scrollTop ?? 0
|
|
846
|
+
const requestedAmount = opts.requestedAmount ?? 0
|
|
847
|
+
const visibleLoad = opts.visibleLoad ?? false
|
|
848
|
+
const source = opts.intent === "explicit-scroll" ? "key" : "edge"
|
|
694
849
|
if (!hasMorePreviewBefore() || loadingPreviewMore() || prefetchingPreviewBefore()) {
|
|
695
|
-
debug.log("preview:
|
|
696
|
-
|
|
850
|
+
debug.log("preview:before:schedule-skip", {
|
|
851
|
+
loadID: id,
|
|
852
|
+
reason: !hasMorePreviewBefore() ? "no-more-before" : loadingPreviewMore() ? "loading" : "prefetching",
|
|
853
|
+
source,
|
|
854
|
+
intent: opts.intent,
|
|
697
855
|
previousContentHeight,
|
|
698
|
-
|
|
856
|
+
previousScrollTop,
|
|
857
|
+
requestedAmount,
|
|
699
858
|
visibleLoad,
|
|
700
859
|
state: previewScrollState(),
|
|
701
860
|
})
|
|
702
861
|
return
|
|
703
862
|
}
|
|
863
|
+
if (previewBeforeAdjusting) {
|
|
864
|
+
if (opts.intent === "explicit-scroll") {
|
|
865
|
+
pendingCoalescedExplicit = { requestedAmount }
|
|
866
|
+
debug.log("preview:before:coalesce-explicit", {
|
|
867
|
+
loadID: id,
|
|
868
|
+
requestedAmount,
|
|
869
|
+
state: previewScrollState(),
|
|
870
|
+
})
|
|
871
|
+
} else {
|
|
872
|
+
debug.log("preview:before:schedule-skip", {
|
|
873
|
+
loadID: id,
|
|
874
|
+
reason: "adjusting",
|
|
875
|
+
source,
|
|
876
|
+
intent: opts.intent,
|
|
877
|
+
previousContentHeight,
|
|
878
|
+
previousScrollTop,
|
|
879
|
+
requestedAmount,
|
|
880
|
+
visibleLoad,
|
|
881
|
+
state: previewScrollState(),
|
|
882
|
+
})
|
|
883
|
+
}
|
|
884
|
+
return
|
|
885
|
+
}
|
|
704
886
|
if (previewBeforeTimer) {
|
|
705
887
|
if (pendingPreviewBefore) {
|
|
706
888
|
const previousPending = { ...pendingPreviewBefore }
|
|
707
|
-
pendingPreviewBefore.preserveScroll = pendingPreviewBefore.preserveScroll && preserveScroll
|
|
708
889
|
pendingPreviewBefore.visibleLoad = pendingPreviewBefore.visibleLoad || visibleLoad
|
|
709
|
-
|
|
890
|
+
if (opts.intent === "explicit-scroll" && pendingPreviewBefore.intent === "passive-prefetch") {
|
|
891
|
+
pendingPreviewBefore.intent = "explicit-scroll"
|
|
892
|
+
pendingPreviewBefore.requestedAmount = requestedAmount
|
|
893
|
+
pendingPreviewBefore.previousScrollTop = previousScrollTop
|
|
894
|
+
}
|
|
895
|
+
debug.log("preview:before:schedule-merge", {
|
|
896
|
+
loadID: id,
|
|
710
897
|
previousPending,
|
|
711
898
|
nextPending: pendingPreviewBefore,
|
|
712
|
-
requested: { previousContentHeight,
|
|
713
|
-
state: previewScrollState(),
|
|
899
|
+
requested: { source, intent: opts.intent, previousContentHeight, previousScrollTop, requestedAmount, visibleLoad },
|
|
714
900
|
})
|
|
715
901
|
} else {
|
|
716
|
-
debug.log("preview:
|
|
902
|
+
debug.log("preview:before:schedule-skip", {
|
|
903
|
+
loadID: id,
|
|
717
904
|
reason: "timer-already-set",
|
|
905
|
+
source,
|
|
906
|
+
intent: opts.intent,
|
|
718
907
|
previousContentHeight,
|
|
719
|
-
|
|
908
|
+
previousScrollTop,
|
|
909
|
+
requestedAmount,
|
|
720
910
|
visibleLoad,
|
|
721
911
|
state: previewScrollState(),
|
|
722
912
|
})
|
|
723
913
|
}
|
|
724
914
|
return
|
|
725
915
|
}
|
|
726
|
-
pendingPreviewBefore = { previousContentHeight,
|
|
727
|
-
debug.log("preview:
|
|
916
|
+
pendingPreviewBefore = { id, previousContentHeight, previousScrollTop, requestedAmount, intent: opts.intent, visibleLoad }
|
|
917
|
+
debug.log("preview:before:schedule", {
|
|
918
|
+
loadID: id,
|
|
919
|
+
source,
|
|
920
|
+
intent: opts.intent,
|
|
921
|
+
previousContentHeight,
|
|
922
|
+
previousScrollTop,
|
|
923
|
+
requestedAmount,
|
|
924
|
+
visibleLoad,
|
|
925
|
+
firstPart: previewParts()[0]?.id,
|
|
926
|
+
partCount: previewParts().length,
|
|
927
|
+
state: previewScrollState(),
|
|
928
|
+
})
|
|
728
929
|
previewBeforeTimer = setTimeout(() => {
|
|
729
930
|
const pending = pendingPreviewBefore
|
|
730
931
|
previewBeforeTimer = undefined
|
|
731
932
|
pendingPreviewBefore = undefined
|
|
732
|
-
if (pending) loadPreviewBefore(pending
|
|
933
|
+
if (pending) loadPreviewBefore(pending)
|
|
733
934
|
}, 1)
|
|
734
935
|
}
|
|
735
936
|
|
|
@@ -815,11 +1016,14 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
815
1016
|
debug.timeEnd("preview:load")
|
|
816
1017
|
})
|
|
817
1018
|
|
|
1019
|
+
let lastBeforeLoadMs = 0
|
|
1020
|
+
let lastPreviewScrollKeyMs = 0
|
|
818
1021
|
createEffect(() => {
|
|
819
1022
|
const item = selectedResult()
|
|
820
1023
|
if (!item) return
|
|
821
1024
|
const interval = setInterval(() => {
|
|
822
1025
|
if (loadingPreviewMore() || prefetchingPreviewBefore() || prefetchingPreviewAfter()) return
|
|
1026
|
+
if (previewBeforeAdjusting) return
|
|
823
1027
|
const scroll = previewScroll
|
|
824
1028
|
const children = scroll?.getChildren()
|
|
825
1029
|
if (!scroll || !children || children.length === 0) return
|
|
@@ -829,26 +1033,28 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
829
1033
|
const nearTop = scroll.scrollTop <= prefetchDistance
|
|
830
1034
|
const atBottom = scroll.scrollTop + scroll.height >= totalContentHeight - 1
|
|
831
1035
|
const nearBottom = scroll.scrollTop + scroll.height >= totalContentHeight - prefetchDistance
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1036
|
+
const msSinceManualScroll = Date.now() - lastPreviewScrollKeyMs
|
|
1037
|
+
const willLoadBefore = atTop && hasMorePreviewBefore() && (Date.now() - lastBeforeLoadMs) > 100 && msSinceManualScroll > 700
|
|
1038
|
+
debug.log("preview:edge:decision", {
|
|
1039
|
+
scrollTop: scroll.scrollTop,
|
|
1040
|
+
height: scroll.height,
|
|
1041
|
+
contentHeight: totalContentHeight,
|
|
1042
|
+
prefetchDistance,
|
|
1043
|
+
atTop,
|
|
1044
|
+
nearTop,
|
|
1045
|
+
atBottom,
|
|
1046
|
+
nearBottom,
|
|
1047
|
+
hasMoreBefore: hasMorePreviewBefore(),
|
|
1048
|
+
hasMoreAfter: hasMorePreviewAfter(),
|
|
1049
|
+
loadingBefore: loadingPreviewMore(),
|
|
1050
|
+
pendingBefore: Boolean(previewBeforeTimer),
|
|
1051
|
+
willLoadBefore,
|
|
1052
|
+
msSinceLastLoad: Date.now() - lastBeforeLoadMs,
|
|
1053
|
+
msSinceManualScroll,
|
|
1054
|
+
})
|
|
1055
|
+
if (willLoadBefore) {
|
|
1056
|
+
schedulePreviewBefore({ intent: "passive-prefetch", previousContentHeight: totalContentHeight, previousScrollTop: scroll.scrollTop })
|
|
850
1057
|
}
|
|
851
|
-
if (atTop && hasMorePreviewBefore()) schedulePreviewBefore(totalContentHeight, true, false)
|
|
852
1058
|
if (nearBottom && hasMorePreviewAfter()) schedulePreviewAfter(atBottom)
|
|
853
1059
|
}, 400)
|
|
854
1060
|
onCleanup(() => clearInterval(interval))
|
|
@@ -883,11 +1089,14 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
883
1089
|
}
|
|
884
1090
|
|
|
885
1091
|
const beforeState = previewScrollState()
|
|
1092
|
+
lastPreviewScrollKeyMs = Date.now()
|
|
886
1093
|
debug.log("preview:scroll-key", { direction, state: beforeState })
|
|
887
1094
|
|
|
1095
|
+
const requestedAmount = previewScrollAmount(scroll)
|
|
1096
|
+
|
|
888
1097
|
if (direction < 0 && scroll.scrollTop <= 0 && hasMorePreviewBefore()) {
|
|
889
|
-
debug.log("preview:scroll-key:load-before", { direction, state: beforeState })
|
|
890
|
-
schedulePreviewBefore(
|
|
1098
|
+
debug.log("preview:scroll-key:load-before", { direction, requestedAmount, state: beforeState })
|
|
1099
|
+
schedulePreviewBefore({ intent: "explicit-scroll", visibleLoad: true, requestedAmount, previousScrollTop: scroll.scrollTop })
|
|
891
1100
|
return
|
|
892
1101
|
}
|
|
893
1102
|
|
|
@@ -898,7 +1107,7 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
898
1107
|
return
|
|
899
1108
|
}
|
|
900
1109
|
|
|
901
|
-
const amount = direction *
|
|
1110
|
+
const amount = direction * requestedAmount
|
|
902
1111
|
scroll.scrollBy(amount)
|
|
903
1112
|
updatePreviewWindow()
|
|
904
1113
|
debug.log("preview:scroll-key:scroll", { direction, amount, before: beforeState, after: previewScrollState() })
|
|
@@ -1027,6 +1236,7 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
1027
1236
|
>
|
|
1028
1237
|
<Show when={!loading()}>
|
|
1029
1238
|
<Show when={results().length > 0} fallback={<EmptyState query={query()} owner={ownerLabel()} theme={theme()} />}>
|
|
1239
|
+
<box height={resultTopSpacerHeight()} flexShrink={0} />
|
|
1030
1240
|
<For each={resultRenderWindow().items}>
|
|
1031
1241
|
{(item, index) => {
|
|
1032
1242
|
const absoluteIndex = () => resultRenderWindow().start + index()
|
|
@@ -1043,6 +1253,7 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
1043
1253
|
)
|
|
1044
1254
|
}}
|
|
1045
1255
|
</For>
|
|
1256
|
+
<box height={resultBottomSpacerHeight()} flexShrink={0} />
|
|
1046
1257
|
<Show when={loadingMore()}>
|
|
1047
1258
|
<SkeletonRow theme={theme()} />
|
|
1048
1259
|
</Show>
|