@@ -43,10 +43,33 @@ type SourceViewProps = {
4343 readonly scrollGeneration : number ;
4444 readonly scrollToLineNumber ?: number ;
4545 readonly highlightedLine : number | null ;
46+ // 1-based line number for the start of the source in the full document.
47+ readonly startLine : number ;
4648} ;
4749
4850let editorModulePromise : Promise < any > | null = null ;
4951
52+ // Remap absolute line timings to document-relative (1-based) line numbers.
53+ // Timings keys are absolute line numbers; CodeMirror uses doc-relative line numbers.
54+ function remapTimingsToRelative (
55+ timings : LineTimings ,
56+ startLine : number
57+ ) : LineTimings {
58+ if ( startLine <= 1 ) {
59+ return timings ;
60+ }
61+ const offset = startLine - 1 ;
62+ const totalLineHits = new Map < number , number > ( ) ;
63+ for ( const [ line , hits ] of timings . totalLineHits ) {
64+ totalLineHits . set ( line - offset , hits ) ;
65+ }
66+ const selfLineHits = new Map < number , number > ( ) ;
67+ for ( const [ line , hits ] of timings . selfLineHits ) {
68+ selfLineHits . set ( line - offset , hits ) ;
69+ }
70+ return { totalLineHits, selfLineHits } ;
71+ }
72+
5073export class SourceView extends React . PureComponent < SourceViewProps > {
5174 _ref = React . createRef < HTMLDivElement > ( ) ;
5275 _editor : SourceViewEditor | null = null ;
@@ -57,8 +80,28 @@ export class SourceView extends React.PureComponent<SourceViewProps> {
5780 }
5881 }
5982
83+ _toRelativeLine ( absoluteLine : number ) : number {
84+ return Math . max ( 1 , absoluteLine - ( this . props . startLine - 1 ) ) ;
85+ }
86+
87+ // Convert an absolute scroll-to line number to a doc-relative line number.
88+ _getRelativeScrollToLineNumber ( ) : number | undefined {
89+ const { scrollToLineNumber } = this . props ;
90+ return scrollToLineNumber === undefined
91+ ? undefined
92+ : this . _toRelativeLine ( scrollToLineNumber ) ;
93+ }
94+
95+ // Convert an absolute highlighted line number to a doc-relative line number.
96+ _getRelativeHighlightedLine ( ) : number | null {
97+ const { highlightedLine } = this . props ;
98+ return highlightedLine === null
99+ ? null
100+ : this . _toRelativeLine ( highlightedLine ) ;
101+ }
102+
60103 _getMaxLineNumber ( ) {
61- const { sourceCode, timings } = this . props ;
104+ const { sourceCode, timings, startLine } = this . props ;
62105 const sourceLines = sourceCode . split ( '\n' ) ;
63106 let maxLineNumber = sourceLines . length ;
64107 if ( maxLineNumber <= 1 ) {
@@ -69,7 +112,9 @@ export class SourceView extends React.PureComponent<SourceViewProps> {
69112 // isn't too constrained - if the last known line is chosen as the "hot spot",
70113 // this extra space allows us to display it in the top half of the viewport,
71114 // if the viewport is small enough.
72- maxLineNumber = Math . max ( 1 , ...timings . totalLineHits . keys ( ) ) + 10 ;
115+ // timings keys are absolute line numbers; convert to doc-relative count.
116+ const maxAbsoluteLine = Math . max ( 1 , ...timings . totalLineHits . keys ( ) ) ;
117+ maxLineNumber = Math . max ( 1 , maxAbsoluteLine - startLine + 1 ) + 10 ;
73118 }
74119 return maxLineNumber ;
75120 }
@@ -108,14 +153,16 @@ export class SourceView extends React.PureComponent<SourceViewProps> {
108153 const editor = new SourceViewEditor (
109154 this . _getSourceCodeOrFallback ( ) ,
110155 this . props . filePath ,
111- this . props . timings ,
112- this . props . highlightedLine ,
156+ remapTimingsToRelative ( this . props . timings , this . props . startLine ) ,
157+ this . _getRelativeHighlightedLine ( ) ,
158+ this . props . startLine ,
113159 domParent
114160 ) ;
115161 this . _editor = editor ;
116162 // If an explicit line number is provided, scroll to it. Otherwise, scroll to the hotspot.
117- if ( this . props . scrollToLineNumber !== undefined ) {
118- this . _scrollToLine ( Math . max ( 1 , this . props . scrollToLineNumber - 5 ) ) ;
163+ const relativeScrollTo = this . _getRelativeScrollToLineNumber ( ) ;
164+ if ( relativeScrollTo !== undefined ) {
165+ this . _scrollToLine ( Math . max ( 1 , relativeScrollTo - 5 ) ) ;
119166 }
120167 } ) ( ) ;
121168 }
@@ -131,6 +178,11 @@ export class SourceView extends React.PureComponent<SourceViewProps> {
131178 this . _editor . updateLanguageForFilePath ( this . props . filePath ) ;
132179 }
133180
181+ const startLineChanged = this . props . startLine !== prevProps . startLine ;
182+ if ( startLineChanged ) {
183+ this . _editor . setStartLine ( this . props . startLine ) ;
184+ }
185+
134186 let contentsChanged = false ;
135187 if (
136188 this . props . sourceCode !== prevProps . sourceCode ||
@@ -147,17 +199,23 @@ export class SourceView extends React.PureComponent<SourceViewProps> {
147199 this . props . scrollGeneration !== prevProps . scrollGeneration
148200 ) {
149201 // If an explicit line number is provided, scroll to it. Otherwise, scroll to the hotspot.
150- if ( this . props . scrollToLineNumber !== undefined ) {
151- this . _scrollToLine ( Math . max ( 1 , this . props . scrollToLineNumber - 5 ) ) ;
202+ const relativeScrollTo = this . _getRelativeScrollToLineNumber ( ) ;
203+ if ( relativeScrollTo !== undefined ) {
204+ this . _scrollToLine ( Math . max ( 1 , relativeScrollTo - 5 ) ) ;
152205 }
153206 }
154207
155- if ( this . props . timings !== prevProps . timings ) {
156- this . _editor . setTimings ( this . props . timings ) ;
208+ if ( this . props . timings !== prevProps . timings || startLineChanged ) {
209+ this . _editor . setTimings (
210+ remapTimingsToRelative ( this . props . timings , this . props . startLine )
211+ ) ;
157212 }
158213
159- if ( this . props . highlightedLine !== prevProps . highlightedLine ) {
160- this . _editor . setHighlightedLine ( this . props . highlightedLine ) ;
214+ if (
215+ this . props . highlightedLine !== prevProps . highlightedLine ||
216+ startLineChanged
217+ ) {
218+ this . _editor . setHighlightedLine ( this . _getRelativeHighlightedLine ( ) ) ;
161219 }
162220 }
163221}
0 commit comments