1
1
'use strict' ;
2
2
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' ) ;
29
4
30
5
function Scroller ( consoleElement ) {
31
6
this . lines = [ ] ;
32
- this . minVisible = 30 ;
7
+ this . lineOffset = 0 ;
8
+ this . visibleCount = 50 ;
33
9
this . startPosition = 0 ;
10
+ this . endPosition = 0 ;
34
11
this . animateRequest = null ;
35
12
this . sticky = true ;
36
13
this . jumpToBottom = true ;
37
14
this . dirty = false ;
38
15
this . console = consoleElement ;
16
+ this . expandDistance = 200 ;
39
17
40
18
//pre-bind functions and throttle expansion
41
19
this . refresh = this . _renderVisible . bind ( this ) ;
42
20
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 , {
44
26
leading : true ,
45
27
trailing : true
46
28
} ) ;
47
29
}
48
30
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 ) {
51
46
this . lines = newLines ;
47
+ this . lineOffset = offset ;
52
48
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 ) {
55
56
// ^^ `lines` is reset to an array with one empty line. ugh.
56
57
57
58
// handle the reset case when lines is replaced with an empty array
58
59
// we don't have a direct event that can call this
59
60
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 ;
63
66
}
64
67
this . dirty = true ;
65
68
} ;
66
69
70
+ Scroller . prototype . lineCount = function ( ) {
71
+ return this . lines . length + this . lineOffset ;
72
+ } ;
73
+
67
74
Scroller . 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 ;
69
78
this . jumpToBottom = true ;
70
79
this . sticky = true ;
71
80
this . dirty = true ;
@@ -80,34 +89,61 @@ Scroller.prototype.requestRefresh = function(){
80
89
Scroller . prototype . _renderVisible = function ( ) {
81
90
this . animateRequest = null ;
82
91
if ( this . dirty && this . console ) {
83
- var top = this . console . scrollTop ;
92
+ const top = this . console . scrollTop ;
84
93
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 ) ;
86
96
}
87
- this . console . innerHTML = generateContent ( this . lines , this . startPosition , this . lines . length , this . minVisible ) ;
97
+ this . console . innerHTML = this . _generateContent ( ) ;
88
98
if ( this . jumpToBottom ) {
89
- this . console . scrollTop = 2000 ;
99
+ this . console . scrollTop = 4000 ;
90
100
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 ) {
92
102
//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 ) ;
94
104
}
95
105
this . dirty = false ;
96
106
}
97
107
} ;
98
108
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 ) ;
102
111
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 ;
105
115
106
116
// 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 ;
109
119
this . console . scrollTop = scrollTop + newScrollHeight - scrollHeight ;
110
120
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
+
111
147
this . dirty = false ;
112
148
}
113
149
} ;
@@ -117,23 +153,33 @@ Scroller.prototype._onScroll = function(){
117
153
// do nothing, prepare to jump
118
154
return ;
119
155
}
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
+
123
163
if ( this . sticky ) {
124
- if ( scrollTop + height < scrollHeight - 30 ) {
164
+ if ( ! nearSticky ) {
125
165
this . sticky = false ;
126
166
}
127
167
} 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
+ }
134
178
}
135
179
}
136
180
181
+
182
+
137
183
if ( this . dirty && ! this . animateRequest ) {
138
184
this . animateRequest = requestAnimationFrame ( this . refresh ) ;
139
185
}
0 commit comments