11'use strict' ;
22
3- var _ = require ( 'lodash' ) ;
4-
5- function generateContent ( lines , start , end , minLength ) {
6- return _ ( lines )
7- . slice ( start , end )
8- . thru ( function ( array ) {
9- if ( array . length < minLength ) {
10- // pad whitespace at top of array
11- return _ ( new Array ( minLength - array . length ) )
12- . fill ( '\u2009' )
13- . concat ( array )
14- . value ( ) ;
15- } else {
16- return array ;
17- }
18- } )
19- . map ( function ( line ) {
20- if ( line . length === 0 ) {
21- // insert a blank space to prevent pre omitting a trailing newline,
22- // even though pre/pre-nowrap/pre-line are specified.
23- return '\u2009' ;
24- }
25- return line ;
26- } )
27- . join ( '\n' ) ;
28- }
3+ const _ = require ( 'lodash' ) ;
294
305function Scroller ( consoleElement ) {
316 this . lines = [ ] ;
32- this . minVisible = 30 ;
7+ this . lineOffset = 0 ;
8+ this . visibleCount = 50 ;
339 this . startPosition = 0 ;
10+ this . endPosition = 0 ;
3411 this . animateRequest = null ;
3512 this . sticky = true ;
3613 this . jumpToBottom = true ;
3714 this . dirty = false ;
3815 this . console = consoleElement ;
16+ this . expandDistance = 200 ;
3917
4018 //pre-bind functions and throttle expansion
4119 this . refresh = this . _renderVisible . bind ( this ) ;
4220 this . scroll = this . _onScroll . bind ( this ) ;
43- this . expand = _ . throttle ( this . _expand . bind ( this ) , 150 , {
21+ this . expandTop = _ . throttle ( this . _expandTop . bind ( this ) , 150 , {
22+ leading : true ,
23+ trailing : true
24+ } ) ;
25+ this . expandBottom = _ . throttle ( this . _expandBottom . bind ( this ) , 150 , {
4426 leading : true ,
4527 trailing : true
4628 } ) ;
4729}
4830
49- Scroller . prototype . setLines = function ( newLines ) {
50- var len = newLines . length ;
31+ Scroller . prototype . _generateContent = function ( ) {
32+ return _ ( this . lines )
33+ . slice ( this . startPosition - this . lineOffset , this . endPosition - this . lineOffset )
34+ . map ( function ( line ) {
35+ if ( line . length === 0 ) {
36+ // insert a blank space to prevent pre omitting a trailing newline,
37+ // even though pre/pre-nowrap/pre-line are specified.
38+ return '\u2009' ;
39+ }
40+ return line ;
41+ } )
42+ . join ( '\n' ) ;
43+ } ;
44+
45+ Scroller . prototype . setLines = function ( newLines , offset ) {
5146 this . lines = newLines ;
47+ this . lineOffset = offset ;
5248 if ( this . sticky ) {
53- this . startPosition = Math . max ( 0 , len - this . minVisible ) ;
54- } else if ( len === 1 && newLines [ 0 ] . length === 0 ) {
49+ this . endPosition = this . lineCount ( ) ;
50+ this . startPosition = Math . max ( this . lineOffset , this . endPosition - this . visibleCount ) ;
51+ if ( this . endPosition <= this . visibleCount ) {
52+ // follow text during initial 50 lines
53+ this . jumpToBottom = true ;
54+ }
55+ } else if ( newLines . length === 1 && newLines [ 0 ] . length === 0 ) {
5556 // ^^ `lines` is reset to an array with one empty line. ugh.
5657
5758 // handle the reset case when lines is replaced with an empty array
5859 // we don't have a direct event that can call this
5960 this . reset ( ) ;
60- } else if ( len < this . startPosition ) {
61- // handle buffer rollover, where number of lines will go from 2048 to ~1900
62- this . startPosition = Math . max ( 0 , len - this . minVisible ) ;
61+ } else if ( this . lineOffset > this . startPosition ) {
62+ // when buffer trims and we are now below the trimmed area, move up by difference
63+ const lineDiff = this . lineOffset - this . startPosition ;
64+ this . startPosition += lineDiff ;
65+ this . endPosition += lineDiff ;
6366 }
6467 this . dirty = true ;
6568} ;
6669
70+ Scroller . prototype . lineCount = function ( ) {
71+ return this . lines . length + this . lineOffset ;
72+ } ;
73+
6774Scroller . prototype . reset = function ( ) {
68- this . startPosition = Math . max ( 0 , this . lines . length - this . minVisible ) ;
75+ this . endPosition = Math . max ( 0 , this . lineCount ( ) ) ;
76+ this . startPosition = Math . max ( 0 , this . endPosition - this . visibleCount ) ;
77+ this . lineOffset = 0 ;
6978 this . jumpToBottom = true ;
7079 this . sticky = true ;
7180 this . dirty = true ;
@@ -80,34 +89,61 @@ Scroller.prototype.requestRefresh = function(){
8089Scroller . prototype . _renderVisible = function ( ) {
8190 this . animateRequest = null ;
8291 if ( this . dirty && this . console ) {
83- var top = this . console . scrollTop ;
92+ const top = this . console . scrollTop ;
8493 if ( this . sticky ) {
85- this . startPosition = Math . max ( 0 , this . lines . length - this . minVisible ) ;
94+ this . endPosition = this . lineCount ( ) ;
95+ this . startPosition = Math . max ( this . lineOffset , this . endPosition - this . visibleCount ) ;
8696 }
87- this . console . innerHTML = generateContent ( this . lines , this . startPosition , this . lines . length , this . minVisible ) ;
97+ this . console . innerHTML = this . _generateContent ( ) ;
8898 if ( this . jumpToBottom ) {
89- this . console . scrollTop = 2000 ;
99+ this . console . scrollTop = 4000 ;
90100 this . jumpToBottom = false ;
91- } else if ( ! this . sticky && this . startPosition > 0 && top === 0 ) {
101+ } else if ( ! this . sticky && this . startPosition > this . lineOffset && top === this . lineOffset ) {
92102 //cover the situation where the window was fully scrolled faster than expand could keep up and locked to the top
93- requestAnimationFrame ( this . expand ) ;
103+ requestAnimationFrame ( this . expandTop ) ;
94104 }
95105 this . dirty = false ;
96106 }
97107} ;
98108
99- Scroller . prototype . _expand = function ( ) {
100- this . startPosition = Math . max ( 0 , this . startPosition - this . minVisible ) ;
101- this . sticky = false ;
109+ Scroller . prototype . _expandTop = function ( ) {
110+ this . startPosition = Math . max ( this . lineOffset , this . startPosition - this . visibleCount ) ;
102111 if ( this . console ) {
103- var scrollHeight = this . console . scrollHeight ;
104- var scrollTop = this . console . scrollTop ;
112+ this . sticky = false ;
113+ const scrollHeight = this . console . scrollHeight ;
114+ const scrollTop = this . console . scrollTop ;
105115
106116 // do an inline scroll to avoid potential scroll interleaving
107- this . console . innerHTML = generateContent ( this . lines , this . startPosition , this . lines . length , this . minVisible ) ;
108- var newScrollHeight = this . console . scrollHeight ;
117+ this . console . innerHTML = this . _generateContent ( ) ;
118+ const newScrollHeight = this . console . scrollHeight ;
109119 this . console . scrollTop = scrollTop + newScrollHeight - scrollHeight ;
110120
121+ const oldEndPos = this . endPosition ;
122+ this . endPosition = Math . min ( this . endPosition , this . startPosition + ( this . visibleCount * 2 ) ) ;
123+
124+ this . dirty = oldEndPos !== this . endPosition ;
125+ if ( this . dirty && ! this . animateRequest ) {
126+ this . animateRequest = requestAnimationFrame ( this . refresh ) ;
127+ }
128+ }
129+ } ;
130+
131+ Scroller . prototype . _expandBottom = function ( ) {
132+ this . endPosition = Math . min ( this . lineCount ( ) , this . endPosition + this . visibleCount ) ;
133+ if ( this . console ) {
134+ // add the new content to the bottom, then get scroll position to remove content
135+ this . console . innerHTML = this . _generateContent ( ) ;
136+ const scrollHeight = this . console . scrollHeight ;
137+ const scrollTop = this . console . scrollTop ;
138+
139+ // update start position and render
140+ this . startPosition = Math . max ( this . lineOffset , Math . min ( this . lineCount ( ) - ( this . visibleCount * 2 ) , this . endPosition - ( this . visibleCount * 2 ) ) ) ;
141+ this . console . innerHTML = this . _generateContent ( ) ;
142+
143+ // use difference to scroll offset
144+ const newScrollHeight = this . console . scrollHeight ;
145+ this . console . scrollTop = scrollTop - ( scrollHeight - newScrollHeight ) ;
146+
111147 this . dirty = false ;
112148 }
113149} ;
@@ -117,23 +153,33 @@ Scroller.prototype._onScroll = function(){
117153 // do nothing, prepare to jump
118154 return ;
119155 }
120- var height = this . console . offsetHeight ;
121- var scrollHeight = this . console . scrollHeight ;
122- var scrollTop = this . console . scrollTop ;
156+ const height = this . console . offsetHeight ;
157+ const scrollHeight = this . console . scrollHeight ;
158+ const scrollTop = this . console . scrollTop ;
159+ const nearTop = scrollTop < this . expandDistance ;
160+ const nearBottom = scrollTop + height > scrollHeight - this . expandDistance ;
161+ const nearSticky = scrollTop + height > scrollHeight - 10 ;
162+
123163 if ( this . sticky ) {
124- if ( scrollTop + height < scrollHeight - 30 ) {
164+ if ( ! nearSticky ) {
125165 this . sticky = false ;
126166 }
127167 } else {
128- if ( scrollTop < 15 && this . startPosition > 0 ) {
129- this . expand ( ) ;
130- } else if ( scrollTop + height > scrollHeight - 30 ) {
131- this . jumpToBottom = true ;
132- this . sticky = true ;
133- this . dirty = true ;
168+ if ( nearTop && this . startPosition > this . lineOffset ) {
169+ this . expandTop ( ) ;
170+ } else if ( nearBottom ) {
171+ if ( this . endPosition < this . lineCount ( ) - 2 ) {
172+ this . expandBottom ( ) ;
173+ } else if ( nearSticky ) {
174+ this . jumpToBottom = true ;
175+ this . sticky = true ;
176+ this . dirty = true ;
177+ }
134178 }
135179 }
136180
181+
182+
137183 if ( this . dirty && ! this . animateRequest ) {
138184 this . animateRequest = requestAnimationFrame ( this . refresh ) ;
139185 }
0 commit comments