- 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>
263 lines
5.4 KiB
Go
263 lines
5.4 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// dbEdit() — Harbour-compatible implementation using TBrowse.
|
|
// Same pattern as Harbour's dbedit.prg:
|
|
// oBrowse := TBrowseDB() → addColumn → loop { stabilize + inkey + navigate }
|
|
package hbrtl
|
|
|
|
import (
|
|
"five/hbrt"
|
|
"five/hbrdd"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Key constants
|
|
const (
|
|
kUP = 5
|
|
kDOWN = 24
|
|
kLEFT = 19
|
|
kRIGHT = 4
|
|
kPGUP = 18
|
|
kPGDN = 3
|
|
kHOME = 1
|
|
kEND = 6
|
|
kESC = 27
|
|
)
|
|
|
|
// DbEdit implements Harbour dbEdit() function.
|
|
func DbEdit(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
nTop := 0
|
|
nLeft := 0
|
|
nBottom := 22
|
|
nRight := 79
|
|
|
|
if nParams >= 1 {
|
|
nTop = int(t.Local(1).AsNumInt())
|
|
}
|
|
if nParams >= 2 {
|
|
nLeft = int(t.Local(2).AsNumInt())
|
|
}
|
|
if nParams >= 3 {
|
|
nBottom = int(t.Local(3).AsNumInt())
|
|
}
|
|
if nParams >= 4 {
|
|
nRight = int(t.Local(4).AsNumInt())
|
|
}
|
|
|
|
wa := getDbEditWA(t)
|
|
if wa == nil {
|
|
t.PushBool(false)
|
|
t.RetValue()
|
|
return
|
|
}
|
|
area := wa.Current()
|
|
if area == nil {
|
|
t.PushBool(false)
|
|
t.RetValue()
|
|
return
|
|
}
|
|
|
|
// Create TBrowse object
|
|
oBrowse := hbrt.NewObject(hbrt.FindClass("TBROWSE").ID)
|
|
arr := oBrowse.AsArray()
|
|
cls := hbrt.GetClass(arr.Class)
|
|
|
|
setF(arr, cls, "NTOP", hbrt.MakeInt(nTop))
|
|
setF(arr, cls, "NLEFT", hbrt.MakeInt(nLeft))
|
|
setF(arr, cls, "NBOTTOM", hbrt.MakeInt(nBottom))
|
|
setF(arr, cls, "NRIGHT", hbrt.MakeInt(nRight))
|
|
rowCount := nBottom - nTop - 1
|
|
if rowCount < 1 {
|
|
rowCount = 1
|
|
}
|
|
setF(arr, cls, "NROWCOUNT", hbrt.MakeInt(rowCount))
|
|
setF(arr, cls, "CHEADSEP", hbrt.MakeString("-"))
|
|
setF(arr, cls, "CCOLSEP", hbrt.MakeString(" | "))
|
|
|
|
// Skip/GoTop/GoBottom blocks
|
|
setF(arr, cls, "BSKIPBLOCK", hbrt.MakeBlock(func(bt *hbrt.Thread) {
|
|
bt.Frame(1, 0)
|
|
defer bt.EndProc()
|
|
n := int(bt.Local(1).AsNumInt())
|
|
bt.RetInt(int64(dbSkipBlock(wa, n)))
|
|
}, 0))
|
|
setF(arr, cls, "BGOTOPBLOCK", hbrt.MakeBlock(func(bt *hbrt.Thread) {
|
|
bt.Frame(0, 0)
|
|
defer bt.EndProc()
|
|
if a := wa.Current(); a != nil {
|
|
a.GoTop()
|
|
}
|
|
bt.RetNil()
|
|
}, 0))
|
|
setF(arr, cls, "BGOBOTTOMBLOCK", hbrt.MakeBlock(func(bt *hbrt.Thread) {
|
|
bt.Frame(0, 0)
|
|
defer bt.EndProc()
|
|
if a := wa.Current(); a != nil {
|
|
a.GoBottom()
|
|
}
|
|
bt.RetNil()
|
|
}, 0))
|
|
|
|
// Add columns
|
|
colsArr := getFA(arr, cls, "ACOLUMNS")
|
|
for i := 0; i < area.FieldCount(); i++ {
|
|
fi := area.GetFieldInfo(i)
|
|
idx := i
|
|
|
|
oCol := hbrt.NewObject(hbrt.FindClass("TBCOLUMN").ID)
|
|
ca := oCol.AsArray()
|
|
cc := hbrt.GetClass(ca.Class)
|
|
setF(ca, cc, "CHEADING", hbrt.MakeString(fi.Name))
|
|
setF(ca, cc, "BBLOCK", hbrt.MakeBlock(func(bt *hbrt.Thread) {
|
|
bt.Frame(0, 0)
|
|
defer bt.EndProc()
|
|
val, _ := area.GetValue(idx)
|
|
bt.PushValue(val)
|
|
bt.RetValue()
|
|
}, 0))
|
|
w := fi.Len
|
|
if w < len(fi.Name) {
|
|
w = len(fi.Name)
|
|
}
|
|
if w > 25 {
|
|
w = 25
|
|
}
|
|
if w < 4 {
|
|
w = 4
|
|
}
|
|
setF(ca, cc, "NWIDTH", hbrt.MakeInt(w))
|
|
colsArr.Items = append(colsArr.Items, oCol)
|
|
}
|
|
|
|
// Set raw terminal mode
|
|
dbEditRawOn()
|
|
defer dbEditRawOff()
|
|
fmt.Print("\033[2J\033[H\033[?25l")
|
|
defer fmt.Print("\033[?25h\033[0m\n")
|
|
|
|
area.GoTop()
|
|
screenWidth := nRight - nLeft + 1
|
|
rc, _ := area.RecCount()
|
|
|
|
// Main loop — same as Harbour dbEdit
|
|
for {
|
|
// stabilize
|
|
t.PushValue(oBrowse)
|
|
t.Send("STABILIZE", 0)
|
|
t.Pop2()
|
|
|
|
// Status bar
|
|
colPos := getFI(arr, cls, "NCOLPOS")
|
|
colName := ""
|
|
if colPos >= 1 && colPos <= len(colsArr.Items) {
|
|
ca2 := colsArr.Items[colPos-1].AsArray()
|
|
cc2 := hbrt.GetClass(ca2.Class)
|
|
colName = getFS(ca2, cc2, "CHEADING")
|
|
}
|
|
eof := ""
|
|
if area.EOF() {
|
|
eof = " EOF"
|
|
}
|
|
status := fmt.Sprintf(" Rec %d/%d [%s]%s Arrows PgUp/Dn Home/End ESC=quit",
|
|
area.RecNo(), rc, strings.TrimSpace(colName), eof)
|
|
fmt.Printf("\033[%d;%dH\033[7m%-*s\033[0m", nBottom+2, nLeft+1, screenWidth, status)
|
|
|
|
// Read key
|
|
key := dbEditReadKey()
|
|
|
|
switch key {
|
|
case 'A': // ESC[A = Up
|
|
t.PushValue(oBrowse)
|
|
t.Send("UP", 0)
|
|
t.Pop2()
|
|
case 'B': // ESC[B = Down
|
|
t.PushValue(oBrowse)
|
|
t.Send("DOWN", 0)
|
|
t.Pop2()
|
|
case 'D': // ESC[D = Left
|
|
t.PushValue(oBrowse)
|
|
t.Send("LEFT", 0)
|
|
t.Pop2()
|
|
case 'C': // ESC[C = Right
|
|
t.PushValue(oBrowse)
|
|
t.Send("RIGHT", 0)
|
|
t.Pop2()
|
|
case '5': // PgUp
|
|
t.PushValue(oBrowse)
|
|
t.Send("PAGEUP", 0)
|
|
t.Pop2()
|
|
case '6': // PgDn
|
|
t.PushValue(oBrowse)
|
|
t.Send("PAGEDOWN", 0)
|
|
t.Pop2()
|
|
case 'H': // Home
|
|
t.PushValue(oBrowse)
|
|
t.Send("GOTOP", 0)
|
|
t.Pop2()
|
|
case 'F': // End
|
|
t.PushValue(oBrowse)
|
|
t.Send("GOBOTTOM", 0)
|
|
t.Pop2()
|
|
case kESC, 'q', 'Q':
|
|
fmt.Print("\033[2J\033[H")
|
|
t.PushBool(true)
|
|
t.RetValue()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func dbEditRawOn() {
|
|
InitRawTerminal() // from rawtty.go
|
|
}
|
|
|
|
func dbEditRawOff() {
|
|
RestoreTerminal() // from rawtty.go
|
|
}
|
|
|
|
func dbEditReadKey() int {
|
|
return ReadKey() // from rawtty.go
|
|
}
|
|
|
|
// helpers
|
|
func setF(a *hbrt.HbArray, c *hbrt.ClassDef, n string, v hbrt.Value) {
|
|
if i := c.FieldIndex(n); i >= 0 {
|
|
a.Items[i] = v
|
|
}
|
|
}
|
|
func getFA(a *hbrt.HbArray, c *hbrt.ClassDef, n string) *hbrt.HbArray {
|
|
if i := c.FieldIndex(n); i >= 0 {
|
|
return a.Items[i].AsArray()
|
|
}
|
|
return nil
|
|
}
|
|
func getFI(a *hbrt.HbArray, c *hbrt.ClassDef, n string) int {
|
|
if i := c.FieldIndex(n); i >= 0 {
|
|
return int(a.Items[i].AsNumInt())
|
|
}
|
|
return 0
|
|
}
|
|
func getFS(a *hbrt.HbArray, c *hbrt.ClassDef, n string) string {
|
|
if i := c.FieldIndex(n); i >= 0 {
|
|
return a.Items[i].AsString()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func getDbEditWA(t *hbrt.Thread) *hbrdd.WorkAreaManager {
|
|
if t.WA == nil {
|
|
return nil
|
|
}
|
|
wa, ok := t.WA.(*hbrdd.WorkAreaManager)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return wa
|
|
}
|