Skip to content

Commit ee8796e

Browse files
Auto scrolling
1 parent f824777 commit ee8796e

File tree

3 files changed

+157
-160
lines changed

3 files changed

+157
-160
lines changed

playground/internal/react/codeBox.go

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package react
22

33
import (
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 {
6766
func (cba *codeBoxAssistant) onInput(e *js.Object) {
6867
code := e.Get(`target`).Get(`value`).String()
6968
cba.setCode(code)
70-
//cba.updateLineNumbers(code)
7169
}
7270

7371
func (cba *codeBoxAssistant) onKeyDown(e *js.Object) {
@@ -77,15 +75,15 @@ func (cba *codeBoxAssistant) onKeyDown(e *js.Object) {
7775
}
7876

7977
func (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

8582
func (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-
166151
func (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
}

playground/playground.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ pre,
6666
padding: 0;
6767
padding-left: 5px;
6868
resize: none;
69+
tab-size: 4;
6970
white-space: nowrap;
7071
}
7172

0 commit comments

Comments
 (0)