// 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 }