@@ -2,7 +2,6 @@ package react
22
33import (
44 "fmt"
5- "strconv"
65 "strings"
76
87 "github.com/gopherjs/gopherjs/js"
@@ -36,7 +35,7 @@ func codeBoxComponent(props Props) *Element {
3635 CreateElement (`textarea` , Props {
3736 `id` : `line-nums` ,
3837 `ref` : cba .lineNumsRef ,
39- `value` : cba .getLineNumbers (cba . curCode ),
38+ `value` : cba .getLineNumbers (),
4039 `readOnly` : true ,
4140 `disable` : `true` ,
4241 }),
@@ -67,7 +66,6 @@ type codeBoxAssistant struct {
6766func (cba * codeBoxAssistant ) onInput (e * js.Object ) {
6867 code := e .Get (`target` ).Get (`value` ).String ()
6968 cba .setCode (code )
70- //cba.updateLineNumbers(code)
7169}
7270
7371func (cba * codeBoxAssistant ) onKeyDown (e * js.Object ) {
@@ -77,15 +75,15 @@ func (cba *codeBoxAssistant) onKeyDown(e *js.Object) {
7775}
7876
7977func (cba * codeBoxAssistant ) onScroll (e * js.Object ) {
80- scrollTop := e .Get ("target" ).Get ("scrollTop" ).Int ()
81- println ("curScrollTop:" , scrollTop )
82- cba .lineNumsRef .Set ("scrollTop" , scrollTop )
78+ scrollTop := e .Get (`target` ).Get (`scrollTop` ).Int ()
79+ cba .lineNumsRef .Set (`scrollTop` , scrollTop )
8380}
8481
8582func (cba * codeBoxAssistant ) handleKeyDown (keyCode int ) bool {
8683 toInsert := ``
8784 switch keyCode {
8885 case '\t' :
86+ // Insert tab character and prevent focus change.
8987 toInsert = "\t "
9088 case '\r' :
9189 toInsert = "\n "
@@ -131,12 +129,12 @@ func (cba *codeBoxAssistant) currentIndent(start int, code string) string {
131129 return code [par :i ]
132130}
133131
134- func (cba * codeBoxAssistant ) getLineNumbers (code string ) string {
135- lines := strings .Count (code , "\n " ) + 1
136- size := len (fmt .Sprintf ("%d" , lines ))
132+ func (cba * codeBoxAssistant ) getLineNumbers () string {
133+ lines := strings .Count (cba . curCode , "\n " ) + 1
134+ size := len (fmt .Sprintf (`%d` , lines ))
137135 var sb strings.Builder
138136 for i := 1 ; i <= lines ; i ++ {
139- sb .WriteString (fmt .Sprintf (" %*d" , size , i ))
137+ sb .WriteString (fmt .Sprintf (` %*d` , size , i ))
140138 if i < lines {
141139 sb .WriteString ("\n " )
142140 }
@@ -150,19 +148,6 @@ func (cba *codeBoxAssistant) getSelection() (int, int) {
150148 return start , end
151149}
152150
153- func (cba * codeBoxAssistant ) getLineHeight () int {
154- style := js .Global .Get ("window" ).Call ("getComputedStyle" , cba .textAreaRef .Current ())
155- // Get line-height property (returns string like "15px")
156- lineHeightStr := style .Call ("getPropertyValue" , "line-height" ).String ()
157- lineHeightStr = strings .TrimSuffix (lineHeightStr , "px" )
158- lineHeight , err := strconv .Atoi (lineHeightStr )
159- if err != nil {
160- // Fallback to default if parsing fails (15px is about right for 11pt font)
161- return 15
162- }
163- return lineHeight
164- }
165-
166151func (cba * codeBoxAssistant ) setSelection (caret int , code string ) {
167152 // Pre-update the textarea value so that the caret and scroll can be set
168153 // correctly before the next render so that the next render doesn't reset them.
@@ -173,20 +158,73 @@ func (cba *codeBoxAssistant) setSelection(caret int, code string) {
173158 cba .textAreaRef .Set (`selectionEnd` , caret )
174159
175160 // Auto-scroll to keep caret in view.
176- curLineNum := strings .Count (code [:caret ], "\n " )
177- lineHeight := cba .getLineHeight ()
178- scrollTop := curLineNum * lineHeight
161+ cba .verticallyAutoScroll (caret , code )
162+ cba .horizontallyAutoScroll (caret , code )
163+ }
164+
165+ func (cba * codeBoxAssistant ) verticallyAutoScroll (caret int , code string ) {
166+ totalHeight := cba .textAreaRef .Get (`scrollHeight` ).Int ()
167+ visibleHeight := cba .textAreaRef .Get (`clientHeight` ).Int ()
168+ if totalHeight <= visibleHeight {
169+ return // No vertical scrolling needed.
170+ }
179171
180- curTop := cba .textAreaRef .Get ("scrollTop" ).Int ()
172+ lineCount := strings .Count (code , "\n " ) + 1
173+ curLine := strings .Count (code [:caret ], "\n " ) + 1
174+ scrollTop := int (float64 (curLine ) * float64 (totalHeight ) / float64 (lineCount ))
175+
176+ curTop := cba .textAreaRef .Get (`scrollTop` ).Int ()
181177 if scrollTop < curTop {
182- cba .textAreaRef .Set ("scrollTop" , scrollTop )
183- } else {
184- height := cba .textAreaRef .Get ("clientHeight" ).Int ()
185- scrollTop = scrollTop - height + lineHeight
186-
187- println ("curLineNum:" , curLineNum , "lineHeight:" , lineHeight , "scrollTop:" , scrollTop , "curTop:" , curTop , "height:" , height )
188- if scrollTop > curTop {
189- cba .textAreaRef .Set ("scrollTop" , scrollTop )
178+ cba .textAreaRef .Set (`scrollTop` , scrollTop )
179+ } else if scrollTop -= visibleHeight ; scrollTop > curTop {
180+ cba .textAreaRef .Set (`scrollTop` , scrollTop )
181+ }
182+ }
183+
184+ func (cba * codeBoxAssistant ) horizontallyAutoScroll (caret int , code string ) {
185+ totalWidth := cba .textAreaRef .Get (`scrollWidth` ).Int ()
186+ visibleWidth := cba .textAreaRef .Get (`clientWidth` ).Int ()
187+ if totalWidth <= visibleWidth {
188+ return // No horizontal scrolling needed.
189+ }
190+
191+ longestLine := longestLineLength (code )
192+ par := strings .LastIndex (code [:caret ], "\n " ) + 1
193+ curLine := measureLineLength (code [par :caret ])
194+ scrollLeft := int (float64 (curLine ) * float64 (totalWidth ) / float64 (longestLine ))
195+
196+ curLeft := cba .textAreaRef .Get (`scrollLeft` ).Int ()
197+
198+ println (fmt .Sprintf ("HScroll: total=%d visible=%d longest=%d curLine=%d scrollLeft=%d curLeft=%d" ,
199+ totalWidth , visibleWidth , longestLine , curLine , scrollLeft , curLeft ))
200+
201+ if scrollLeft < curLeft {
202+ cba .textAreaRef .Set (`scrollLeft` , scrollLeft )
203+ } else if scrollLeft -= visibleWidth ; scrollLeft > curLeft {
204+ cba .textAreaRef .Set (`scrollLeft` , scrollLeft )
205+ }
206+ }
207+
208+ func measureLineLength (line string ) int {
209+ const tabWidth = 4
210+ tabAdjust := strings .Count (line , "\t " ) * (tabWidth - 1 )
211+ return len (line ) + tabAdjust
212+ }
213+
214+ func longestLineLength (code string ) int {
215+ longest := 0
216+ for {
217+ index := strings .Index (code , "\n " )
218+ if index < 0 {
219+ if length := measureLineLength (code ); length > longest {
220+ longest = length
221+ }
222+ break
223+ }
224+ if length := measureLineLength (code [:index ]); length > longest {
225+ longest = length
190226 }
227+ code = code [index + 1 :]
191228 }
229+ return longest
192230}
0 commit comments