diff --git a/JitHub.WinUI/Services/CodeViewer/RepoFileCacheService.cs b/JitHub.WinUI/Services/CodeViewer/RepoFileCacheService.cs index bc9f301..95713c8 100644 --- a/JitHub.WinUI/Services/CodeViewer/RepoFileCacheService.cs +++ b/JitHub.WinUI/Services/CodeViewer/RepoFileCacheService.cs @@ -49,7 +49,7 @@ public RepoFileCacheService(int memMaxEntries, long memMaxBytes, long diskMaxByt _diskMaxBytes = diskMaxBytes; _ttl = ttl; - _diskRoot = Path.Combine(ApplicationData.Current.LocalCacheFolder.Path, "RepoFileCache"); + _diskRoot = GetDefaultDiskRoot(); Directory.CreateDirectory(_diskRoot); // Background startup purge — do not block the constructor. @@ -73,6 +73,22 @@ internal RepoFileCacheService( // ── Public API ─────────────────────────────────────────────────────────── + private static string GetDefaultDiskRoot() + { + try + { + return Path.Combine(ApplicationData.Current.LocalCacheFolder.Path, "RepoFileCache"); + } + catch (InvalidOperationException) + { + string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + if (string.IsNullOrWhiteSpace(localAppData)) + localAppData = Path.GetTempPath(); + + return Path.Combine(localAppData, "JitHub", "RepoFileCache"); + } + } + public bool TryGet(RepoFileCacheKey key, out RepoFileCacheEntry entry) { string mk = MemKey(key); diff --git a/MarkdownRenderer/MarkdownRenderer/Controls/MarkdownRendererControl.cs b/MarkdownRenderer/MarkdownRenderer/Controls/MarkdownRendererControl.cs index b355650..2a6ee5c 100644 --- a/MarkdownRenderer/MarkdownRenderer/Controls/MarkdownRendererControl.cs +++ b/MarkdownRenderer/MarkdownRenderer/Controls/MarkdownRendererControl.cs @@ -2138,24 +2138,57 @@ private bool EnsureLazyLayoutForViewport() if (snapshot is null || !snapshot.IsLazyLayoutEnabled || _scroll is null) return false; - var anchor = CaptureScrollAnchor(snapshot, _scroll.VerticalOffset); - var commit = snapshot.EnsureMeasuredViewport( + var band = LazyLayoutBand.FromViewport( _scroll.VerticalOffset, _scroll.ViewportHeight > 0 ? _scroll.ViewportHeight : Math.Max(ActualHeight, 600), - LazyLayoutOverscanPx, - CancellationToken.None); + LazyLayoutOverscanPx); + return EnsureLazyLayoutForBand(snapshot, band, preserveScrollAnchor: true, invalidateCanvas: true); + } + + private bool EnsureLazyLayoutForPaintRegion(Rect region) + { + var snapshot = _snapshot; + if (snapshot is null || !snapshot.IsLazyLayoutEnabled) + return false; + + var band = LazyLayoutBand.FromViewport(region.Top, region.Height, LazyLayoutOverscanPx); + return EnsureLazyLayoutForBand(snapshot, band, preserveScrollAnchor: _scroll is not null, invalidateCanvas: false); + } + + private bool EnsureLazyLayoutForBand( + LayoutSnapshot snapshot, + LazyLayoutBand band, + bool preserveScrollAnchor, + bool invalidateCanvas) + { + (int BlockIndex, double OffsetFromTop)? anchor = null; + if (preserveScrollAnchor && _scroll is not null) + anchor = CaptureScrollAnchor(snapshot, _scroll.VerticalOffset); + + var commit = snapshot.EnsureMeasuredBand(band, CancellationToken.None); if (!commit.Changed) return false; + ApplyLazyLayoutCommit(snapshot, anchor, invalidateCanvas); + return true; + } + + private void ApplyLazyLayoutCommit( + LayoutSnapshot snapshot, + (int BlockIndex, double OffsetFromTop)? scrollAnchor, + bool invalidateCanvas) + { ApplySnapshotSize(snapshot); - RestoreScrollAnchor(snapshot, anchor); + RestoreScrollAnchor(snapshot, scrollAnchor); RebuildRealizationPlans(snapshot, preserveRealized: true); _focusableItems = snapshot.CollectFocusableItems(); + RealizeVisibleEmbeds(); + ScheduleVisibleCodeBlockHighlighting(); UpdateSelectionAdornerViewport(); UpdateFocusRing(); - _canvas?.Invalidate(); - return true; + if (invalidateCanvas) + _canvas?.Invalidate(); } private void DerealizeAllEmbeds() @@ -2482,6 +2515,9 @@ private void OnRegionsInvalidated(CanvasVirtualControl sender, CanvasRegionsInva "region", regionCount, region.X, region.Y, region.Width, region.Height); try { + // The tile itself is the authoritative paint band; the ScrollViewer + // viewport can lag behind CanvasVirtualControl's region requests. + EnsureLazyLayoutForPaintRegion(region); using var ds = sender.CreateDrawingSession(region); // Clear to the theme-appropriate background color so that switching between // light and dark mode (or any theme change) fully overwrites old tile content. diff --git a/MarkdownRenderer/MarkdownRenderer/Layout/Boxes/InlineContainerBox.cs b/MarkdownRenderer/MarkdownRenderer/Layout/Boxes/InlineContainerBox.cs index 62c3e9b..7ac6484 100644 --- a/MarkdownRenderer/MarkdownRenderer/Layout/Boxes/InlineContainerBox.cs +++ b/MarkdownRenderer/MarkdownRenderer/Layout/Boxes/InlineContainerBox.cs @@ -483,7 +483,15 @@ internal IEnumerable GetBufferRangeLineRects(int from, int length) int rangeEnd = from + length; int lineStart = 0; double y = 0; - foreach (var metric in _layout.LineMetrics) + var lineMetrics = TryGetLineMetrics(); + if (lineMetrics is null) + { + foreach (var rect in GetBufferRangeRects(from, length)) + yield return rect; + yield break; + } + + foreach (var metric in lineMetrics) { int charCount = Math.Max(0, metric.CharacterCount); int lineEnd = lineStart + charCount; @@ -557,7 +565,7 @@ public void PaintLinkStateForeground(CanvasDrawingSession ds, LinkRun link, bool if (regions is null) return; - var lineMetrics = _layout.LineMetrics; + var lineMetrics = TryGetLineMetrics(); foreach (var r in regions) { var rect = new Rect( @@ -1124,9 +1132,10 @@ private void DrawDecorations(CanvasDrawingSession ds, float baseX, float baseY) private void DrawDecorations(CanvasDrawingSession ds, float baseX, float baseY, Color? overrideColor) { if (_layout is null) return; - var lineMetrics = _layout.LineMetrics; int cumulative = 0; + CanvasLineMetrics[]? lineMetrics = null; + bool lineMetricsAttempted = false; foreach (var run in _runs) { int len = run.Text.Length; @@ -1145,6 +1154,11 @@ private void DrawDecorations(CanvasDrawingSession ds, float baseX, float baseY, if (rs.Underline || rs.Strikethrough) { + if (!lineMetricsAttempted) + { + lineMetrics = TryGetLineMetrics(); + lineMetricsAttempted = true; + } var regions = _layout.GetCharacterRegions(cumulative, len); if (regions is null) { cumulative += len; continue; } // Win2D can return null on DirectWrite errors foreach (var r in regions) @@ -1160,7 +1174,7 @@ private static void DrawRunDecorations( CanvasDrawingSession ds, float baseX, float baseY, - CanvasLineMetrics[] lineMetrics, + CanvasLineMetrics[]? lineMetrics, CanvasTextLayoutRegion region, InlineRun run, ElementStyle style, @@ -1240,8 +1254,25 @@ private void DrawRunBackgrounds(CanvasDrawingSession ds, float baseX, float base } } - private static CanvasLineMetrics FindLineMetrics(CanvasLineMetrics[] metrics, CanvasTextLayoutRegion region) + private CanvasLineMetrics[]? TryGetLineMetrics() { + try + { + return _layout?.LineMetrics; + } + catch (NotSupportedException) + { + // Some WinRT/Release projections cannot marshal CanvasLineMetrics. + // Text still renders; decoration placement can use region bounds. + return null; + } + } + + private static CanvasLineMetrics FindLineMetrics(CanvasLineMetrics[]? metrics, CanvasTextLayoutRegion region) + { + if (metrics is null || metrics.Length == 0) + return default; + int idx = (int)region.CharacterIndex; int run = 0; foreach (var m in metrics)