From 62fc2594ef75cb75cc20e968c62d2b70f0e63590 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 11 Oct 2022 01:11:04 -0700 Subject: [PATCH] checkpoint cmd-fg --- db/migrations/000001_init.up.sql | 3 ++- db/schema.sql | 3 +++ pkg/cmdrunner/cmdrunner.go | 41 ++++++++++++++++++++++---------- pkg/sstore/dbops.go | 28 +++++++++++++++++----- pkg/sstore/sstore.go | 34 ++++++++++++++++++++------ pkg/sstore/updatebus.go | 24 +++++++++---------- 6 files changed, 95 insertions(+), 38 deletions(-) diff --git a/db/migrations/000001_init.up.sql b/db/migrations/000001_init.up.sql index 7d788502..55b4c25a 100644 --- a/db/migrations/000001_init.up.sql +++ b/db/migrations/000001_init.up.sql @@ -51,7 +51,8 @@ CREATE TABLE screen_window ( name varchar(50) NOT NULL, layout json NOT NULL, selectedline int NOT NULL, - scrolltop int NOT NULL, + anchor json NOT NULL, + focustype varchar(12) NOT NULL, PRIMARY KEY (sessionid, screenid, windowid) ); diff --git a/db/schema.sql b/db/schema.sql index 0b998b39..c0d09ed5 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -48,6 +48,9 @@ CREATE TABLE screen_window ( windowid varchar(36) NOT NULL, name varchar(50) NOT NULL, layout json NOT NULL, + selectedline int NOT NULL, + anchor json NOT NULL, + focustype varchar(12) NOT NULL, PRIMARY KEY (sessionid, screenid, windowid) ); CREATE TABLE remote_instance ( diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index 3aefe3c0..b8361db5 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -260,6 +260,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U if sw != nil { updateMap := make(map[string]interface{}) updateMap[sstore.SWField_SelectedLine] = rtnLine.LineNum + updateMap[sstore.SWField_Focus] = sstore.SWFocusCmd sw, err = sstore.UpdateScreenWindow(ctx, ids.SessionId, ids.ScreenId, ids.WindowId, updateMap) if err != nil { // ignore error again (nothing to do) @@ -267,10 +268,10 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U } } update := sstore.ModelUpdate{ - Line: rtnLine, - Cmd: cmd, - ScreenWindow: sw, - Interactive: pk.Interactive, + Line: rtnLine, + Cmd: cmd, + ScreenWindows: []*sstore.ScreenWindowType{sw}, + Interactive: pk.Interactive, } sstore.MainBus.SendUpdate(ids.SessionId, update) ctxVal := ctx.Value(historyContextKey) @@ -444,6 +445,8 @@ func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor return update, nil } +var swAnchorRe = regexp.MustCompile("^(\\d+)(?::(\\d+))?$") + func SwSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window) if err != nil { @@ -451,12 +454,25 @@ func SwSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore } var setNonST bool // scrolltop does not receive an update updateMap := make(map[string]interface{}) - if pk.Kwargs["scrolltop"] != "" { - stVal, err := resolveNonNegInt(pk.Kwargs["scrolltop"], 0) - if err != nil { - return nil, fmt.Errorf("/sw:set invalid scrolltop argument: %v", err) + if pk.Kwargs["anchor"] != "" { + m := swAnchorRe.FindStringSubmatch(pk.Kwargs["anchor"]) + if m == nil { + return nil, fmt.Errorf("/sw:set invalid anchor argument (must be [line] or [line]:[offset])") } - updateMap[sstore.SWField_ScrollTop] = stVal + anchorLine, _ := strconv.Atoi(m[1]) + updateMap[sstore.SWField_AnchorLine] = anchorLine + if m[2] != "" { + anchorOffset, _ := strconv.Atoi(m[2]) + updateMap[sstore.SWField_AnchorOffset] = anchorOffset + } + } + if pk.Kwargs["focus"] != "" { + focusVal := pk.Kwargs["focus"] + if focusVal != sstore.SWFocusInput && focusVal != sstore.SWFocusCmd && focusVal != sstore.SWFocusCmdFg { + return nil, fmt.Errorf("/sw:set invalid focus argument %q, must be %s", focusVal, formatStrs([]string{sstore.SWFocusInput, sstore.SWFocusCmd, sstore.SWFocusCmdFg}, "or", false)) + } + updateMap[sstore.SWField_Focus] = focusVal + setNonST = true } if pk.Kwargs["line"] != "" { sw, err := sstore.GetScreenWindowByIds(ctx, ids.SessionId, ids.ScreenId, ids.WindowId) @@ -478,7 +494,7 @@ func SwSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore updateMap[sstore.SWField_SelectedLine] = ritem.Num } if len(updateMap) == 0 { - return nil, fmt.Errorf("/sw:set no updates, can set %s", formatStrs([]string{"line", "scrolltop"}, "or", false)) + return nil, fmt.Errorf("/sw:set no updates, can set %s", formatStrs([]string{"line", "scrolltop", "focus"}, "or", false)) } sw, err := sstore.UpdateScreenWindow(ctx, ids.SessionId, ids.ScreenId, ids.WindowId, updateMap) if err != nil { @@ -487,7 +503,7 @@ func SwSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore if !setNonST { return nil, nil } - return sstore.ModelUpdate{ScreenWindow: sw}, nil + return sstore.ModelUpdate{ScreenWindows: []*sstore.ScreenWindowType{sw}}, nil } func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -1258,12 +1274,13 @@ func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto } updateMap := make(map[string]interface{}) updateMap[sstore.SWField_SelectedLine] = rtnLine.LineNum + updateMap[sstore.SWField_Focus] = sstore.SWFocusInput sw, err := sstore.UpdateScreenWindow(ctx, ids.SessionId, ids.ScreenId, ids.WindowId, updateMap) if err != nil { // ignore error again (nothing to do) fmt.Printf("/comment error updating screen-window selected line: %v\n", err) } - update := sstore.ModelUpdate{Line: rtnLine, ScreenWindow: sw} + update := sstore.ModelUpdate{Line: rtnLine, ScreenWindows: []*sstore.ScreenWindowType{sw}} return update, nil } diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index d34f0172..745d8665 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -494,8 +494,8 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string, query = `INSERT INTO screen (sessionid, screenid, name, activewindowid, screenidx, screenopts, ownerid, sharemode) VALUES (?, ?, ?, ?, ?, ?, '', 'local')` tx.ExecWrap(query, sessionId, newScreenId, screenName, newWindowId, maxScreenIdx+1, ScreenOptsType{}) layout := LayoutType{Type: LayoutFull} - query = `INSERT INTO screen_window (sessionid, screenid, windowid, name, layout, selectedline, scrolltop) VALUES (?, ?, ?, ?, ?, ?, ?)` - tx.ExecWrap(query, sessionId, newScreenId, newWindowId, DefaultScreenWindowName, layout, 0, 0) + query = `INSERT INTO screen_window (sessionid, screenid, windowid, name, layout, selectedline, anchor, focustype) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` + tx.ExecWrap(query, sessionId, newScreenId, newWindowId, DefaultScreenWindowName, layout, 0, "", "input") if activate { query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?` tx.ExecWrap(query, newScreenId, sessionId) @@ -690,6 +690,12 @@ func AppendCmdErrorPk(ctx context.Context, errPk *packet.CmdErrorPacketType) err }) } +type SWKey struct { + SessionId string + ScreenId string + WindowId string +} + func HangupAllRunningCmds(ctx context.Context) error { return WithTx(ctx, func(tx *TxWrap) error { query := `UPDATE cmd SET status = ? WHERE status = ?` @@ -1101,8 +1107,10 @@ func UpdateRemote(ctx context.Context, remoteId string, editMap map[string]inter } const ( - SWField_ScrollTop = "scrolltop" // int + SWField_AnchorLine = "anchorline" // int + SWField_AnchorOffset = "anchoroffset" // int SWField_SelectedLine = "selectedline" // int + SWField_Focus = "focustype" // string ) func UpdateScreenWindow(ctx context.Context, sessionId string, screenId string, windowId string, editMap map[string]interface{}) (*ScreenWindowType, error) { @@ -1112,14 +1120,22 @@ func UpdateScreenWindow(ctx context.Context, sessionId string, screenId string, if !tx.Exists(query, sessionId, screenId, windowId) { return fmt.Errorf("screen-window not found") } - if stVal, found := editMap[SWField_ScrollTop]; found { - query = `UPDATE screen_window SET scrolltop = ? WHERE sessionid = ? AND screenid = ? AND windowid = ?` - tx.ExecWrap(query, stVal, sessionId, screenId, windowId) + if anchorLine, found := editMap[SWField_AnchorLine]; found { + query = `UPDATE screen_window SET anchor = json_set(anchor, '$.anchorline', ?) WHERE sessionid = ? AND screenid = ? AND windowid = ?` + tx.ExecWrap(query, anchorLine, sessionId, screenId, windowId) + } + if anchorOffset, found := editMap[SWField_AnchorOffset]; found { + query = `UPDATE screen_window SET anchor = json_set(anchor, '$.anchoroffset', ?) WHERE sessionid = ? AND screenid = ? AND windowid = ?` + tx.ExecWrap(query, anchorOffset, sessionId, screenId, windowId) } if sline, found := editMap[SWField_SelectedLine]; found { query = `UPDATE screen_window SET selectedline = ? WHERE sessionid = ? AND screenid = ? AND windowid = ?` tx.ExecWrap(query, sline, sessionId, screenId, windowId) } + if focusType, found := editMap[SWField_Focus]; found { + query = `UPDATE screen_window SET focustype = ? WHERE sessionid = ? AND screenid = ? AND windowid = ?` + tx.ExecWrap(query, focusType, sessionId, screenId, windowId) + } var sw ScreenWindowType query = `SELECT * FROM screen_window WHERE sessionid = ? AND screenid = ? AND windowid = ?` found := tx.GetWrap(&sw, query, sessionId, screenId, windowId) diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index fbf80c0b..6d307544 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -62,6 +62,12 @@ const ( RemoteTypeSsh = "ssh" ) +const ( + SWFocusInput = "input" + SWFocusCmd = "cmd" + SWFocusCmdFg = "cmd-fg" +) + var globalDBLock = &sync.Mutex{} var globalDB *sqlx.DB var globalDBErr error @@ -375,14 +381,28 @@ func (l LayoutType) Value() (driver.Value, error) { return quickValueJson(l) } +type SWAnchorType struct { + AnchorLine int `json:"anchorline,omitempty"` + AnchorOffset int `json:"anchoroffset,omitempty"` +} + +func (a *SWAnchorType) Scan(val interface{}) error { + return quickScanJson(a, val) +} + +func (a SWAnchorType) Value() (driver.Value, error) { + return quickValueJson(a) +} + type ScreenWindowType struct { - SessionId string `json:"sessionid"` - ScreenId string `json:"screenid"` - WindowId string `json:"windowid"` - Name string `json:"name"` - Layout LayoutType `json:"layout"` - SelectedLine int `json:"selectedline"` - ScrollTop int `json:"scrolltop"` + SessionId string `json:"sessionid"` + ScreenId string `json:"screenid"` + WindowId string `json:"windowid"` + Name string `json:"name"` + Layout LayoutType `json:"layout"` + SelectedLine int `json:"selectedline"` + Anchor SWAnchorType `json:"anchor"` + FocusType string `json:"focustype"` // only for updates Remove bool `json:"remove,omitempty"` diff --git a/pkg/sstore/updatebus.go b/pkg/sstore/updatebus.go index e73df343..5d3baf6b 100644 --- a/pkg/sstore/updatebus.go +++ b/pkg/sstore/updatebus.go @@ -29,18 +29,18 @@ func (PtyDataUpdate) UpdateType() string { } type ModelUpdate struct { - Sessions []*SessionType `json:"sessions,omitempty"` - ActiveSessionId string `json:"activesessionid,omitempty"` - Window *WindowType `json:"window,omitempty"` - ScreenWindow *ScreenWindowType `json:"screenwindow,omitempty"` - Line *LineType `json:"line,omitempty"` - Cmd *CmdType `json:"cmd,omitempty"` - CmdLine *CmdLineType `json:"cmdline,omitempty"` - Info *InfoMsgType `json:"info,omitempty"` - Remotes []interface{} `json:"remotes,omitempty"` // []*remote.RemoteState - History *HistoryInfoType `json:"history,omitempty"` - Interactive bool `json:"interactive"` - Connect bool `json:"connect,omitempty"` + Sessions []*SessionType `json:"sessions,omitempty"` + ActiveSessionId string `json:"activesessionid,omitempty"` + Window *WindowType `json:"window,omitempty"` + ScreenWindows []*ScreenWindowType `json:"screenwindows,omitempty"` + Line *LineType `json:"line,omitempty"` + Cmd *CmdType `json:"cmd,omitempty"` + CmdLine *CmdLineType `json:"cmdline,omitempty"` + Info *InfoMsgType `json:"info,omitempty"` + Remotes []interface{} `json:"remotes,omitempty"` // []*remote.RemoteState + History *HistoryInfoType `json:"history,omitempty"` + Interactive bool `json:"interactive"` + Connect bool `json:"connect,omitempty"` } func (ModelUpdate) UpdateType() string {