diff --git a/spx-gui/src/components/editor/panels/ConsolePanel.vue b/spx-gui/src/components/editor/panels/ConsolePanel.vue index 08a5851b1..1bd496918 100644 --- a/spx-gui/src/components/editor/panels/ConsolePanel.vue +++ b/spx-gui/src/components/editor/panels/ConsolePanel.vue @@ -24,17 +24,18 @@ function humanizeTime(time: number) { } function getOutputSourceLocation(output: RuntimeOutput) { - console.warn('TODO: use `output.source`', output) // TODO: see details in https://github.com/goplus/builder/issues/1096 + if (output.source == null) return null return { - file: { uri: 'file:///main.spx' }, - position: { line: 1, column: 1 } + file: output.source.textDocument, + position: output.source.range.start } } -function getOutputSourceLocationText(runtime: RuntimeOutput) { - const { file, position } = getOutputSourceLocation(runtime) - const codeFileName = i18n.t(textDocumentId2CodeFileName(file)) - return `${codeFileName}:${position.line}` +function getOutputSourceLocationText(output: RuntimeOutput) { + const location = getOutputSourceLocation(output) + if (location == null) return '' + const codeFileName = i18n.t(textDocumentId2CodeFileName(location.file)) + return `${codeFileName}:${location.position.line}` } @@ -60,9 +61,9 @@ function getOutputSourceLocationText(runtime: RuntimeOutput) { :class="`kind-${output.kind}`" > {{ humanizeTime(output.time) }} - {{ - getOutputSourceLocationText(output) - }} + + {{ getOutputSourceLocationText(output) }} + {{ output.message }} diff --git a/spx-gui/src/components/editor/preview/InPlaceRunner.vue b/spx-gui/src/components/editor/preview/InPlaceRunner.vue index b7648d5c6..e014bbb37 100644 --- a/spx-gui/src/components/editor/preview/InPlaceRunner.vue +++ b/spx-gui/src/components/editor/preview/InPlaceRunner.vue @@ -14,7 +14,42 @@ const editorCtx = useEditorCtx() const projectRunnerRef = ref>() function handleConsole(type: 'log' | 'warn', args: unknown[]) { - // TODO: parse source + if (type === 'log' && args.length === 1 && typeof args[0] === 'string') { + try { + // Log format is determined by `github.com/goplus/builder/tools/ispx/main.go:logWithCallerInfo`. + // + // Example: + // { + // "time": "2025-01-14T11:50:34.808+08:00", + // "level": "INFO", + // "msg": "Hello, world!\n", + // "function": "main.(*MySprite).Main.func1", + // "file": "MySprite.spx", + // "line": 2 + // } + const logMsg = JSON.parse(args[0]) + if (logMsg.level === 'INFO') { + editorCtx.runtime.addOutput({ + kind: RuntimeOutputKind.Log, + time: logMsg.time, + message: logMsg.msg, + source: { + textDocument: { + uri: `file:///${logMsg.file}` + }, + range: { + start: { line: logMsg.line, column: 1 }, + end: { line: logMsg.line, column: 1 } + } + } + }) + return + } + } catch { + // If parsing fails, fall through to default handling. + } + } + editorCtx.runtime.addOutput({ kind: type === 'warn' ? RuntimeOutputKind.Error : RuntimeOutputKind.Log, time: Date.now(), diff --git a/tools/ispx/go.mod b/tools/ispx/go.mod index 0efba00d2..83875d7c3 100644 --- a/tools/ispx/go.mod +++ b/tools/ispx/go.mod @@ -3,7 +3,7 @@ module github.com/goplus/builder/ispx go 1.21 require ( - github.com/goplus/igop v0.28.0 + github.com/goplus/igop v0.28.1 github.com/goplus/reflectx v1.2.2 github.com/goplus/spx v1.0.1-0.20241029011511-845f2c0e2e74 github.com/hajimehoshi/ebiten/v2 v2.8.0-alpha.3 diff --git a/tools/ispx/go.sum b/tools/ispx/go.sum index 2e5b09b8f..de831969a 100644 --- a/tools/ispx/go.sum +++ b/tools/ispx/go.sum @@ -31,8 +31,8 @@ github.com/goplus/gogen v1.16.6-0.20250112152508-dc8cddfd52df h1:YIcLq6a7eGp3niB github.com/goplus/gogen v1.16.6-0.20250112152508-dc8cddfd52df/go.mod h1:6TQYbabXDF9LCdDkOOzHmfg1R4ENfXQ3XpHa9RhTSD8= github.com/goplus/gop v1.2.0-pre.1.0.20250112163018-5fb12b1b2972 h1:wA4+I+DDfoNwR+zCnaeckt5pxZgLAuaKvVkC+T9GdiQ= github.com/goplus/gop v1.2.0-pre.1.0.20250112163018-5fb12b1b2972/go.mod h1:lcW75c0a5v361jId1Vxs4lRrsasWsQDH0k3dG3Z7wH0= -github.com/goplus/igop v0.28.0 h1:hbcZR2Z9Ax2y29ndkYZmXx5KE9YQPy/OV7mb9szKGHQ= -github.com/goplus/igop v0.28.0/go.mod h1:ObprI7cQUSZN0LYeUX0+MqzpQ0MmeS5o1/ToDU+jfTg= +github.com/goplus/igop v0.28.1 h1:I+T006IxENdHPn5vvebmSwoZp0BAy0WUybnnyB+tZWM= +github.com/goplus/igop v0.28.1/go.mod h1:ObprI7cQUSZN0LYeUX0+MqzpQ0MmeS5o1/ToDU+jfTg= github.com/goplus/llgo v0.9.9/go.mod h1:udcq+s6tGOdhJq7I8fXPTv4qT2j17/KrlvtcJrMZAoM= github.com/goplus/llvm v0.8.0/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= github.com/goplus/mod v0.13.10/go.mod h1:HDuPZgpWiaTp3PUolFgsiX+Q77cbUWB/mikVHfYND3c= diff --git a/tools/ispx/main.go b/tools/ispx/main.go index 2c139c21a..3e7c36df5 100644 --- a/tools/ispx/main.go +++ b/tools/ispx/main.go @@ -6,7 +6,10 @@ package main import ( "archive/zip" "bytes" + "fmt" "log" + "log/slog" + "os" "syscall/js" _ "github.com/goplus/builder/ispx/pkg/github.com/goplus/spx" @@ -34,6 +37,20 @@ func loadData(this js.Value, args []js.Value) interface{} { return nil } +var logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)) + +func logWithCallerInfo(msg string, frame *igop.Frame) { + if frs := frame.CallerFrames(); len(frs) > 0 { + fr := frs[0] + logger.Info( + msg, + "function", fr.Function, + "file", fr.File, + "line", fr.Line, + ) + } +} + func main() { js.Global().Set("goLoadData", js.FuncOf(loadData)) @@ -57,7 +74,7 @@ func main() { // Register patch for spx to support functions with generic type like `Gopt_Game_Gopx_GetWidget`. // See details in https://github.com/goplus/builder/issues/765#issuecomment-2313915805 - err = gopbuild.RegisterPackagePatch(ctx, "github.com/goplus/spx", ` + if err := gopbuild.RegisterPackagePatch(ctx, "github.com/goplus/spx", ` package spx import ( @@ -72,11 +89,26 @@ func Gopt_Game_Gopx_GetWidget[T any](sg ShapeGetter, name string) *T { panic("GetWidget: type mismatch") } } -`) - if err != nil { - log.Fatalln("Failed to register package patch:", err) +`); err != nil { + log.Fatalln("Failed to register package patch for github.com/goplus/spx:", err) } + ctx.RegisterExternal("fmt.Print", func(frame *igop.Frame, a ...interface{}) (n int, err error) { + msg := fmt.Sprint(a...) + logWithCallerInfo(msg, frame) + return len(msg), nil + }) + ctx.RegisterExternal("fmt.Printf", func(frame *igop.Frame, format string, a ...interface{}) (n int, err error) { + msg := fmt.Sprintf(format, a...) + logWithCallerInfo(msg, frame) + return len(msg), nil + }) + ctx.RegisterExternal("fmt.Println", func(frame *igop.Frame, a ...interface{}) (n int, err error) { + msg := fmt.Sprintln(a...) + logWithCallerInfo(msg, frame) + return len(msg), nil + }) + source, err := gopbuild.BuildFSDir(ctx, fs, "") if err != nil { log.Fatalln("Failed to build Go+ source:", err)