@@ -3,16 +3,20 @@ package page
33import (
44 "fmt"
55 "go/scanner"
6+ "strings"
7+
8+ "github.com/gopherjs/gopherjs/compiler/errlist"
69
710 "github.com/gopherjs/gopherjs.github.io/playground/internal/common"
811 "github.com/gopherjs/gopherjs.github.io/playground/internal/react"
912)
1013
1114const (
12- errType = `err`
13- textType = `text`
15+ stderrType = `stderr`
16+ stdoutType = `stdout`
17+ systemType = `system`
1418 typeKey = `type`
15- contextKey = `context `
19+ contentKey = `content `
1620)
1721
1822func Output (setOutput react.Func ) common.Output {
@@ -25,29 +29,77 @@ func (o *outputImpl) Clear() {
2529 o .setOutput .Invoke ([]any {})
2630}
2731
32+ func ungroupError (err error ) []error {
33+ switch t := err .(type ) {
34+ case scanner.ErrorList :
35+ errs := make ([]error , len (t ))
36+ for i , entry := range t {
37+ errs [i ] = entry
38+ }
39+ return errs
40+ case errlist.ErrorList :
41+ return t
42+ default :
43+ return []error {err }
44+ }
45+ }
46+
47+ func appendContent (items []any , itemTyp , content string , startNewLine bool ) []any {
48+ // Check for a form feed ("\x0c") to clear the output.
49+ if index := strings .LastIndexByte (content , '\x0c' ); index >= 0 {
50+ items = []any {}
51+ content = content [index :]
52+ }
53+
54+ // Attempt to append the content to a prior item if the type matches,
55+ // also prepend a new line to the content if needed and requested.
56+ if maxItem := len (items ) - 1 ; maxItem >= 0 {
57+ lastItem := items [maxItem ].(map [string ]any )
58+
59+ lastContent := lastItem [contentKey ].(string )
60+ if startNewLine && ! strings .HasSuffix (lastContent , "\n " ) {
61+ content = "\n " + content
62+ }
63+
64+ if lastItem [typeKey ] == itemTyp {
65+ lastItem [contentKey ] = lastContent + content
66+ return items
67+ }
68+ }
69+
70+ // Append new type of content.
71+ return append (items , map [string ]any {typeKey : itemTyp , contentKey : content })
72+ }
73+
2874func (o * outputImpl ) AddError (err error ) {
2975 o .setOutput .Invoke (func (items []any ) []any {
30- // TODO(grantnelson-wf): Need to move errlist.ErrorList out of internal so that we can split it out here
31- if list , ok := err .(scanner.ErrorList ); ok {
32- for _ , entry := range list {
33- items = append (items , map [string ]any {typeKey : errType , contextKey : entry .Error ()})
76+ content := ``
77+ for _ , entry := range ungroupError (err ) {
78+ text := entry .Error ()
79+ content += text
80+ if ! strings .HasSuffix (text , "\n " ) {
81+ content += "\n "
3482 }
35- return items
3683 }
37- return append (items , map [ string ] any { typeKey : errType , contextKey : err . Error ()} )
84+ return appendContent (items , stderrType , content , true )
3885 })
3986}
4087
41- func (o * outputImpl ) AddOutput (out string ) {
88+ func (o * outputImpl ) AppendOut (out string ) {
4289 o .setOutput .Invoke (func (items []any ) []any {
43- if maxItem := len (items ) - 1 ; maxItem >= 0 {
44- lastItem := items [maxItem ].(map [string ]any )
45- if lastItem [typeKey ] == textType {
46- lastItem [contextKey ] = lastItem [contextKey ].(string ) + out
47- return items
48- }
49- }
50- return append (items , map [string ]any {typeKey : textType , contextKey : out })
90+ return appendContent (items , stdoutType , out , false )
91+ })
92+ }
93+
94+ func (o * outputImpl ) AppendErr (err string ) {
95+ o .setOutput .Invoke (func (items []any ) []any {
96+ return appendContent (items , stderrType , err , false )
97+ })
98+ }
99+
100+ func (o * outputImpl ) AddSystem (sys string ) {
101+ o .setOutput .Invoke (func (items []any ) []any {
102+ return appendContent (items , systemType , sys + "\n " , true )
51103 })
52104}
53105
@@ -64,24 +116,21 @@ func outputBoxComponent(props react.Props) *react.Element {
64116 outputBoxRef = react .UseRef ()
65117 )
66118
119+ // Add effect so that if there are no stderr's in the output, scroll to the bottom.
67120 react .UseEffect (func () {
68- // If there are only errors, scroll to the top,
69- // otherwise scroll to the bottom.
70- outputBox := outputBoxRef .Current ()
71- scrollTop := 0
72- if hasNonErrors (output ) {
73- scrollTop = outputBox .Get (`scrollHeight` ).Int ()
121+ if ! hasErrors (output ) {
122+ outputBox := outputBoxRef .Current ()
123+ scrollTop := outputBox .Get (`scrollHeight` ).Int ()
124+ outputBox .Set (`scrollTop` , scrollTop )
74125 }
75- outputBox .Set (`scrollTop` , scrollTop )
76126 }, []any {output })
77127
78- children := make ([]react.Node , 0 , len (output ))
128+ children := make ([]react.Node , len (output ))
79129 for i , item := range output {
80130 itemMap := item .(map [string ]any )
81- children = append (children , outputLine (
82- i , itemMap [typeKey ] == errType ,
83- itemMap [contextKey ].(string ),
84- ))
131+ classType := itemMap [typeKey ].(string )
132+ content := itemMap [contentKey ].(string )
133+ children [i ] = outputLine (i , classType , content )
85134 }
86135
87136 return react .Div (react.Props {
@@ -92,42 +141,35 @@ func outputBoxComponent(props react.Props) *react.Element {
92141 }, children ... ))
93142}
94143
95- // hasNonErrors determines if any output is not an error,
96- // otherwise the list is empty or only contains errors.
97- func hasNonErrors (output []any ) bool {
144+ // hasErrors determines if any output is an error.
145+ func hasErrors (output []any ) bool {
98146 for _ , item := range output {
99- if item .(map [string ]any )[typeKey ] != errType {
147+ if item .(map [string ]any )[typeKey ] == stderrType {
100148 return true
101149 }
102150 }
103151 return false
104152}
105153
106- // outputLine creates a React element for a single output line .
154+ // outputLine creates a React element for a span of output content .
107155// The index is used to create a unique ID for the line so it should
108156// be the line's position in the output list.
109- func outputLine (index int , isError bool , content string ) * react.Element {
157+ func outputLine (index int , classType string , content string ) * react.Element {
110158 return react .CreateElement (outputLineComponent , react.Props {
111- `index` : index ,
112- `isError ` : isError ,
113- `content` : content ,
159+ `index` : index ,
160+ `classType ` : classType ,
161+ `content` : content ,
114162 })
115163}
116164
117165func outputLineComponent (props react.Props ) * react.Element {
118166 var (
119- index = props .GetInt (`index` )
120- isError = props .GetBool ( `isError ` )
121- content = props .GetString (`content` )
167+ index = props .GetInt (`index` )
168+ classType = props .GetString ( `classType ` )
169+ content = props .GetString (`content` )
122170 )
123-
124- classType := `output-text`
125- if isError {
126- classType = `output-err`
127- }
128171 id := fmt .Sprintf (`output-item-%d` , index )
129-
130- return react .Pre (react.Props {
172+ return react .Span (react.Props {
131173 `className` : classType ,
132174 `id` : id ,
133175 }, content )
0 commit comments