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
153 changes: 106 additions & 47 deletions apps/mobile/src/features/files/SourceFileSurface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { useAtomValue } from "@effect/atom-react";
import { AsyncResult } from "effect/unstable/reactivity";
import type { ComponentType } from "react";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { FlatList, ScrollView, Text as NativeText, useWindowDimensions, View } from "react-native";
import {
FlatList,
RefreshControl,
ScrollView,
Text as NativeText,
useWindowDimensions,
View,
} from "react-native";

import { AppText as Text } from "../../components/AppText";
import { LoadingStrip } from "../../components/LoadingStrip";
Expand Down Expand Up @@ -46,7 +53,7 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: {
}) {
return (
<View
className={cn("flex-row", props.highlighted && "bg-primary/10")}
className={cn("flex-row items-start", props.highlighted && "bg-primary/10")}
style={{ minHeight: props.codeSurface.rowHeight }}
>
<NativeText
Expand All @@ -60,51 +67,55 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: {
>
{props.index + 1}
</NativeText>
<NativeText
selectable
numberOfLines={props.wordBreak ? undefined : 1}
className="flex-1 font-normal text-foreground"
style={{
fontFamily: REVIEW_MONO_FONT_FAMILY,
fontSize: props.codeSurface.fontSize,
lineHeight: props.codeSurface.rowHeight,
minWidth: props.wordBreak ? undefined : 320,
}}
<View
className={props.wordBreak ? "min-w-0 flex-1" : undefined}
style={props.wordBreak ? undefined : { minWidth: 320 }}
>
{props.tokens && props.tokens.length > 0
? (() => {
let offset = 0;
return props.tokens.map((token) => {
const start = offset;
offset += token.content.length;
<NativeText
selectable
numberOfLines={props.wordBreak ? undefined : 1}
className="font-normal text-foreground"
style={{
fontFamily: REVIEW_MONO_FONT_FAMILY,
fontSize: props.codeSurface.fontSize,
lineHeight: props.codeSurface.rowHeight,
}}
>
{props.tokens && props.tokens.length > 0
? (() => {
let offset = 0;
return props.tokens.map((token) => {
const start = offset;
offset += token.content.length;

const fontWeight =
token.fontStyle !== null && (token.fontStyle & 2) === 2
? ("700" as const)
: ("400" as const);
const fontStyle =
token.fontStyle !== null && (token.fontStyle & 1) === 1
? ("italic" as const)
: ("normal" as const);
const fontWeight =
token.fontStyle !== null && (token.fontStyle & 2) === 2
? ("700" as const)
: ("400" as const);
const fontStyle =
token.fontStyle !== null && (token.fontStyle & 1) === 1
? ("italic" as const)
: ("normal" as const);

return (
<NativeText
key={`${start}:${token.content.length}:${token.color ?? ""}`}
selectable
style={{
color: token.color ?? undefined,
fontFamily: REVIEW_MONO_FONT_FAMILY,
fontWeight,
fontStyle,
}}
>
{token.content.length > 0 ? renderVisibleWhitespace(token.content) : " "}
</NativeText>
);
});
})()
: renderVisibleWhitespace(props.line || " ")}
</NativeText>
return (
<NativeText
key={`${start}:${token.content.length}:${token.color ?? ""}`}
selectable
style={{
color: token.color ?? undefined,
fontFamily: REVIEW_MONO_FONT_FAMILY,
fontWeight,
fontStyle,
}}
>
{token.content.length > 0 ? renderVisibleWhitespace(token.content) : " "}
</NativeText>
);
});
})()
: renderVisibleWhitespace(props.line || " ")}
</NativeText>
</View>
</View>
);
});
Expand Down Expand Up @@ -212,19 +223,57 @@ function NativeSourceFileSurface(
}

function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) {
const { onRefresh } = props;
const { codeSurface, codeWordBreak } = useAppearanceCodeSurface();
const { lines, status, targetIndex, tokens } = useSourceFileModel(props);
const listRef = useRef<FlatList<string>>(null);
const scrollRetryCountRef = useRef(0);
const [isPullRefreshing, setIsPullRefreshing] = useState(false);
const handlePullToRefresh = useCallback(async () => {
if (!onRefresh) {
return;
}
setIsPullRefreshing(true);
try {
await onRefresh();
} finally {
setIsPullRefreshing(false);
}
}, [onRefresh]);

const scrollToLine = 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 });
scrollToLine(targetIndex);
});
return () => cancelAnimationFrame(frame);
}, [props.path, targetIndex]);
}, [props.path, scrollToLine, targetIndex]);

const handleScrollToIndexFailed = useCallback(
(info: { index: number; averageItemLength: number }) => {
if (scrollRetryCountRef.current >= 5) {
return;
}
scrollRetryCountRef.current += 1;
const itemLength =
info.averageItemLength > 0 ? info.averageItemLength : codeSurface.rowHeight;
listRef.current?.scrollToOffset({
offset: info.index * itemLength,
animated: false,
});
requestAnimationFrame(() => {
scrollToLine(info.index);
});
},
[codeSurface.rowHeight, scrollToLine],
);

const renderLine = useCallback(
({ item, index }: { item: string; index: number }) => (
Expand Down Expand Up @@ -263,6 +312,15 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) {
paddingTop: 8,
}}
renderItem={renderLine}
onScrollToIndexFailed={handleScrollToIndexFailed}
refreshControl={
onRefresh ? (
<RefreshControl
refreshing={isPullRefreshing}
onRefresh={() => void handlePullToRefresh()}
/>
) : undefined
}
/>
);

Expand All @@ -282,7 +340,8 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) {

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