- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
728 lines
17 KiB
Go
728 lines
17 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// TBrowse class implementation in Go.
|
|
// Ported from Harbour src/rtl/tbrowse.prg (2719 lines).
|
|
// This is the core subset needed for dbEdit functionality.
|
|
//
|
|
// Harbour TBrowse behavior:
|
|
// - Cursor row moves within visible area (nRowPos: 1..nRowCount)
|
|
// - When at bottom row, down() scrolls data up
|
|
// - When at top row, up() scrolls data down
|
|
// - Columns scroll horizontally when current column goes off-screen
|
|
// - stabilize() redraws the entire visible area
|
|
// - forceStable() loops until stable
|
|
package hbrtl
|
|
|
|
import (
|
|
"five/hbrt"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// TBrowse Five class registered at init.
|
|
var tbrowseClassID uint16
|
|
var tbcolumnClassID uint16
|
|
|
|
func init() {
|
|
// Register TBColumn class
|
|
tbcolCls := hbrt.NewClassDef("TBCOLUMN")
|
|
tbcolCls.AddData("CHEADING", hbrt.MakeString(""))
|
|
tbcolCls.AddData("BBLOCK", hbrt.MakeNil())
|
|
tbcolCls.AddData("CCOLSEP", hbrt.MakeString(""))
|
|
tbcolCls.AddData("CHEADSEP", hbrt.MakeString(""))
|
|
tbcolCls.AddData("CFOOTSEP", hbrt.MakeString(""))
|
|
tbcolCls.AddData("CFOOTING", hbrt.MakeString(""))
|
|
tbcolCls.AddData("NWIDTH", hbrt.MakeInt(0))
|
|
tbcolCls.AddData("CPICTURE", hbrt.MakeString(""))
|
|
tbcolCls.AddMethod("INIT", tbcolInit)
|
|
tbcolumnClassID = tbcolCls.Register()
|
|
|
|
// Register TBrowse class
|
|
cls := hbrt.NewClassDef("TBROWSE")
|
|
cls.AddData("NTOP", hbrt.MakeInt(0))
|
|
cls.AddData("NLEFT", hbrt.MakeInt(0))
|
|
cls.AddData("NBOTTOM", hbrt.MakeInt(24))
|
|
cls.AddData("NRIGHT", hbrt.MakeInt(79))
|
|
cls.AddData("ACOLUMNS", hbrt.MakeArray(0))
|
|
cls.AddData("NCOLPOS", hbrt.MakeInt(1))
|
|
cls.AddData("NROWPOS", hbrt.MakeInt(1))
|
|
cls.AddData("NROWCOUNT", hbrt.MakeInt(0))
|
|
cls.AddData("NCOLOFFSET", hbrt.MakeInt(1))
|
|
cls.AddData("BSKIPBLOCK", hbrt.MakeNil())
|
|
cls.AddData("BGOTOPBLOCK", hbrt.MakeNil())
|
|
cls.AddData("BGOBOTTOMBLOCK", hbrt.MakeNil())
|
|
cls.AddData("CHEADSEP", hbrt.MakeString("-"))
|
|
cls.AddData("CCOLSEP", hbrt.MakeString(" | "))
|
|
cls.AddData("CFOOTSEP", hbrt.MakeString(""))
|
|
cls.AddData("CCOLORSPEC", hbrt.MakeString(""))
|
|
cls.AddData("LSTABLE", hbrt.MakeBool(false))
|
|
cls.AddData("LHITTOP", hbrt.MakeBool(false))
|
|
cls.AddData("LHITBOTTOM", hbrt.MakeBool(false))
|
|
cls.AddData("LAUTOLITE", hbrt.MakeBool(true))
|
|
|
|
cls.AddMethod("INIT", tbrowseInit)
|
|
cls.AddMethod("ADDCOLUMN", tbrowseAddColumn)
|
|
cls.AddMethod("GETCOLUMN", tbrowseGetColumn)
|
|
cls.AddMethod("COLCOUNT", tbrowseColCount)
|
|
cls.AddMethod("DOWN", tbrowseDown)
|
|
cls.AddMethod("UP", tbrowseUp)
|
|
cls.AddMethod("PAGEDOWN", tbrowsePageDown)
|
|
cls.AddMethod("PAGEUP", tbrowsePageUp)
|
|
cls.AddMethod("GOTOP", tbrowseGoTop)
|
|
cls.AddMethod("GOBOTTOM", tbrowseGoBottom)
|
|
cls.AddMethod("LEFT", tbrowseLeft)
|
|
cls.AddMethod("RIGHT", tbrowseRight)
|
|
cls.AddMethod("HOME", tbrowseHome)
|
|
cls.AddMethod("END", tbrowseEnd)
|
|
cls.AddMethod("STABILIZE", tbrowseStabilize)
|
|
cls.AddMethod("FORCESTABLE", tbrowseForceStable)
|
|
cls.AddMethod("REFRESHALL", tbrowseRefreshAll)
|
|
cls.AddMethod("REFRESHCURRENT", tbrowseRefreshCurrent)
|
|
cls.AddMethod("HILITE", tbrowseHiLite)
|
|
cls.AddMethod("DEHILITE", tbrowseDeHilite)
|
|
|
|
tbrowseClassID = cls.Register()
|
|
}
|
|
|
|
// --- Helper: get object fields ---
|
|
func getObjInt(obj hbrt.Value, field string) int {
|
|
arr := obj.AsArray()
|
|
cls := hbrt.GetClass(arr.Class)
|
|
if idx := cls.FieldIndex(field); idx >= 0 {
|
|
return int(arr.Items[idx].AsNumInt())
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func setObjInt(obj hbrt.Value, field string, val int) {
|
|
arr := obj.AsArray()
|
|
cls := hbrt.GetClass(arr.Class)
|
|
if idx := cls.FieldIndex(field); idx >= 0 {
|
|
arr.Items[idx] = hbrt.MakeInt(val)
|
|
}
|
|
}
|
|
|
|
func setObjBool(obj hbrt.Value, field string, val bool) {
|
|
arr := obj.AsArray()
|
|
cls := hbrt.GetClass(arr.Class)
|
|
if idx := cls.FieldIndex(field); idx >= 0 {
|
|
arr.Items[idx] = hbrt.MakeBool(val)
|
|
}
|
|
}
|
|
|
|
func getObjBlock(obj hbrt.Value, field string) *hbrt.HbBlock {
|
|
arr := obj.AsArray()
|
|
cls := hbrt.GetClass(arr.Class)
|
|
if idx := cls.FieldIndex(field); idx >= 0 {
|
|
return arr.Items[idx].AsBlock()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getObjArray(obj hbrt.Value, field string) *hbrt.HbArray {
|
|
arr := obj.AsArray()
|
|
cls := hbrt.GetClass(arr.Class)
|
|
if idx := cls.FieldIndex(field); idx >= 0 {
|
|
return arr.Items[idx].AsArray()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getObjString(obj hbrt.Value, field string) string {
|
|
arr := obj.AsArray()
|
|
cls := hbrt.GetClass(arr.Class)
|
|
if idx := cls.FieldIndex(field); idx >= 0 {
|
|
return arr.Items[idx].AsString()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// --- TBColumn methods ---
|
|
|
|
func tbcolInit(t *hbrt.Thread) {
|
|
t.Frame(2, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
heading := t.Local(1)
|
|
block := t.Local(2)
|
|
arr := self.AsArray()
|
|
cls := hbrt.GetClass(arr.Class)
|
|
if idx := cls.FieldIndex("CHEADING"); idx >= 0 {
|
|
arr.Items[idx] = heading
|
|
}
|
|
if idx := cls.FieldIndex("BBLOCK"); idx >= 0 {
|
|
arr.Items[idx] = block
|
|
}
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
// --- TBrowse methods ---
|
|
|
|
func tbrowseInit(t *hbrt.Thread) {
|
|
t.Frame(4, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
setObjInt(self, "NTOP", int(t.Local(1).AsNumInt()))
|
|
setObjInt(self, "NLEFT", int(t.Local(2).AsNumInt()))
|
|
setObjInt(self, "NBOTTOM", int(t.Local(3).AsNumInt()))
|
|
setObjInt(self, "NRIGHT", int(t.Local(4).AsNumInt()))
|
|
nRowCount := int(t.Local(3).AsNumInt()) - int(t.Local(1).AsNumInt()) - 1
|
|
if nRowCount < 1 {
|
|
nRowCount = 1
|
|
}
|
|
setObjInt(self, "NROWCOUNT", nRowCount)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseAddColumn(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
col := t.Local(1)
|
|
cols := getObjArray(self, "ACOLUMNS")
|
|
if cols != nil {
|
|
cols.Items = append(cols.Items, col)
|
|
}
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseGetColumn(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
n := int(t.Local(1).AsNumInt())
|
|
cols := getObjArray(self, "ACOLUMNS")
|
|
if cols != nil && n >= 1 && n <= len(cols.Items) {
|
|
t.PushValue(cols.Items[n-1])
|
|
} else {
|
|
t.PushNil()
|
|
}
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseColCount(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
cols := getObjArray(self, "ACOLUMNS")
|
|
if cols != nil {
|
|
t.RetInt(int64(len(cols.Items)))
|
|
} else {
|
|
t.RetInt(0)
|
|
}
|
|
}
|
|
|
|
// --- Navigation: Harbour TBrowse behavior ---
|
|
|
|
func callSkipBlock(t *hbrt.Thread, self hbrt.Value, nRecs int) int {
|
|
blk := getObjBlock(self, "BSKIPBLOCK")
|
|
if blk == nil {
|
|
return 0
|
|
}
|
|
t.PushValue(hbrt.MakeInt(nRecs))
|
|
t.PendingParams2(1)
|
|
blk.Fn(t)
|
|
return int(t.GetRetValue().AsNumInt())
|
|
}
|
|
|
|
func tbrowseDown(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
rowPos := getObjInt(self, "NROWPOS")
|
|
rowCount := getObjInt(self, "NROWCOUNT")
|
|
|
|
nSkipped := callSkipBlock(t, self, 1)
|
|
if nSkipped > 0 {
|
|
if rowPos < rowCount {
|
|
// Cursor moves down within screen
|
|
setObjInt(self, "NROWPOS", rowPos+1)
|
|
}
|
|
// else: cursor at bottom, data scrolls (rowPos stays)
|
|
} else {
|
|
setObjBool(self, "LHITBOTTOM", true)
|
|
}
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseUp(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
rowPos := getObjInt(self, "NROWPOS")
|
|
|
|
nSkipped := callSkipBlock(t, self, -1)
|
|
if nSkipped < 0 {
|
|
if rowPos > 1 {
|
|
// Cursor moves up within screen
|
|
setObjInt(self, "NROWPOS", rowPos-1)
|
|
}
|
|
// else: cursor at top, data scrolls down (rowPos stays at 1)
|
|
} else {
|
|
setObjBool(self, "LHITTOP", true)
|
|
}
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowsePageDown(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
rowCount := getObjInt(self, "NROWCOUNT")
|
|
rowPos := getObjInt(self, "NROWPOS")
|
|
|
|
// Try to skip a full page from current position
|
|
nSkipped := callSkipBlock(t, self, rowCount)
|
|
|
|
if nSkipped <= 0 {
|
|
// Already at bottom: move cursor to last data row
|
|
setObjBool(self, "LHITBOTTOM", true)
|
|
// Find last data row: skip forward from first visible row
|
|
callSkipBlock(t, self, -(rowPos - 1)) // go to first visible
|
|
count := 1
|
|
for {
|
|
s := callSkipBlock(t, self, 1)
|
|
if s <= 0 {
|
|
break
|
|
}
|
|
count++
|
|
if count >= rowCount {
|
|
break
|
|
}
|
|
}
|
|
// Now skip back to put cursor at last row
|
|
callSkipBlock(t, self, -(count - 1)) // back to first visible
|
|
callSkipBlock(t, self, count-1) // forward to last data
|
|
setObjInt(self, "NROWPOS", count)
|
|
} else if nSkipped < rowCount {
|
|
// Partial page: cursor stays at row 1, but we didn't move a full page
|
|
// Skip back so screen doesn't shift too much — just move cursor down
|
|
callSkipBlock(t, self, -nSkipped) // undo the skip
|
|
// Instead: move cursor to last row
|
|
callSkipBlock(t, self, -(rowPos - 1)) // go to first visible
|
|
count := 1
|
|
for {
|
|
s := callSkipBlock(t, self, 1)
|
|
if s <= 0 {
|
|
break
|
|
}
|
|
count++
|
|
if count >= rowCount {
|
|
break
|
|
}
|
|
}
|
|
callSkipBlock(t, self, -(count - 1))
|
|
callSkipBlock(t, self, count-1)
|
|
setObjInt(self, "NROWPOS", count)
|
|
}
|
|
// else: full page skip succeeded, rowPos stays
|
|
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowsePageUp(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
rowCount := getObjInt(self, "NROWCOUNT")
|
|
nSkipped := callSkipBlock(t, self, -rowCount)
|
|
if nSkipped >= 0 {
|
|
setObjBool(self, "LHITTOP", true)
|
|
}
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseGoTop(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
blk := getObjBlock(self, "BGOTOPBLOCK")
|
|
if blk != nil {
|
|
t.PendingParams2(0)
|
|
blk.Fn(t)
|
|
}
|
|
setObjInt(self, "NROWPOS", 1)
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseGoBottom(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
blk := getObjBlock(self, "BGOBOTTOMBLOCK")
|
|
if blk != nil {
|
|
t.PendingParams2(0)
|
|
blk.Fn(t)
|
|
}
|
|
setObjInt(self, "NROWPOS", getObjInt(self, "NROWCOUNT"))
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseLeft(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
pos := getObjInt(self, "NCOLPOS")
|
|
if pos > 1 {
|
|
setObjInt(self, "NCOLPOS", pos-1)
|
|
off := getObjInt(self, "NCOLOFFSET")
|
|
if pos-1 < off {
|
|
setObjInt(self, "NCOLOFFSET", pos-1)
|
|
}
|
|
}
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseRight(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
pos := getObjInt(self, "NCOLPOS")
|
|
cols := getObjArray(self, "ACOLUMNS")
|
|
if cols != nil && pos < len(cols.Items) {
|
|
setObjInt(self, "NCOLPOS", pos+1)
|
|
}
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseHome(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
setObjInt(self, "NCOLPOS", 1)
|
|
setObjInt(self, "NCOLOFFSET", 1)
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseEnd(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
cols := getObjArray(self, "ACOLUMNS")
|
|
if cols != nil {
|
|
setObjInt(self, "NCOLPOS", len(cols.Items))
|
|
}
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
// --- Display ---
|
|
|
|
func tbrowseStabilize(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
|
|
nTop := getObjInt(self, "NTOP")
|
|
nLeft := getObjInt(self, "NLEFT")
|
|
nRight := getObjInt(self, "NRIGHT")
|
|
nRowCount := getObjInt(self, "NROWCOUNT")
|
|
nRowPos := getObjInt(self, "NROWPOS")
|
|
nColPos := getObjInt(self, "NCOLPOS")
|
|
nColOffset := getObjInt(self, "NCOLOFFSET")
|
|
cols := getObjArray(self, "ACOLUMNS")
|
|
headSep := getObjString(self, "CHEADSEP")
|
|
colSep := getObjString(self, "CCOLSEP")
|
|
screenWidth := nRight - nLeft + 1
|
|
|
|
if cols == nil || len(cols.Items) == 0 {
|
|
t.PushBool(true)
|
|
t.RetValue()
|
|
return
|
|
}
|
|
|
|
// Adjust colOffset for visibility
|
|
if nColPos < nColOffset {
|
|
nColOffset = nColPos
|
|
}
|
|
for {
|
|
used := 0
|
|
visible := false
|
|
for i := nColOffset - 1; i < len(cols.Items); i++ {
|
|
w := tbColWidth(cols.Items[i])
|
|
if i > nColOffset-1 && len(colSep) > 0 {
|
|
used += len(colSep)
|
|
}
|
|
if used+w > screenWidth {
|
|
break
|
|
}
|
|
used += w
|
|
if i == nColPos-1 {
|
|
visible = true
|
|
break
|
|
}
|
|
}
|
|
if visible || nColOffset >= nColPos {
|
|
break
|
|
}
|
|
nColOffset++
|
|
}
|
|
setObjInt(self, "NCOLOFFSET", nColOffset)
|
|
|
|
// Draw header
|
|
fmt.Printf("\033[%d;%dH", nTop+1, nLeft+1)
|
|
x := 0
|
|
for i := nColOffset - 1; i < len(cols.Items) && x < screenWidth; i++ {
|
|
w := tbColWidth(cols.Items[i])
|
|
if i > nColOffset-1 && len(colSep) > 0 {
|
|
if x+len(colSep) >= screenWidth {
|
|
break
|
|
}
|
|
fmt.Printf("\033[7m%s\033[0m", colSep)
|
|
x += len(colSep)
|
|
}
|
|
if x+w > screenWidth {
|
|
w = screenWidth - x
|
|
}
|
|
heading := tbColHeading(cols.Items[i])
|
|
cell := padRightS(heading, w)
|
|
if i == nColPos-1 {
|
|
fmt.Printf("\033[1;7m%s\033[0m", cell)
|
|
} else {
|
|
fmt.Printf("\033[7m%s\033[0m", cell)
|
|
}
|
|
x += w
|
|
}
|
|
for x < screenWidth {
|
|
fmt.Printf("\033[7m \033[0m")
|
|
x++
|
|
}
|
|
|
|
// Header separator
|
|
hasHeadSep := len(headSep) > 0
|
|
if hasHeadSep {
|
|
fmt.Printf("\033[%d;%dH\033[36m%s\033[0m", nTop+2, nLeft+1, padRightS(strings.Repeat(string(headSep[0]), screenWidth), screenWidth))
|
|
}
|
|
|
|
// Skip back to first visible row from current position
|
|
actualBack := callSkipBlock(t, self, -(nRowPos - 1))
|
|
// actualBack is negative (e.g., -4 means we went back 4)
|
|
actualFirstToPos := -(actualBack) // how many rows from first to curPos
|
|
|
|
// If we couldn't go back enough, adjust rowPos
|
|
if actualFirstToPos < nRowPos-1 {
|
|
nRowPos = actualFirstToPos + 1
|
|
setObjInt(self, "NROWPOS", nRowPos)
|
|
}
|
|
|
|
// Draw data rows
|
|
dataStartRow := nTop + 2
|
|
if hasHeadSep {
|
|
dataStartRow = nTop + 3
|
|
}
|
|
|
|
actualDataRows := 0 // count of real data rows drawn
|
|
pastEOF := false
|
|
for r := 1; r <= nRowCount; r++ {
|
|
fmt.Printf("\033[%d;%dH", dataStartRow+r-1, nLeft+1)
|
|
x = 0
|
|
|
|
if pastEOF {
|
|
fmt.Printf("%-*s", screenWidth, "~")
|
|
} else {
|
|
actualDataRows = r
|
|
for i := nColOffset - 1; i < len(cols.Items) && x < screenWidth; i++ {
|
|
w := tbColWidth(cols.Items[i])
|
|
if i > nColOffset-1 && len(colSep) > 0 {
|
|
if x+len(colSep) >= screenWidth {
|
|
break
|
|
}
|
|
fmt.Print(colSep)
|
|
x += len(colSep)
|
|
}
|
|
if x+w > screenWidth {
|
|
w = screenWidth - x
|
|
}
|
|
val := tbEvalBlock(t, cols.Items[i])
|
|
cell := padRightS(val, w)
|
|
|
|
if r == nRowPos && i == nColPos-1 {
|
|
fmt.Printf("\033[7m%s\033[0m", cell)
|
|
} else if r == nRowPos {
|
|
fmt.Printf("\033[47;30m%s\033[0m", cell)
|
|
} else {
|
|
fmt.Print(cell)
|
|
}
|
|
x += w
|
|
}
|
|
for x < screenWidth {
|
|
if r == nRowPos {
|
|
fmt.Printf("\033[47;30m \033[0m")
|
|
} else {
|
|
fmt.Print(" ")
|
|
}
|
|
x++
|
|
}
|
|
|
|
if r < nRowCount {
|
|
skipped := callSkipBlock(t, self, 1)
|
|
if skipped == 0 {
|
|
pastEOF = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clamp rowPos to actual data rows
|
|
if nRowPos > actualDataRows && actualDataRows > 0 {
|
|
nRowPos = actualDataRows
|
|
setObjInt(self, "NROWPOS", nRowPos)
|
|
}
|
|
|
|
// Restore to current row position: skip back from wherever we are to first, then forward to rowPos
|
|
if !pastEOF {
|
|
callSkipBlock(t, self, -(nRowCount - nRowPos))
|
|
} else {
|
|
// We're at last data record. Skip back to first visible, then forward to rowPos.
|
|
callSkipBlock(t, self, -(actualDataRows - 1))
|
|
if nRowPos > 1 {
|
|
callSkipBlock(t, self, nRowPos-1)
|
|
}
|
|
}
|
|
|
|
setObjBool(self, "LSTABLE", true)
|
|
setObjBool(self, "LHITTOP", false)
|
|
setObjBool(self, "LHITBOTTOM", false)
|
|
|
|
t.PushBool(true)
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseForceStable(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
// Just call stabilize once (simplified)
|
|
self := t.GetSelf()
|
|
_ = self
|
|
tbrowseStabilize(t)
|
|
// Discard stabilize result, push self
|
|
t.Pop()
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseRefreshAll(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseRefreshCurrent(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
self := t.GetSelf()
|
|
setObjBool(self, "LSTABLE", false)
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseHiLite(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
func tbrowseDeHilite(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
t.PushSelf()
|
|
t.RetValue()
|
|
}
|
|
|
|
// --- Helpers ---
|
|
|
|
func tbColWidth(colVal hbrt.Value) int {
|
|
colArr := colVal.AsArray()
|
|
if colArr == nil {
|
|
return 10
|
|
}
|
|
cls := hbrt.GetClass(colArr.Class)
|
|
if cls == nil {
|
|
return 10
|
|
}
|
|
if idx := cls.FieldIndex("NWIDTH"); idx >= 0 {
|
|
w := int(colArr.Items[idx].AsNumInt())
|
|
if w > 0 {
|
|
return w
|
|
}
|
|
}
|
|
if idx := cls.FieldIndex("CHEADING"); idx >= 0 {
|
|
w := len(colArr.Items[idx].AsString())
|
|
if w < 10 {
|
|
w = 10
|
|
}
|
|
return w
|
|
}
|
|
return 10
|
|
}
|
|
|
|
func tbColHeading(colVal hbrt.Value) string {
|
|
colArr := colVal.AsArray()
|
|
if colArr == nil {
|
|
return ""
|
|
}
|
|
cls := hbrt.GetClass(colArr.Class)
|
|
if cls == nil {
|
|
return ""
|
|
}
|
|
if idx := cls.FieldIndex("CHEADING"); idx >= 0 {
|
|
return colArr.Items[idx].AsString()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func tbEvalBlock(t *hbrt.Thread, colVal hbrt.Value) string {
|
|
colArr := colVal.AsArray()
|
|
if colArr == nil {
|
|
return ""
|
|
}
|
|
cls := hbrt.GetClass(colArr.Class)
|
|
if cls == nil {
|
|
return ""
|
|
}
|
|
if idx := cls.FieldIndex("BBLOCK"); idx >= 0 {
|
|
blk := colArr.Items[idx].AsBlock()
|
|
if blk != nil {
|
|
t.PendingParams2(0)
|
|
blk.Fn(t)
|
|
v := t.GetRetValue()
|
|
return strings.TrimRight(valueToDisplay(v), " ")
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func padRightS(s string, w int) string {
|
|
if len(s) >= w {
|
|
return s[:w]
|
|
}
|
|
return s + strings.Repeat(" ", w-len(s))
|
|
}
|