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)