Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 70 additions & 18 deletions apps/mobile/src/features/files/SourceFileSurface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ interface SourceFileSurfaceProps {

type SourceHighlightStatus = "highlighting" | "ready" | "error";

const SOURCE_SCROLL_RETRY_DELAY_MS = 100;
const SOURCE_SCROLL_MAX_RETRIES = 3;

function useSourcePullToRefresh(onRefresh: SourceFileSurfaceProps["onRefresh"]) {
const [isRefreshing, setIsRefreshing] = useState(false);
const handleRefresh = useCallback(async () => {
if (!onRefresh) {
return;
}
setIsRefreshing(true);
try {
await onRefresh();
} finally {
setIsRefreshing(false);
}
}, [onRefresh]);

return { handleRefresh, isRefreshing };
}

const HighlightedSourceLine = memo(function HighlightedSourceLine(props: {
readonly codeSurface: ResolvedMobileCodeSurface;
readonly index: number;
Expand Down Expand Up @@ -157,18 +177,7 @@ function NativeSourceFileSurface(
const appTheme = useUniwindTheme();
const { width: viewportWidth } = useWindowDimensions();
const { rowsJson, status, targetIndex, tokens } = useSourceFileModel(props);
const [isPullRefreshing, setIsPullRefreshing] = useState(false);
const handlePullToRefresh = useCallback(async () => {
if (!onRefresh) {
return;
}
setIsPullRefreshing(true);
try {
await onRefresh();
} finally {
setIsPullRefreshing(false);
}
}, [onRefresh]);
const { handleRefresh, isRefreshing } = useSourcePullToRefresh(onRefresh);
const tokensJson = useMemo(() => JSON.stringify(buildNativeSourceTokens(tokens)), [tokens]);
const selectedRowIdsJson = useMemo(
() => JSON.stringify(targetIndex === null ? [] : [nativeSourceRowId(targetIndex)]),
Expand Down Expand Up @@ -202,8 +211,8 @@ function NativeSourceFileSurface(
tokensJson={tokensJson}
{...(onRefresh
? {
refreshing: isPullRefreshing,
onPullToRefresh: () => void handlePullToRefresh(),
refreshing: isRefreshing,
onPullToRefresh: () => void handleRefresh(),
}
: {})}
/>
Expand All @@ -214,17 +223,52 @@ function NativeSourceFileSurface(
function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) {
const { codeSurface, codeWordBreak } = useAppearanceCodeSurface();
const { lines, status, targetIndex, tokens } = useSourceFileModel(props);
const { handleRefresh, isRefreshing } = useSourcePullToRefresh(props.onRefresh);
const listRef = useRef<FlatList<string>>(null);
const scrollRetryCountRef = useRef(0);
const scrollRetryTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

const scrollToTargetIndex = useCallback((index: number) => {
listRef.current?.scrollToIndex({ index, animated: false, viewPosition: 0.3 });
}, []);

useEffect(() => {
if (targetIndex === null) {
return;
}
scrollRetryCountRef.current = 0;
const frame = requestAnimationFrame(() => {
listRef.current?.scrollToIndex({ index: targetIndex, animated: false, viewPosition: 0.3 });
scrollToTargetIndex(targetIndex);
});
return () => cancelAnimationFrame(frame);
}, [props.path, targetIndex]);
return () => {
cancelAnimationFrame(frame);
if (scrollRetryTimeoutRef.current !== null) {
clearTimeout(scrollRetryTimeoutRef.current);
scrollRetryTimeoutRef.current = null;
}
};
}, [props.path, scrollToTargetIndex, targetIndex]);

const handleScrollToIndexFailed = useCallback(
(failure: { readonly averageItemLength: number; readonly index: number }) => {
listRef.current?.scrollToOffset({
animated: false,
offset: failure.averageItemLength * failure.index,
});
if (scrollRetryCountRef.current >= SOURCE_SCROLL_MAX_RETRIES) {
return;
}
scrollRetryCountRef.current += 1;
if (scrollRetryTimeoutRef.current !== null) {
clearTimeout(scrollRetryTimeoutRef.current);
}
scrollRetryTimeoutRef.current = setTimeout(() => {
scrollRetryTimeoutRef.current = null;
scrollToTargetIndex(failure.index);
}, SOURCE_SCROLL_RETRY_DELAY_MS);
},
[scrollToTargetIndex],
);

const renderLine = useCallback(
({ item, index }: { item: string; index: number }) => (
Expand All @@ -248,6 +292,13 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) {
initialNumToRender={80}
maxToRenderPerBatch={80}
windowSize={12}
onScrollToIndexFailed={handleScrollToIndexFailed}
{...(props.onRefresh
? {
refreshing: isRefreshing,
onRefresh: () => void handleRefresh(),
}
: {})}
{...(codeWordBreak
? {}
: {
Expand Down Expand Up @@ -281,8 +332,9 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) {
}

export function SourceFileSurface(props: SourceFileSurfaceProps) {
const { appearance } = useAppearancePreferences();
const NativeView = resolveNativeReviewDiffView();
return NativeView ? (
return NativeView && !appearance.codeWordBreak ? (
<NativeSourceFileSurface {...props} NativeView={NativeView} />
) : (
<JavaScriptSourceFileSurface {...props} />
Expand Down
Loading