diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index 1e7ce512..2aaf5184 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -43,7 +43,7 @@ var hostNameRe = regexp.MustCompile("^[a-z][a-z0-9.-]*$") var userHostRe = regexp.MustCompile("^(sudo@)?([a-z][a-z0-9-]*)@([a-z][a-z0-9.-]*)(?::([0-9]+))?$") var remoteAliasRe = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_-]*$") var genericNameRe = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_ .()<>,/\"'\\[\\]{}=+$@!*-]*$") -var positionRe = regexp.MustCompile("^((\\+|-)?[0-9]+|(\\+|-))$") +var positionRe = regexp.MustCompile("^((S?\\+|E?-)?[0-9]+|(\\+|-|S|E))$") var wsRe = regexp.MustCompile("\\s+") type contextType string @@ -439,6 +439,25 @@ func SwSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore } updateMap[sstore.SWField_ScrollTop] = stVal } + if pk.Kwargs["line"] != "" { + sw, err := sstore.GetScreenWindowByIds(ctx, ids.SessionId, ids.ScreenId, ids.WindowId) + if err != nil { + return nil, fmt.Errorf("/sw:set cannot get screen-window: %v", err) + } + var selectedLineStr string + if sw.SelectedLine > 0 { + selectedLineStr = strconv.Itoa(sw.SelectedLine) + } + ritem, err := resolveLine(ctx, ids.SessionId, ids.WindowId, pk.Kwargs["line"], selectedLineStr) + if err != nil { + return nil, fmt.Errorf("/sw:set error resolving line: %v", err) + } + if ritem == nil { + return nil, fmt.Errorf("/sw:set could not resolve line %q", pk.Kwargs["line"]) + } + setNonST = true + 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)) } diff --git a/pkg/cmdrunner/resolver.go b/pkg/cmdrunner/resolver.go index 984a9e4e..0db0dd41 100644 --- a/pkg/cmdrunner/resolver.go +++ b/pkg/cmdrunner/resolver.go @@ -95,26 +95,40 @@ func boundInt(ival int, maxVal int, wrap bool) int { } type posArgType struct { - Pos int - IsWrap bool - IsRelative bool + Pos int + IsWrap bool + IsRelative bool + StartAnchor bool + EndAnchor bool } func parsePosArg(posStr string) *posArgType { if !positionRe.MatchString(posStr) { return nil } - rtn := &posArgType{} - rtn.IsRelative = strings.HasPrefix(posStr, "+") || strings.HasPrefix(posStr, "-") - rtn.IsWrap = posStr == "+" || posStr == "-" - if rtn.IsWrap && posStr == "+" { - rtn.Pos = 1 - } else if rtn.IsWrap && posStr == "-" { - rtn.Pos = -1 - } else { - rtn.Pos, _ = strconv.Atoi(posStr) // don't need to check error because of positionRe.Match + if posStr == "+" { + return &posArgType{Pos: 1, IsWrap: true, IsRelative: true} + } else if posStr == "-" { + return &posArgType{Pos: -1, IsWrap: true, IsRelative: true} + } else if posStr == "S" { + return &posArgType{Pos: 0, IsRelative: true, StartAnchor: true} + } else if posStr == "E" { + return &posArgType{Pos: 0, IsRelative: true, EndAnchor: true} } - return rtn + if strings.HasPrefix(posStr, "S+") { + pos, _ := strconv.Atoi(posStr[2:]) + return &posArgType{Pos: pos, IsRelative: true, StartAnchor: true} + } + if strings.HasPrefix(posStr, "E-") { + pos, _ := strconv.Atoi(posStr[1:]) + return &posArgType{Pos: pos, IsRelative: true, EndAnchor: true} + } + if strings.HasPrefix(posStr, "+") || strings.HasPrefix(posStr, "-") { + pos, _ := strconv.Atoi(posStr) + return &posArgType{Pos: pos, IsRelative: true} + } + pos, _ := strconv.Atoi(posStr) + return &posArgType{Pos: pos} } func resolveByPosition(isNumeric bool, items []ResolveItem, curId string, posStr string) *ResolveItem { @@ -127,15 +141,31 @@ func resolveByPosition(isNumeric bool, items []ResolveItem, curId string, posStr } var finalPos int if posArg.IsRelative { - curIdx := 1 // if no match, curIdx will be first item - for idx, item := range items { - if item.Id == curId { - curIdx = idx + 1 - break + var curIdx int + if posArg.StartAnchor { + curIdx = 1 + } else if posArg.EndAnchor { + curIdx = len(items) + } else { + curIdx = 1 // if no match, curIdx will be first item + for idx, item := range items { + if item.Id == curId { + curIdx = idx + 1 + break + } } } finalPos = curIdx + posArg.Pos + } else if isNumeric { + // these resolve items have a "Num" set that should be used to look up non-relative positions + for _, item := range items { + if item.Num == posArg.Pos { + return &item + } + } + return nil } else { + // non-numeric means position is just the index finalPos = posArg.Pos } finalPos = boundInt(finalPos, len(items), posArg.IsWrap) @@ -243,12 +273,20 @@ func resolveUiIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype i func resolveSessionScreen(ctx context.Context, sessionId string, screenArg string, curScreenArg string) (*ResolveItem, error) { screens, err := sstore.GetSessionScreens(ctx, sessionId) if err != nil { - return nil, fmt.Errorf("could not retreive screens for session=%s", sessionId) + return nil, fmt.Errorf("could not retreive screens for session=%s: %v", sessionId, err) } ritems := screensToResolveItems(screens) return genericResolve(screenArg, curScreenArg, ritems, false, "screen") } +func resolveLine(ctx context.Context, sessionId string, windowId string, lineArg string, curLineArg string) (*ResolveItem, error) { + lines, err := sstore.GetLineResolveItems(ctx, sessionId, windowId) + if err != nil { + return nil, fmt.Errorf("could not get lines: %v", err) + } + return genericResolve(lineArg, curLineArg, lines, true, "line") +} + func getSessionIds(sarr []*sstore.SessionType) []string { rtn := make([]string, len(sarr)) for idx, s := range sarr { @@ -263,6 +301,11 @@ func isPartialUUID(s string) bool { return partialUUIDRe.MatchString(s) } +func isUUID(s string) bool { + _, err := uuid.Parse(s) + return err == nil +} + func getResolveItemById(id string, items []ResolveItem) *ResolveItem { if id == "" { return nil @@ -290,10 +333,11 @@ func genericResolve(arg string, curArg string, items []ResolveItem, isNumeric bo if rtnItem != nil { return rtnItem, nil } + isUuid := isUUID(arg) tryPuid := isPartialUUID(arg) var prefixMatches []ResolveItem for _, item := range items { - if item.Id == arg || (tryPuid && strings.HasPrefix(item.Id, arg)) { + if (isUuid && item.Id == arg) || (tryPuid && strings.HasPrefix(item.Id, arg)) { return &item, nil } if item.Name != "" { diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 709c08fd..feb5ced6 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -1105,7 +1105,8 @@ func UpdateRemote(ctx context.Context, remoteId string, editMap map[string]inter } const ( - SWField_ScrollTop = "scrolltop" // int + SWField_ScrollTop = "scrolltop" // int + SWField_SelectedLine = "selectedline" // int ) func UpdateScreenWindow(ctx context.Context, sessionId string, screenId string, windowId string, editMap map[string]interface{}) (*ScreenWindowType, error) { @@ -1119,6 +1120,10 @@ func UpdateScreenWindow(ctx context.Context, sessionId string, screenId string, query = `UPDATE screen_window SET scrolltop = ? WHERE sessionid = ? AND screenid = ? AND windowid = ?` tx.ExecWrap(query, stVal, 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) + } var sw ScreenWindowType query = `SELECT * FROM screen_window WHERE sessionid = ? AND screenid = ? AND windowid = ?` found := tx.GetWrap(&sw, query, sessionId, screenId, windowId) @@ -1133,6 +1138,23 @@ func UpdateScreenWindow(ctx context.Context, sessionId string, screenId string, return rtn, nil } +func GetScreenWindowByIds(ctx context.Context, sessionId string, screenId string, windowId string) (*ScreenWindowType, error) { + var rtn *ScreenWindowType + txErr := WithTx(ctx, func(tx *TxWrap) error { + var sw ScreenWindowType + query := `SELECT * FROM screen_window WHERE sessionid = ? AND screenid = ? AND windowid = ?` + found := tx.GetWrap(&sw, query, sessionId, screenId, windowId) + if found { + rtn = &sw + } + return nil + }) + if txErr != nil { + return nil, txErr + } + return rtn, nil +} + func GetLineResolveItems(ctx context.Context, sessionId string, windowId string) ([]ResolveItem, error) { var rtn []ResolveItem txErr := WithTx(ctx, func(tx *TxWrap) error {