2023-10-17 06:31:13 +02:00
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
2022-07-16 02:37:32 +02:00
package cmdrunner
import (
2022-08-23 23:01:52 +02:00
"bytes"
2022-07-16 02:37:32 +02:00
"context"
2023-09-02 00:21:35 +02:00
"encoding/json"
2022-07-16 02:37:32 +02:00
"fmt"
2023-09-01 07:04:31 +02:00
"io/fs"
2022-10-31 20:40:45 +01:00
"log"
2023-03-31 03:08:35 +02:00
"net/url"
2022-09-01 21:47:10 +02:00
"os"
2023-09-01 07:04:31 +02:00
"path/filepath"
2022-08-11 03:33:32 +02:00
"regexp"
"sort"
2022-07-16 02:37:32 +02:00
"strconv"
"strings"
2022-10-30 20:52:40 +01:00
"syscall"
2022-08-12 08:45:15 +02:00
"time"
2023-03-24 18:34:07 +01:00
"unicode"
2022-07-16 02:37:32 +02:00
2023-10-24 08:22:18 +02:00
"github.com/google/uuid"
2023-10-16 22:30:10 +02:00
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
"github.com/wavetermdev/waveterm/waveshell/pkg/packet"
"github.com/wavetermdev/waveterm/waveshell/pkg/shexec"
"github.com/wavetermdev/waveterm/wavesrv/pkg/comp"
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
"github.com/wavetermdev/waveterm/wavesrv/pkg/pcloud"
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote"
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote/openai"
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
"github.com/wavetermdev/waveterm/wavesrv/pkg/utilfn"
2022-07-16 02:37:32 +02:00
)
2022-08-31 22:28:52 +02:00
const (
2023-03-15 00:37:22 +01:00
HistoryTypeScreen = "screen"
2022-08-31 22:28:52 +02:00
HistoryTypeSession = "session"
HistoryTypeGlobal = "global"
)
2022-11-10 22:52:51 +01:00
func init ( ) {
2022-11-11 01:00:51 +01:00
comp . RegisterSimpleCompFn ( comp . CGTypeMeta , simpleCompMeta )
comp . RegisterSimpleCompFn ( comp . CGTypeCommandMeta , simpleCompCommandMeta )
2022-11-10 22:52:51 +01:00
}
2023-05-04 10:01:13 +02:00
const DefaultUserId = "user"
2022-08-27 01:21:19 +02:00
const MaxNameLen = 50
2023-03-24 18:34:07 +01:00
const MaxShareNameLen = 150
2023-03-17 05:46:10 +01:00
const MaxRendererLen = 50
2022-09-01 21:47:10 +02:00
const MaxRemoteAliasLen = 50
2022-10-04 04:04:48 +02:00
const PasswordUnchangedSentinel = "--unchanged--"
2022-11-28 09:13:00 +01:00
const DefaultPTERM = "MxM"
2022-12-20 02:36:19 +01:00
const MaxCommandLen = 4096
2022-12-21 06:58:58 +01:00
const MaxSignalLen = 12
const MaxSignalNum = 64
2023-03-20 20:19:48 +01:00
const MaxEvalDepth = 5
2023-05-09 01:06:51 +02:00
const MaxOpenAIAPITokenLen = 100
const MaxOpenAIModelLen = 100
2022-08-27 01:21:19 +02:00
2023-09-01 07:04:31 +02:00
const TsFormatStr = "2006-01-02 15:04:05"
2023-04-18 00:23:58 +02:00
const (
KwArgRenderer = "renderer"
KwArgView = "view"
2023-09-02 00:21:35 +02:00
KwArgState = "state"
2023-09-17 23:10:35 +02:00
KwArgTemplate = "template"
KwArgLang = "lang"
2023-04-18 00:23:58 +02:00
)
2023-11-02 06:38:37 +01:00
var ColorNames = [ ] string { "yellow" , "blue" , "pink" , "mint" , "cyan" , "violet" , "orange" , "green" , "red" , "white" }
2023-11-08 01:15:54 +01:00
var TabIcons = [ ] string { "square" , "sparkle" , "fire" , "ghost" , "cloud" , "compass" , "crown" , "droplet" , "graduation-cap" , "heart" , "file" }
2022-09-01 21:47:10 +02:00
var RemoteColorNames = [ ] string { "red" , "green" , "yellow" , "blue" , "magenta" , "cyan" , "white" , "orange" }
2022-10-04 04:04:48 +02:00
var RemoteSetArgs = [ ] string { "alias" , "connectmode" , "key" , "password" , "autoinstall" , "color" }
2022-08-27 06:44:18 +02:00
2023-05-09 02:54:38 +02:00
var ScreenCmds = [ ] string { "run" , "comment" , "cd" , "cr" , "clear" , "sw" , "reset" , "signal" , "chat" }
2023-01-02 21:13:55 +01:00
var NoHistCmds = [ ] string { "_compgen" , "line" , "history" , "_killserver" }
2023-02-22 03:03:13 +01:00
var GlobalCmds = [ ] string { "session" , "screen" , "remote" , "set" , "client" , "telemetry" , "bookmark" , "bookmarks" }
2022-11-28 09:13:00 +01:00
var SetVarNameMap map [ string ] string = map [ string ] string {
2023-01-23 21:54:32 +01:00
"tabcolor" : "screen.tabcolor" ,
2023-11-08 01:15:54 +01:00
"tabicon" : "screen.tabicon" ,
2023-03-15 00:37:22 +01:00
"pterm" : "screen.pterm" ,
"anchor" : "screen.anchor" ,
"focus" : "screen.focus" ,
"line" : "screen.line" ,
2022-11-28 09:13:00 +01:00
}
var SetVarScopes = [ ] SetVarScope {
SetVarScope { ScopeName : "global" , VarNames : [ ] string { } } ,
2023-01-19 20:10:12 +01:00
SetVarScope { ScopeName : "client" , VarNames : [ ] string { "telemetry" } } ,
2022-11-28 09:13:00 +01:00
SetVarScope { ScopeName : "session" , VarNames : [ ] string { "name" , "pos" } } ,
2023-11-07 09:04:25 +01:00
SetVarScope { ScopeName : "screen" , VarNames : [ ] string { "name" , "tabcolor" , "tabicon" , "pos" , "pterm" , "anchor" , "focus" , "line" } } ,
2022-11-28 09:13:00 +01:00
SetVarScope { ScopeName : "line" , VarNames : [ ] string { } } ,
// connection = remote, remote = remoteinstance
SetVarScope { ScopeName : "connection" , VarNames : [ ] string { "alias" , "connectmode" , "key" , "password" , "autoinstall" , "color" } } ,
SetVarScope { ScopeName : "remote" , VarNames : [ ] string { } } ,
}
2022-10-22 23:46:39 +02:00
2022-09-01 21:47:10 +02:00
var hostNameRe = regexp . MustCompile ( "^[a-z][a-z0-9.-]*$" )
2023-01-12 02:16:20 +01:00
var userHostRe = regexp . MustCompile ( "^(sudo@)?([a-z][a-z0-9-]*)@([a-z0-9][a-z0-9.-]*)(?::([0-9]+))?$" )
2022-09-01 21:47:10 +02:00
var remoteAliasRe = regexp . MustCompile ( "^[a-zA-Z][a-zA-Z0-9_-]*$" )
2022-08-27 01:24:07 +02:00
var genericNameRe = regexp . MustCompile ( "^[a-zA-Z][a-zA-Z0-9_ .()<>,/\"'\\[\\]{}=+$@!*-]*$" )
2023-03-17 05:46:10 +01:00
var rendererRe = regexp . MustCompile ( "^[a-zA-Z][a-zA-Z0-9_.:-]*$" )
2022-10-07 10:08:03 +02:00
var positionRe = regexp . MustCompile ( "^((S?\\+|E?-)?[0-9]+|(\\+|-|S|E))$" )
2022-08-27 01:24:07 +02:00
var wsRe = regexp . MustCompile ( "\\s+" )
2022-12-21 06:58:58 +01:00
var sigNameRe = regexp . MustCompile ( "^((SIG[A-Z0-9]+)|(\\d+))$" )
2022-08-27 01:21:19 +02:00
2022-09-13 21:06:12 +02:00
type contextType string
var historyContextKey = contextType ( "history" )
2023-03-20 20:19:48 +01:00
var depthContextKey = contextType ( "depth" )
2022-09-13 21:06:12 +02:00
2022-11-28 09:13:00 +01:00
type SetVarScope struct {
ScopeName string
VarNames [ ] string
}
2022-09-13 21:06:12 +02:00
type historyContextType struct {
LineId string
2023-03-18 05:36:49 +01:00
LineNum int64
2022-09-13 21:06:12 +02:00
RemotePtr * sstore . RemotePtrType
}
2022-08-27 02:17:33 +02:00
type MetaCmdFnType = func ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error )
type MetaCmdEntryType struct {
IsAlias bool
Fn MetaCmdFnType
}
2022-08-17 21:24:09 +02:00
2022-08-27 02:17:33 +02:00
var MetaCmdFnMap = make ( map [ string ] MetaCmdEntryType )
func init ( ) {
registerCmdFn ( "run" , RunCommand )
registerCmdFn ( "eval" , EvalCommand )
registerCmdFn ( "comment" , CommentCommand )
registerCmdFn ( "cr" , CrCommand )
2023-03-01 02:50:42 +01:00
registerCmdFn ( "connect" , CrCommand )
2022-11-23 20:12:05 +01:00
registerCmdFn ( "_compgen" , CompGenCommand )
2022-08-27 07:01:29 +02:00
registerCmdFn ( "clear" , ClearCommand )
2022-12-20 02:36:19 +01:00
registerCmdFn ( "reset" , RemoteResetCommand )
2022-12-21 06:58:58 +01:00
registerCmdFn ( "signal" , SignalCommand )
2023-04-13 06:42:16 +02:00
registerCmdFn ( "sync" , SyncCommand )
2022-08-27 02:17:33 +02:00
registerCmdFn ( "session" , SessionCommand )
registerCmdFn ( "session:open" , SessionOpenCommand )
registerCmdAlias ( "session:new" , SessionOpenCommand )
registerCmdFn ( "session:set" , SessionSetCommand )
2023-01-25 22:57:36 +01:00
registerCmdAlias ( "session:delete" , SessionDeleteCommand )
registerCmdFn ( "session:purge" , SessionDeleteCommand )
2022-12-25 22:03:11 +01:00
registerCmdFn ( "session:archive" , SessionArchiveCommand )
registerCmdFn ( "session:showall" , SessionShowAllCommand )
2022-12-27 04:06:46 +01:00
registerCmdFn ( "session:show" , SessionShowCommand )
2023-02-22 07:41:56 +01:00
registerCmdFn ( "session:openshared" , SessionOpenSharedCommand )
2022-08-27 02:17:33 +02:00
registerCmdFn ( "screen" , ScreenCommand )
2022-12-25 22:21:48 +01:00
registerCmdFn ( "screen:archive" , ScreenArchiveCommand )
2022-12-24 00:56:29 +01:00
registerCmdFn ( "screen:purge" , ScreenPurgeCommand )
2022-08-27 02:17:33 +02:00
registerCmdFn ( "screen:open" , ScreenOpenCommand )
registerCmdAlias ( "screen:new" , ScreenOpenCommand )
2022-08-27 02:51:28 +02:00
registerCmdFn ( "screen:set" , ScreenSetCommand )
2022-12-24 00:56:29 +01:00
registerCmdFn ( "screen:showall" , ScreenShowAllCommand )
2023-02-01 02:56:56 +01:00
registerCmdFn ( "screen:reset" , ScreenResetCommand )
2023-03-24 18:34:07 +01:00
registerCmdFn ( "screen:webshare" , ScreenWebShareCommand )
2022-08-27 02:17:33 +02:00
registerCmdAlias ( "remote" , RemoteCommand )
registerCmdFn ( "remote:show" , RemoteShowCommand )
2022-08-28 23:24:05 +02:00
registerCmdFn ( "remote:showall" , RemoteShowAllCommand )
2022-09-01 08:12:26 +02:00
registerCmdFn ( "remote:new" , RemoteNewCommand )
2022-09-13 21:06:12 +02:00
registerCmdFn ( "remote:archive" , RemoteArchiveCommand )
registerCmdFn ( "remote:set" , RemoteSetCommand )
2022-09-01 21:47:10 +02:00
registerCmdFn ( "remote:disconnect" , RemoteDisconnectCommand )
registerCmdFn ( "remote:connect" , RemoteConnectCommand )
2022-09-27 06:09:43 +02:00
registerCmdFn ( "remote:install" , RemoteInstallCommand )
2022-09-27 08:23:04 +02:00
registerCmdFn ( "remote:installcancel" , RemoteInstallCancelCommand )
2023-02-01 02:56:56 +01:00
registerCmdFn ( "remote:reset" , RemoteResetCommand )
2022-08-28 23:24:05 +02:00
2023-03-13 20:10:23 +01:00
registerCmdFn ( "screen:resize" , ScreenResizeCommand )
2022-09-06 05:08:59 +02:00
2022-09-13 21:06:12 +02:00
registerCmdFn ( "line" , LineCommand )
registerCmdFn ( "line:show" , LineShowCommand )
2022-12-06 07:59:00 +01:00
registerCmdFn ( "line:star" , LineStarCommand )
2023-02-21 06:39:29 +01:00
registerCmdFn ( "line:bookmark" , LineBookmarkCommand )
registerCmdFn ( "line:pin" , LinePinCommand )
2022-12-28 08:12:27 +01:00
registerCmdFn ( "line:archive" , LineArchiveCommand )
2022-12-22 02:45:40 +01:00
registerCmdFn ( "line:purge" , LinePurgeCommand )
2023-02-01 07:21:19 +01:00
registerCmdFn ( "line:setheight" , LineSetHeightCommand )
2023-03-03 08:24:01 +01:00
registerCmdFn ( "line:view" , LineViewCommand )
2023-03-17 22:47:30 +01:00
registerCmdFn ( "line:set" , LineSetCommand )
2022-09-13 21:06:12 +02:00
2023-01-19 20:10:12 +01:00
registerCmdFn ( "client" , ClientCommand )
2023-01-23 08:10:18 +01:00
registerCmdFn ( "client:show" , ClientShowCommand )
2023-02-26 23:33:01 +01:00
registerCmdFn ( "client:set" , ClientSetCommand )
2023-03-31 22:25:57 +02:00
registerCmdFn ( "client:notifyupdatewriter" , ClientNotifyUpdateWriterCommand )
2023-04-05 06:52:20 +02:00
registerCmdFn ( "client:accepttos" , ClientAcceptTosCommand )
2023-01-19 20:10:12 +01:00
2023-01-23 21:54:32 +01:00
registerCmdFn ( "telemetry" , TelemetryCommand )
registerCmdFn ( "telemetry:on" , TelemetryOnCommand )
registerCmdFn ( "telemetry:off" , TelemetryOffCommand )
registerCmdFn ( "telemetry:send" , TelemetrySendCommand )
registerCmdFn ( "telemetry:show" , TelemetryShowCommand )
2023-01-02 21:13:55 +01:00
registerCmdFn ( "history" , HistoryCommand )
2023-03-02 09:31:19 +01:00
registerCmdFn ( "history:viewall" , HistoryViewAllCommand )
2023-03-03 22:31:16 +01:00
registerCmdFn ( "history:purge" , HistoryPurgeCommand )
2022-10-30 20:52:40 +01:00
2023-02-21 07:00:07 +01:00
registerCmdFn ( "bookmarks:show" , BookmarksShowCommand )
2023-02-22 07:11:06 +01:00
registerCmdFn ( "bookmark:set" , BookmarkSetCommand )
2023-02-22 03:03:13 +01:00
registerCmdFn ( "bookmark:delete" , BookmarkDeleteCommand )
2023-05-09 02:54:38 +02:00
registerCmdFn ( "chat" , OpenAICommand )
2023-05-04 10:01:13 +02:00
2022-12-20 02:36:19 +01:00
registerCmdFn ( "_killserver" , KillServerCommand )
2022-11-28 09:13:00 +01:00
registerCmdFn ( "set" , SetCommand )
2023-09-01 07:04:31 +02:00
registerCmdFn ( "view:stat" , ViewStatCommand )
registerCmdFn ( "view:test" , ViewTestCommand )
registerCmdFn ( "edit:test" , EditTestCommand )
2023-09-02 01:38:54 +02:00
2023-09-06 07:09:24 +02:00
// CodeEditCommand is overloaded to do codeedit and codeview
2023-09-02 01:38:54 +02:00
registerCmdFn ( "codeedit" , CodeEditCommand )
2023-09-06 07:09:24 +02:00
registerCmdFn ( "codeview" , CodeEditCommand )
2023-09-16 20:15:09 +02:00
registerCmdFn ( "imageview" , ImageViewCommand )
registerCmdFn ( "mdview" , MarkdownViewCommand )
registerCmdFn ( "markdownview" , MarkdownViewCommand )
2023-10-12 02:59:22 +02:00
registerCmdFn ( "csvview" , CSVViewCommand )
2022-08-27 02:17:33 +02:00
}
2022-08-11 03:33:32 +02:00
2022-08-27 02:17:33 +02:00
func getValidCommands ( ) [ ] string {
var rtn [ ] string
for key , val := range MetaCmdFnMap {
if val . IsAlias {
continue
}
2022-09-27 08:23:04 +02:00
rtn = append ( rtn , "/" + key )
2022-08-27 02:17:33 +02:00
}
return rtn
}
2022-08-21 21:31:29 +02:00
2022-08-27 02:17:33 +02:00
func registerCmdFn ( cmdName string , fn MetaCmdFnType ) {
MetaCmdFnMap [ cmdName ] = MetaCmdEntryType { Fn : fn }
}
2022-08-23 01:00:25 +02:00
2022-08-27 02:17:33 +02:00
func registerCmdAlias ( cmdName string , fn MetaCmdFnType ) {
MetaCmdFnMap [ cmdName ] = MetaCmdEntryType { IsAlias : true , Fn : fn }
}
2022-08-23 23:01:52 +02:00
2023-03-01 02:50:42 +01:00
func GetCmdStr ( pk * scpacket . FeCommandPacketType ) string {
if pk . MetaSubCmd == "" {
return pk . MetaCmd
}
return pk . MetaCmd + ":" + pk . MetaSubCmd
}
2022-08-27 02:17:33 +02:00
func HandleCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
metaCmd := SubMetaCmd ( pk . MetaCmd )
var cmdName string
if pk . MetaSubCmd == "" {
cmdName = metaCmd
} else {
cmdName = fmt . Sprintf ( "%s:%s" , pk . MetaCmd , pk . MetaSubCmd )
}
entry := MetaCmdFnMap [ cmdName ]
if entry . Fn == nil {
if MetaCmdFnMap [ metaCmd ] . Fn != nil {
return nil , fmt . Errorf ( "invalid /%s subcommand '%s'" , metaCmd , pk . MetaSubCmd )
}
return nil , fmt . Errorf ( "invalid command '/%s', no handler" , cmdName )
2022-08-11 03:33:32 +02:00
}
2022-08-27 02:17:33 +02:00
return entry . Fn ( ctx , pk )
2022-08-11 03:33:32 +02:00
}
2022-07-16 02:37:32 +02:00
func firstArg ( pk * scpacket . FeCommandPacketType ) string {
if len ( pk . Args ) == 0 {
return ""
}
return pk . Args [ 0 ]
}
2022-08-09 23:24:57 +02:00
func argN ( pk * scpacket . FeCommandPacketType , n int ) string {
if len ( pk . Args ) <= n {
return ""
}
return pk . Args [ n ]
}
2022-07-16 02:37:32 +02:00
func resolveBool ( arg string , def bool ) bool {
if arg == "" {
return def
}
if arg == "0" || arg == "false" {
return false
}
return true
}
2022-11-28 09:13:00 +01:00
func defaultStr ( arg string , def string ) string {
if arg == "" {
return def
}
return arg
}
2022-10-01 22:23:36 +02:00
func resolveFile ( arg string ) ( string , error ) {
if arg == "" {
return "" , nil
}
fileName := base . ExpandHomeDir ( arg )
if ! strings . HasPrefix ( fileName , "/" ) {
return "" , fmt . Errorf ( "must be absolute, cannot be a relative path" )
}
fd , err := os . Open ( fileName )
if fd != nil {
fd . Close ( )
}
if err != nil {
return "" , fmt . Errorf ( "cannot open file: %v" , err )
}
return fileName , nil
}
func resolvePosInt ( arg string , def int ) ( int , error ) {
2022-08-28 23:24:05 +02:00
if arg == "" {
return def , nil
}
ival , err := strconv . Atoi ( arg )
if err != nil {
return 0 , err
}
2022-10-01 22:23:36 +02:00
if ival <= 0 {
return 0 , fmt . Errorf ( "must be greater than 0" )
}
2022-08-28 23:24:05 +02:00
return ival , nil
}
2022-12-21 06:58:58 +01:00
func isAllDigits ( arg string ) bool {
if len ( arg ) == 0 {
return false
}
for i := 0 ; i < len ( arg ) ; i ++ {
if arg [ i ] >= '0' && arg [ i ] <= '9' {
continue
}
return false
}
return true
}
2022-10-07 03:33:54 +02:00
func resolveNonNegInt ( arg string , def int ) ( int , error ) {
if arg == "" {
return def , nil
}
ival , err := strconv . Atoi ( arg )
if err != nil {
return 0 , err
}
if ival < 0 {
return 0 , fmt . Errorf ( "cannot be negative" )
}
return ival , nil
}
2023-03-20 20:19:48 +01:00
var histExpansionRe = regexp . MustCompile ( ` ^!(\d+)$ ` )
func doCmdHistoryExpansion ( ctx context . Context , ids resolvedIds , cmdStr string ) ( string , error ) {
if ! strings . HasPrefix ( cmdStr , "!" ) {
return "" , nil
}
if strings . HasPrefix ( cmdStr , "! " ) {
return "" , nil
}
if cmdStr == "!!" {
return doHistoryExpansion ( ctx , ids , - 1 )
}
if strings . HasPrefix ( cmdStr , "!-" ) {
2023-11-01 09:26:19 +01:00
return "" , fmt . Errorf ( "wave does not support negative history offsets, use a stable positive history offset instead: '![linenum]'" )
2023-03-20 20:19:48 +01:00
}
m := histExpansionRe . FindStringSubmatch ( cmdStr )
if m == nil {
return "" , fmt . Errorf ( "unsupported history substitution, can use '!!' or '![linenum]'" )
}
ival , err := strconv . Atoi ( m [ 1 ] )
if err != nil {
return "" , fmt . Errorf ( "invalid history expansion" )
}
return doHistoryExpansion ( ctx , ids , ival )
}
func doHistoryExpansion ( ctx context . Context , ids resolvedIds , hnum int ) ( string , error ) {
if hnum == 0 {
return "" , fmt . Errorf ( "invalid history expansion, cannot expand line number '0'" )
}
if hnum < - 1 {
return "" , fmt . Errorf ( "invalid history expansion, cannot expand negative history offsets" )
}
foundHistoryNum := hnum
if hnum == - 1 {
var err error
foundHistoryNum , err = sstore . GetLastHistoryLineNum ( ctx , ids . ScreenId )
if err != nil {
return "" , fmt . Errorf ( "cannot expand history, error finding last history item: %v" , err )
}
if foundHistoryNum == 0 {
return "" , fmt . Errorf ( "cannot expand history, no last history item" )
}
}
hitem , err := sstore . GetHistoryItemByLineNum ( ctx , ids . ScreenId , foundHistoryNum )
if err != nil {
return "" , fmt . Errorf ( "cannot get history item '%d': %v" , foundHistoryNum , err )
}
if hitem == nil {
return "" , fmt . Errorf ( "cannot expand history, history item '%d' not found" , foundHistoryNum )
}
return hitem . CmdStr , nil
}
func getEvalDepth ( ctx context . Context ) int {
depthVal := ctx . Value ( depthContextKey )
if depthVal == nil {
return 0
}
return depthVal . ( int )
}
2023-04-13 06:42:16 +02:00
func SyncCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_RemoteConnected )
if err != nil {
return nil , fmt . Errorf ( "/run error: %w" , err )
}
runPacket := packet . MakeRunPacket ( )
runPacket . ReqId = uuid . New ( ) . String ( )
2023-11-01 09:26:19 +01:00
runPacket . CK = base . MakeCommandKey ( ids . ScreenId , scbase . GenWaveUUID ( ) )
2023-04-13 06:42:16 +02:00
runPacket . UsePty = true
ptermVal := defaultStr ( pk . Kwargs [ "pterm" ] , DefaultPTERM )
runPacket . TermOpts , err = GetUITermOpts ( pk . UIContext . WinSize , ptermVal )
if err != nil {
return nil , fmt . Errorf ( "/sync error, invalid 'pterm' value %q: %v" , ptermVal , err )
}
runPacket . Command = ":"
runPacket . ReturnState = true
cmd , callback , err := remote . RunCommand ( ctx , ids . SessionId , ids . ScreenId , ids . Remote . RemotePtr , runPacket )
if callback != nil {
defer callback ( )
}
if err != nil {
return nil , err
}
cmd . RawCmdStr = pk . GetRawStr ( )
2023-09-17 23:10:35 +02:00
update , err := addLineForCmd ( ctx , "/sync" , true , ids , cmd , "terminal" , nil )
2023-04-13 06:42:16 +02:00
if err != nil {
return nil , err
}
update . Interactive = pk . Interactive
sstore . MainBus . SendScreenUpdate ( ids . ScreenId , update )
return nil , nil
}
2023-04-18 00:23:58 +02:00
func getRendererArg ( pk * scpacket . FeCommandPacketType ) ( string , error ) {
rval := pk . Kwargs [ KwArgView ]
if rval == "" {
rval = pk . Kwargs [ KwArgRenderer ]
}
if rval == "" {
return "" , nil
}
err := validateRenderer ( rval )
if err != nil {
return "" , err
}
return rval , nil
}
2023-09-17 23:10:35 +02:00
func getTemplateArg ( pk * scpacket . FeCommandPacketType ) ( string , error ) {
rval := pk . Kwargs [ KwArgTemplate ]
if rval == "" {
return "" , nil
}
// TODO validate
return rval , nil
}
func getLangArg ( pk * scpacket . FeCommandPacketType ) ( string , error ) {
// TODO better error checking
if len ( pk . Kwargs [ KwArgLang ] ) > 50 {
return "" , nil // TODO return error, don't fail silently
}
return pk . Kwargs [ KwArgLang ] , nil
}
2022-07-16 02:37:32 +02:00
func RunCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_RemoteConnected )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/run error: %w" , err )
2022-07-16 02:37:32 +02:00
}
2023-04-18 00:23:58 +02:00
renderer , err := getRendererArg ( pk )
if err != nil {
return nil , fmt . Errorf ( "/run error, invalid view/renderer: %w" , err )
2023-03-17 05:46:10 +01:00
}
2023-09-17 23:10:35 +02:00
templateArg , err := getTemplateArg ( pk )
if err != nil {
return nil , fmt . Errorf ( "/run error, invalid template: %w" , err )
}
langArg , err := getLangArg ( pk )
if err != nil {
return nil , fmt . Errorf ( "/run error, invalid lang: %w" , err )
}
2022-07-16 02:37:32 +02:00
cmdStr := firstArg ( pk )
2023-03-20 20:19:48 +01:00
expandedCmdStr , err := doCmdHistoryExpansion ( ctx , ids , cmdStr )
if err != nil {
return nil , err
}
if expandedCmdStr != "" {
newPk := scpacket . MakeFeCommandPacket ( )
newPk . MetaCmd = "eval"
newPk . Args = [ ] string { expandedCmdStr }
newPk . Kwargs = pk . Kwargs
newPk . RawStr = pk . RawStr
newPk . UIContext = pk . UIContext
newPk . Interactive = pk . Interactive
evalDepth := getEvalDepth ( ctx )
ctxWithDepth := context . WithValue ( ctx , depthContextKey , evalDepth + 1 )
return EvalCommand ( ctxWithDepth , newPk )
}
2022-10-27 09:33:50 +02:00
isRtnStateCmd := IsReturnStateCommand ( cmdStr )
2022-11-28 09:13:00 +01:00
// runPacket.State is set in remote.RunCommand()
2022-07-16 02:37:32 +02:00
runPacket := packet . MakeRunPacket ( )
runPacket . ReqId = uuid . New ( ) . String ( )
2023-11-01 09:26:19 +01:00
runPacket . CK = base . MakeCommandKey ( ids . ScreenId , scbase . GenWaveUUID ( ) )
2022-07-16 02:37:32 +02:00
runPacket . UsePty = true
2022-11-28 09:13:00 +01:00
ptermVal := defaultStr ( pk . Kwargs [ "pterm" ] , DefaultPTERM )
runPacket . TermOpts , err = GetUITermOpts ( pk . UIContext . WinSize , ptermVal )
if err != nil {
return nil , fmt . Errorf ( "/run error, invalid 'pterm' value %q: %v" , ptermVal , err )
}
2022-07-16 02:37:32 +02:00
runPacket . Command = strings . TrimSpace ( cmdStr )
2022-10-27 09:33:50 +02:00
runPacket . ReturnState = resolveBool ( pk . Kwargs [ "rtnstate" ] , isRtnStateCmd )
2023-03-15 00:37:22 +01:00
cmd , callback , err := remote . RunCommand ( ctx , ids . SessionId , ids . ScreenId , ids . Remote . RemotePtr , runPacket )
2022-09-05 23:49:23 +02:00
if callback != nil {
defer callback ( )
}
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
2023-03-21 03:20:57 +01:00
cmd . RawCmdStr = pk . GetRawStr ( )
2023-09-17 23:10:35 +02:00
lineState := make ( map [ string ] any )
if templateArg != "" {
lineState [ sstore . LineState_Template ] = templateArg
}
if langArg != "" {
lineState [ sstore . LineState_Lang ] = langArg
}
update , err := addLineForCmd ( ctx , "/run" , true , ids , cmd , renderer , lineState )
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
2022-10-19 03:03:02 +02:00
update . Interactive = pk . Interactive
2023-03-21 03:20:57 +01:00
sstore . MainBus . SendScreenUpdate ( ids . ScreenId , update )
2022-09-05 23:54:17 +02:00
return nil , nil
2022-07-16 02:37:32 +02:00
}
2022-09-13 21:06:12 +02:00
func addToHistory ( ctx context . Context , pk * scpacket . FeCommandPacketType , historyContext historyContextType , isMetaCmd bool , hadError bool ) error {
2022-08-12 08:45:15 +02:00
cmdStr := firstArg ( pk )
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-08-12 08:45:15 +02:00
if err != nil {
return err
}
2022-12-20 03:52:08 +01:00
isIncognito , err := sstore . IsIncognitoScreen ( ctx , ids . SessionId , ids . ScreenId )
if err != nil {
return fmt . Errorf ( "cannot add to history, error looking up incognito status of screen: %v" , err )
}
2022-08-12 08:45:15 +02:00
hitem := & sstore . HistoryItemType {
2023-11-01 09:26:19 +01:00
HistoryId : scbase . GenWaveUUID ( ) ,
2022-08-12 08:45:15 +02:00
Ts : time . Now ( ) . UnixMilli ( ) ,
UserId : DefaultUserId ,
SessionId : ids . SessionId ,
ScreenId : ids . ScreenId ,
2022-09-13 21:06:12 +02:00
LineId : historyContext . LineId ,
2023-03-18 05:36:49 +01:00
LineNum : historyContext . LineNum ,
2022-08-12 08:45:15 +02:00
HadError : hadError ,
CmdStr : cmdStr ,
2022-08-28 23:24:05 +02:00
IsMetaCmd : isMetaCmd ,
2022-12-20 03:52:08 +01:00
Incognito : isIncognito ,
2022-08-28 23:24:05 +02:00
}
2022-09-13 21:06:12 +02:00
if ! isMetaCmd && historyContext . RemotePtr != nil {
hitem . Remote = * historyContext . RemotePtr
2022-08-12 08:45:15 +02:00
}
err = sstore . InsertHistoryItem ( ctx , hitem )
if err != nil {
return err
}
2022-08-11 21:07:41 +02:00
return nil
}
2022-07-16 02:37:32 +02:00
func EvalCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "usage: /eval [command], no command passed to eval" )
2022-07-16 02:37:32 +02:00
}
2022-12-20 02:36:19 +01:00
if len ( pk . Args [ 0 ] ) > MaxCommandLen {
return nil , fmt . Errorf ( "command length too long len:%d, max:%d" , len ( pk . Args [ 0 ] ) , MaxCommandLen )
}
2023-05-02 21:43:54 +02:00
evalDepth := getEvalDepth ( ctx )
if pk . Interactive && evalDepth == 0 {
2023-01-18 01:02:44 +01:00
err := sstore . UpdateCurrentActivity ( ctx , sstore . ActivityUpdate { NumCommands : 1 } )
if err != nil {
log . Printf ( "[error] incrementing activity numcommands: %v\n" , err )
// fall through (non-fatal error)
}
}
2023-05-02 21:43:54 +02:00
if evalDepth > MaxEvalDepth {
2023-03-20 20:19:48 +01:00
return nil , fmt . Errorf ( "alias/history expansion max-depth exceeded" )
}
2022-09-13 21:06:12 +02:00
var historyContext historyContextType
ctxWithHistory := context . WithValue ( ctx , historyContextKey , & historyContext )
2022-08-31 09:01:42 +02:00
var update sstore . UpdatePacket
2022-09-13 21:06:12 +02:00
newPk , rtnErr := EvalMetaCommand ( ctxWithHistory , pk )
2022-08-31 09:01:42 +02:00
if rtnErr == nil {
2022-09-13 21:06:12 +02:00
update , rtnErr = HandleCommand ( ctxWithHistory , newPk )
2022-07-16 02:37:32 +02:00
}
2022-08-11 21:07:41 +02:00
if ! resolveBool ( pk . Kwargs [ "nohist" ] , false ) {
2022-09-13 21:06:12 +02:00
err := addToHistory ( ctx , pk , historyContext , ( newPk . MetaCmd != "run" ) , ( rtnErr != nil ) )
2022-08-12 08:45:15 +02:00
if err != nil {
2022-10-31 20:40:45 +01:00
log . Printf ( "[error] adding to history: %v\n" , err )
2023-01-18 01:02:44 +01:00
// fall through (non-fatal error)
2022-08-12 08:45:15 +02:00
}
2022-08-11 21:07:41 +02:00
}
2022-08-31 09:01:42 +02:00
return update , rtnErr
2022-08-12 08:45:15 +02:00
}
2022-12-25 22:21:48 +01:00
func ScreenArchiveCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-12-24 00:56:29 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session ) // don't force R_Screen
2022-08-27 02:17:33 +02:00
if err != nil {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:archive cannot archive screen: %w" , err )
2022-07-16 02:37:32 +02:00
}
2022-12-24 00:56:29 +01:00
screenId := ids . ScreenId
if len ( pk . Args ) > 0 {
ri , err := resolveSessionScreen ( ctx , ids . SessionId , pk . Args [ 0 ] , ids . ScreenId )
if err != nil {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:archive cannot resolve screen arg: %v" , err )
2022-12-24 00:56:29 +01:00
}
screenId = ri . Id
}
if screenId == "" {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:archive no active screen or screen arg passed" )
2022-12-24 00:56:29 +01:00
}
2022-12-25 22:21:48 +01:00
archiveVal := true
2022-12-24 00:56:29 +01:00
if len ( pk . Args ) > 1 {
2022-12-25 22:21:48 +01:00
archiveVal = resolveBool ( pk . Args [ 1 ] , true )
2022-12-24 00:56:29 +01:00
}
var update sstore . UpdatePacket
2022-12-25 22:21:48 +01:00
if archiveVal {
update , err = sstore . ArchiveScreen ( ctx , ids . SessionId , screenId )
2022-12-24 00:56:29 +01:00
if err != nil {
return nil , err
}
return update , nil
} else {
2023-03-21 05:38:39 +01:00
log . Printf ( "unarchive screen %s\n" , screenId )
2022-12-25 22:21:48 +01:00
err = sstore . UnArchiveScreen ( ctx , ids . SessionId , screenId )
2022-12-24 00:56:29 +01:00
if err != nil {
2023-04-02 09:20:05 +02:00
return nil , fmt . Errorf ( "/screen:archive cannot un-archive screen: %v" , err )
2022-12-24 00:56:29 +01:00
}
2023-03-13 09:52:30 +01:00
screen , err := sstore . GetScreenById ( ctx , screenId )
2022-12-24 00:56:29 +01:00
if err != nil {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:archive cannot get updated screen obj: %v" , err )
2022-12-24 00:56:29 +01:00
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-03-13 18:50:29 +01:00
Screens : [ ] * sstore . ScreenType { screen } ,
2022-12-27 01:09:21 +01:00
}
2022-12-24 00:56:29 +01:00
return update , nil
}
}
func ScreenPurgeCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-04-18 00:23:58 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session ) // don't force R_Screen
2022-12-24 00:56:29 +01:00
if err != nil {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:purge cannot purge screen: %w" , err )
2022-12-24 00:56:29 +01:00
}
2023-04-18 00:23:58 +02:00
screenId := ids . ScreenId
if len ( pk . Args ) > 0 {
ri , err := resolveSessionScreen ( ctx , ids . SessionId , pk . Args [ 0 ] , ids . ScreenId )
if err != nil {
return nil , fmt . Errorf ( "/screen:purge cannot resolve screen arg: %v" , err )
}
screenId = ri . Id
}
if screenId == "" {
return nil , fmt . Errorf ( "/screen:purge no active screen or screen arg passed" )
}
update , err := sstore . PurgeScreen ( ctx , screenId , false )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , err
}
return update , nil
}
func ScreenOpenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , fmt . Errorf ( "/screen:open cannot open screen: %w" , err )
}
activate := resolveBool ( pk . Kwargs [ "activate" ] , true )
newName := pk . Kwargs [ "name" ]
if newName != "" {
err := validateName ( newName , "screen" )
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
}
2023-07-26 19:10:27 +02:00
update , err := sstore . InsertScreen ( ctx , ids . SessionId , newName , sstore . ScreenCreateOpts { } , activate )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , err
2022-07-16 02:37:32 +02:00
}
2022-08-27 02:17:33 +02:00
return update , nil
}
2022-08-27 02:51:28 +02:00
func ScreenSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-08-27 02:51:28 +02:00
if err != nil {
return nil , err
}
var varsUpdated [ ] string
2023-03-13 09:52:30 +01:00
var setNonAnchor bool // anchor does not receive an update
updateMap := make ( map [ string ] interface { } )
2022-08-27 02:51:28 +02:00
if pk . Kwargs [ "name" ] != "" {
newName := pk . Kwargs [ "name" ]
err = validateName ( newName , "screen" )
if err != nil {
return nil , err
}
2023-03-13 09:52:30 +01:00
updateMap [ sstore . ScreenField_Name ] = newName
2022-08-27 02:51:28 +02:00
varsUpdated = append ( varsUpdated , "name" )
2023-03-13 09:52:30 +01:00
setNonAnchor = true
2022-08-27 02:51:28 +02:00
}
2023-04-05 07:28:52 +02:00
if pk . Kwargs [ "sharename" ] != "" {
shareName := pk . Kwargs [ "sharename" ]
err = validateShareName ( shareName )
if err != nil {
return nil , err
}
updateMap [ sstore . ScreenField_ShareName ] = shareName
varsUpdated = append ( varsUpdated , "sharename" )
setNonAnchor = true
}
2022-08-27 06:44:18 +02:00
if pk . Kwargs [ "tabcolor" ] != "" {
color := pk . Kwargs [ "tabcolor" ]
err = validateColor ( color , "screen tabcolor" )
if err != nil {
return nil , err
}
2023-03-13 09:52:30 +01:00
updateMap [ sstore . ScreenField_TabColor ] = color
varsUpdated = append ( varsUpdated , "tabcolor" )
setNonAnchor = true
}
2023-11-07 09:04:25 +01:00
if pk . Kwargs [ "tabicon" ] != "" {
icon := pk . Kwargs [ "tabicon" ]
err = validateIcon ( icon , "screen tabicon" )
if err != nil {
return nil , err
}
updateMap [ sstore . ScreenField_TabIcon ] = icon
varsUpdated = append ( varsUpdated , "tabicon" )
setNonAnchor = true
}
2023-03-13 09:52:30 +01:00
if pk . Kwargs [ "pos" ] != "" {
varsUpdated = append ( varsUpdated , "pos" )
setNonAnchor = true
}
if pk . Kwargs [ "focus" ] != "" {
focusVal := pk . Kwargs [ "focus" ]
2023-04-13 21:53:15 +02:00
if focusVal != sstore . ScreenFocusInput && focusVal != sstore . ScreenFocusCmd {
return nil , fmt . Errorf ( "/screen:set invalid focus argument %q, must be %s" , focusVal , formatStrs ( [ ] string { sstore . ScreenFocusInput , sstore . ScreenFocusCmd } , "or" , false ) )
2023-03-13 09:52:30 +01:00
}
2023-03-13 20:23:36 +01:00
varsUpdated = append ( varsUpdated , "focus" )
2023-03-13 09:52:30 +01:00
updateMap [ sstore . ScreenField_Focus ] = focusVal
setNonAnchor = true
}
if pk . Kwargs [ "line" ] != "" {
screen , err := sstore . GetScreenById ( ctx , ids . ScreenId )
2022-08-27 06:44:18 +02:00
if err != nil {
2023-03-13 09:52:30 +01:00
return nil , fmt . Errorf ( "/screen:set cannot get screen: %v" , err )
2022-08-27 06:44:18 +02:00
}
2023-03-13 09:52:30 +01:00
var selectedLineStr string
if screen . SelectedLine > 0 {
selectedLineStr = strconv . Itoa ( int ( screen . SelectedLine ) )
2022-08-27 06:44:18 +02:00
}
2023-03-15 00:37:22 +01:00
ritem , err := resolveLine ( ctx , screen . SessionId , screen . ScreenId , pk . Kwargs [ "line" ] , selectedLineStr )
2022-08-27 06:44:18 +02:00
if err != nil {
2023-03-13 09:52:30 +01:00
return nil , fmt . Errorf ( "/screen:set error resolving line: %v" , err )
2022-08-27 06:44:18 +02:00
}
2023-03-13 09:52:30 +01:00
if ritem == nil {
return nil , fmt . Errorf ( "/screen:set could not resolve line %q" , pk . Kwargs [ "line" ] )
}
2023-03-13 20:10:23 +01:00
varsUpdated = append ( varsUpdated , "line" )
2023-03-13 09:52:30 +01:00
setNonAnchor = true
updateMap [ sstore . ScreenField_SelectedLine ] = ritem . Num
2022-08-27 06:44:18 +02:00
}
2023-03-13 09:52:30 +01:00
if pk . Kwargs [ "anchor" ] != "" {
2023-03-15 00:37:22 +01:00
m := screenAnchorRe . FindStringSubmatch ( pk . Kwargs [ "anchor" ] )
2023-03-13 09:52:30 +01:00
if m == nil {
return nil , fmt . Errorf ( "/screen:set invalid anchor argument (must be [line] or [line]:[offset])" )
}
anchorLine , _ := strconv . Atoi ( m [ 1 ] )
2023-03-13 20:10:23 +01:00
varsUpdated = append ( varsUpdated , "anchor" )
2023-03-13 09:52:30 +01:00
updateMap [ sstore . ScreenField_AnchorLine ] = anchorLine
if m [ 2 ] != "" {
anchorOffset , _ := strconv . Atoi ( m [ 2 ] )
updateMap [ sstore . ScreenField_AnchorOffset ] = anchorOffset
} else {
updateMap [ sstore . ScreenField_AnchorOffset ] = 0
}
2023-02-21 00:41:39 +01:00
}
2022-08-27 02:51:28 +02:00
if len ( varsUpdated ) == 0 {
2023-11-07 09:04:25 +01:00
return nil , fmt . Errorf ( "/screen:set no updates, can set %s" , formatStrs ( [ ] string { "name" , "pos" , "tabcolor" , "tabicon" , "focus" , "anchor" , "line" , "sharename" } , "or" , false ) )
2022-08-27 02:51:28 +02:00
}
2023-03-13 09:52:30 +01:00
screen , err := sstore . UpdateScreen ( ctx , ids . ScreenId , updateMap )
2022-08-27 02:51:28 +02:00
if err != nil {
2023-03-13 09:52:30 +01:00
return nil , fmt . Errorf ( "error updating screen: %v" , err )
2022-08-27 02:51:28 +02:00
}
2023-03-13 09:52:30 +01:00
if ! setNonAnchor {
return nil , nil
2022-12-27 01:09:21 +01:00
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-03-13 09:52:30 +01:00
Screens : [ ] * sstore . ScreenType { screen } ,
2022-12-27 01:09:21 +01:00
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "screen updated %s" , formatStrs ( varsUpdated , "and" , false ) ) ,
TimeoutMs : 2000 ,
} ,
2022-08-27 02:51:28 +02:00
}
return update , nil
}
2022-08-27 02:17:33 +02:00
func ScreenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/screen cannot switch to screen: %w" , err )
2022-07-16 02:37:32 +02:00
}
firstArg := firstArg ( pk )
if firstArg == "" {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "usage /screen [screen-name|screen-index|screen-id], no param specified" )
2022-07-16 02:37:32 +02:00
}
2022-09-13 21:06:12 +02:00
ritem , err := resolveSessionScreen ( ctx , ids . SessionId , firstArg , ids . ScreenId )
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
2022-08-27 02:29:32 +02:00
update , err := sstore . SwitchScreenById ( ctx , ids . SessionId , ritem . Id )
2022-07-16 02:53:23 +02:00
if err != nil {
return nil , err
}
return update , nil
2022-07-16 02:37:32 +02:00
}
2023-03-15 00:37:22 +01:00
var screenAnchorRe = regexp . MustCompile ( "^(\\d+)(?::(-?\\d+))?$" )
2022-10-11 10:11:04 +02:00
2022-09-27 06:09:43 +02:00
func RemoteInstallCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Remote )
2022-09-27 08:23:04 +02:00
if err != nil {
return nil , err
}
mshell := ids . Remote . MShell
go mshell . RunInstall ( )
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2023-04-04 00:55:36 +02:00
RemoteView : & sstore . RemoteViewType {
2022-09-27 08:23:04 +02:00
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
2022-09-27 06:09:43 +02:00
}
2022-09-27 08:23:04 +02:00
func RemoteInstallCancelCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Remote )
2022-09-01 21:47:10 +02:00
if err != nil {
return nil , err
}
2022-09-27 08:23:04 +02:00
mshell := ids . Remote . MShell
go mshell . CancelInstall ( )
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2023-04-04 00:55:36 +02:00
RemoteView : & sstore . RemoteViewType {
2022-09-27 08:23:04 +02:00
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
}
func RemoteConnectCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Remote )
2022-09-27 08:23:04 +02:00
if err != nil {
return nil , err
2022-09-16 21:28:09 +02:00
}
2023-01-23 22:47:36 +01:00
go ids . Remote . MShell . Launch ( true )
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2023-04-04 00:55:36 +02:00
RemoteView : & sstore . RemoteViewType {
2022-09-27 08:23:04 +02:00
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
2022-09-01 21:47:10 +02:00
}
func RemoteDisconnectCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Remote )
2022-09-01 21:47:10 +02:00
if err != nil {
return nil , err
}
force := resolveBool ( pk . Kwargs [ "force" ] , false )
2022-09-27 08:23:04 +02:00
go ids . Remote . MShell . Disconnect ( force )
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2023-04-04 00:55:36 +02:00
RemoteView : & sstore . RemoteViewType {
2022-09-27 08:23:04 +02:00
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
2022-09-01 21:47:10 +02:00
}
2022-10-04 04:04:48 +02:00
func makeRemoteEditUpdate_new ( err error ) sstore . UpdatePacket {
redit := & sstore . RemoteEditType {
RemoteEdit : true ,
}
2022-09-30 23:46:51 +02:00
if err != nil {
2022-10-04 04:04:48 +02:00
redit . ErrorStr = err . Error ( )
2022-09-30 23:46:51 +02:00
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-04-04 00:55:36 +02:00
RemoteView : & sstore . RemoteViewType {
2022-10-04 04:04:48 +02:00
RemoteEdit : redit ,
2022-09-30 23:46:51 +02:00
} ,
}
2022-10-04 04:04:48 +02:00
return update
2022-09-30 23:46:51 +02:00
}
2022-10-04 04:04:48 +02:00
func makeRemoteEditErrorReturn_new ( visual bool , err error ) ( sstore . UpdatePacket , error ) {
2022-09-30 23:46:51 +02:00
if visual {
2022-10-04 04:04:48 +02:00
return makeRemoteEditUpdate_new ( err ) , nil
}
return nil , err
}
func makeRemoteEditUpdate_edit ( ids resolvedIds , err error ) sstore . UpdatePacket {
redit := & sstore . RemoteEditType {
RemoteEdit : true ,
}
redit . RemoteId = ids . Remote . RemotePtr . RemoteId
if ids . Remote . RemoteCopy . SSHOpts != nil {
redit . KeyStr = ids . Remote . RemoteCopy . SSHOpts . SSHIdentity
redit . HasPassword = ( ids . Remote . RemoteCopy . SSHOpts . SSHPassword != "" )
}
if err != nil {
redit . ErrorStr = err . Error ( )
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-04-04 00:55:36 +02:00
RemoteView : & sstore . RemoteViewType {
2022-10-04 04:04:48 +02:00
RemoteEdit : redit ,
} ,
}
return update
}
func makeRemoteEditErrorReturn_edit ( ids resolvedIds , visual bool , err error ) ( sstore . UpdatePacket , error ) {
if visual {
return makeRemoteEditUpdate_edit ( ids , err ) , nil
2022-09-30 23:46:51 +02:00
}
return nil , err
}
2022-10-01 22:23:36 +02:00
type RemoteEditArgs struct {
2022-10-03 21:25:43 +02:00
CanonicalName string
SSHOpts * sstore . SSHOpts
ConnectMode string
Alias string
AutoInstall bool
SSHPassword string
SSHKeyFile string
Color string
EditMap map [ string ] interface { }
2022-10-01 22:23:36 +02:00
}
2023-04-04 00:55:36 +02:00
func parseRemoteEditArgs ( isNew bool , pk * scpacket . FeCommandPacketType , isLocal bool ) ( * RemoteEditArgs , error ) {
2022-10-03 21:25:43 +02:00
var canonicalName string
2022-10-01 22:23:36 +02:00
var sshOpts * sstore . SSHOpts
var isSudo bool
if isNew {
2022-10-04 04:04:48 +02:00
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/remote:new must specify user@host argument (set visual=1 to edit in UI)" )
}
2022-10-03 21:25:43 +02:00
userHost := pk . Args [ 0 ]
2022-10-01 22:23:36 +02:00
m := userHostRe . FindStringSubmatch ( userHost )
if m == nil {
return nil , fmt . Errorf ( "invalid format of user@host argument" )
}
2022-10-03 21:25:43 +02:00
sudoStr , remoteUser , remoteHost , remotePortStr := m [ 1 ] , m [ 2 ] , m [ 3 ] , m [ 4 ]
var uhPort int
if remotePortStr != "" {
var err error
uhPort , err = strconv . Atoi ( remotePortStr )
if err != nil {
return nil , fmt . Errorf ( "invalid port specified on user@host argument" )
}
}
2022-10-01 22:23:36 +02:00
if sudoStr != "" {
isSudo = true
}
if pk . Kwargs [ "sudo" ] != "" {
sudoArg := resolveBool ( pk . Kwargs [ "sudo" ] , false )
if isSudo && ! sudoArg {
2022-10-03 21:25:43 +02:00
return nil , fmt . Errorf ( "invalid 'sudo' argument, with sudo kw arg set to false" )
2022-10-01 22:23:36 +02:00
}
if ! isSudo && sudoArg {
isSudo = true
}
}
sshOpts = & sstore . SSHOpts {
Local : false ,
SSHHost : remoteHost ,
SSHUser : remoteUser ,
2023-05-02 21:43:54 +02:00
IsSudo : isSudo ,
2022-10-01 22:23:36 +02:00
}
portVal , err := resolvePosInt ( pk . Kwargs [ "port" ] , 0 )
if err != nil {
return nil , fmt . Errorf ( "invalid port %q: %v" , pk . Kwargs [ "port" ] , err )
}
2022-10-03 21:25:43 +02:00
if portVal != 0 && uhPort != 0 && portVal != uhPort {
return nil , fmt . Errorf ( "invalid port argument, does not match port specified in 'user@host:port' argument" )
}
if portVal == 0 && uhPort != 0 {
portVal = uhPort
}
2022-10-01 22:23:36 +02:00
sshOpts . SSHPort = portVal
2022-10-03 21:25:43 +02:00
canonicalName = remoteUser + "@" + remoteHost
if isSudo {
canonicalName = "sudo@" + canonicalName
}
2022-10-01 22:23:36 +02:00
} else {
if pk . Kwargs [ "sudo" ] != "" {
return nil , fmt . Errorf ( "cannot update 'sudo' value" )
}
if pk . Kwargs [ "port" ] != "" {
return nil , fmt . Errorf ( "cannot update 'port' value" )
}
2022-09-01 21:47:10 +02:00
}
alias := pk . Kwargs [ "alias" ]
if alias != "" {
if len ( alias ) > MaxRemoteAliasLen {
2022-10-01 22:23:36 +02:00
return nil , fmt . Errorf ( "alias too long, max length = %d" , MaxRemoteAliasLen )
2022-09-01 21:47:10 +02:00
}
if ! remoteAliasRe . MatchString ( alias ) {
2022-10-01 22:23:36 +02:00
return nil , fmt . Errorf ( "invalid alias format" )
2022-09-01 21:47:10 +02:00
}
}
2022-10-03 03:52:55 +02:00
var connectMode string
if isNew {
connectMode = sstore . ConnectModeAuto
}
2022-09-01 21:47:10 +02:00
if pk . Kwargs [ "connectmode" ] != "" {
connectMode = pk . Kwargs [ "connectmode" ]
}
2022-10-03 03:52:55 +02:00
if connectMode != "" && ! sstore . IsValidConnectMode ( connectMode ) {
2022-10-01 22:23:36 +02:00
err := fmt . Errorf ( "invalid connectmode %q: valid modes are %s" , connectMode , formatStrs ( [ ] string { sstore . ConnectModeStartup , sstore . ConnectModeAuto , sstore . ConnectModeManual } , "or" , false ) )
return nil , err
2022-09-01 21:47:10 +02:00
}
2022-10-01 22:23:36 +02:00
keyFile , err := resolveFile ( pk . Kwargs [ "key" ] )
if err != nil {
return nil , fmt . Errorf ( "invalid ssh keyfile %q: %v" , pk . Kwargs [ "key" ] , err )
2022-10-01 01:23:40 +02:00
}
2022-10-01 22:23:36 +02:00
color := pk . Kwargs [ "color" ]
if color != "" {
2022-09-01 21:47:10 +02:00
err := validateRemoteColor ( color , "remote color" )
if err != nil {
2022-10-01 22:23:36 +02:00
return nil , err
2022-09-01 21:47:10 +02:00
}
}
2022-10-01 22:23:36 +02:00
sshPassword := pk . Kwargs [ "password" ]
if sshOpts != nil {
sshOpts . SSHIdentity = keyFile
sshOpts . SSHPassword = sshPassword
}
2022-10-03 03:52:55 +02:00
// set up editmap
editMap := make ( map [ string ] interface { } )
if _ , found := pk . Kwargs [ sstore . RemoteField_Alias ] ; found {
editMap [ sstore . RemoteField_Alias ] = alias
}
if connectMode != "" {
2023-04-04 00:55:36 +02:00
if isLocal {
return nil , fmt . Errorf ( "Cannot edit connect mode for 'local' remote" )
}
2022-10-03 03:52:55 +02:00
editMap [ sstore . RemoteField_ConnectMode ] = connectMode
}
if _ , found := pk . Kwargs [ "key" ] ; found {
2023-04-04 00:55:36 +02:00
if isLocal {
return nil , fmt . Errorf ( "Cannot edit ssh key file for 'local' remote" )
}
2022-10-03 03:52:55 +02:00
editMap [ sstore . RemoteField_SSHKey ] = keyFile
}
if _ , found := pk . Kwargs [ sstore . RemoteField_Color ] ; found {
editMap [ sstore . RemoteField_Color ] = color
}
2022-10-04 04:04:48 +02:00
if _ , found := pk . Kwargs [ "password" ] ; found && pk . Kwargs [ "password" ] != PasswordUnchangedSentinel {
2023-04-04 00:55:36 +02:00
if isLocal {
return nil , fmt . Errorf ( "Cannot edit ssh password for 'local' remote" )
}
2022-10-03 03:52:55 +02:00
editMap [ sstore . RemoteField_SSHPassword ] = sshPassword
}
2022-10-01 22:23:36 +02:00
return & RemoteEditArgs {
2022-10-03 21:25:43 +02:00
SSHOpts : sshOpts ,
ConnectMode : connectMode ,
Alias : alias ,
2023-10-26 07:07:00 +02:00
AutoInstall : true ,
2022-10-03 21:25:43 +02:00
CanonicalName : canonicalName ,
SSHKeyFile : keyFile ,
SSHPassword : sshPassword ,
Color : color ,
EditMap : editMap ,
2022-10-01 22:23:36 +02:00
} , nil
}
func RemoteNewCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
visualEdit := resolveBool ( pk . Kwargs [ "visual" ] , false )
isSubmitted := resolveBool ( pk . Kwargs [ "submit" ] , false )
2022-10-04 04:04:48 +02:00
if visualEdit && ! isSubmitted && len ( pk . Args ) == 0 {
return makeRemoteEditUpdate_new ( nil ) , nil
2022-10-01 22:23:36 +02:00
}
2023-04-04 00:55:36 +02:00
editArgs , err := parseRemoteEditArgs ( true , pk , false )
2022-10-01 22:23:36 +02:00
if err != nil {
2023-10-24 08:22:18 +02:00
return nil , fmt . Errorf ( "/remote:new %v" , err )
2022-10-01 02:22:28 +02:00
}
2022-09-01 21:47:10 +02:00
r := & sstore . RemoteType {
2023-11-01 09:26:19 +01:00
RemoteId : scbase . GenWaveUUID ( ) ,
2022-09-01 21:47:10 +02:00
RemoteType : sstore . RemoteTypeSsh ,
2022-10-01 22:23:36 +02:00
RemoteAlias : editArgs . Alias ,
2022-10-03 21:25:43 +02:00
RemoteCanonicalName : editArgs . CanonicalName ,
2022-10-01 22:23:36 +02:00
RemoteUser : editArgs . SSHOpts . SSHUser ,
RemoteHost : editArgs . SSHOpts . SSHHost ,
ConnectMode : editArgs . ConnectMode ,
AutoInstall : editArgs . AutoInstall ,
SSHOpts : editArgs . SSHOpts ,
}
if editArgs . Color != "" {
r . RemoteOpts = & sstore . RemoteOptsType { Color : editArgs . Color }
}
2023-01-23 22:28:00 +01:00
err = remote . AddRemote ( ctx , r , true )
2022-09-01 21:47:10 +02:00
if err != nil {
2023-10-24 08:22:18 +02:00
return nil , fmt . Errorf ( "cannot create remote %q: %v" , r . RemoteCanonicalName , err )
2022-09-01 21:47:10 +02:00
}
2022-09-30 23:46:51 +02:00
// SUCCESS
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2023-04-04 00:55:36 +02:00
RemoteView : & sstore . RemoteViewType {
2023-01-23 22:47:36 +01:00
PtyRemoteId : r . RemoteId ,
2022-09-01 21:47:10 +02:00
} ,
2023-01-23 22:47:36 +01:00
} , nil
2022-09-01 08:12:26 +02:00
}
2022-09-13 21:06:12 +02:00
func RemoteSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Remote )
2022-09-13 21:06:12 +02:00
if err != nil {
return nil , err
}
2022-10-03 03:52:55 +02:00
visualEdit := resolveBool ( pk . Kwargs [ "visual" ] , false )
isSubmitted := resolveBool ( pk . Kwargs [ "submit" ] , false )
2023-04-04 00:55:36 +02:00
editArgs , err := parseRemoteEditArgs ( false , pk , ids . Remote . MShell . IsLocal ( ) )
2022-10-03 03:52:55 +02:00
if err != nil {
2022-10-04 04:04:48 +02:00
return makeRemoteEditErrorReturn_edit ( ids , visualEdit , fmt . Errorf ( "/remote:new %v" , err ) )
2022-10-03 03:52:55 +02:00
}
if visualEdit && ! isSubmitted && len ( editArgs . EditMap ) == 0 {
2022-10-04 04:04:48 +02:00
return makeRemoteEditUpdate_edit ( ids , nil ) , nil
}
if ! visualEdit && len ( editArgs . EditMap ) == 0 {
return nil , fmt . Errorf ( "/remote:set no updates, can set %s. (set visual=1 to edit in UI)" , formatStrs ( RemoteSetArgs , "or" , false ) )
2022-10-03 03:52:55 +02:00
}
err = ids . Remote . MShell . UpdateRemote ( ctx , editArgs . EditMap )
if err != nil {
2022-10-04 04:04:48 +02:00
return makeRemoteEditErrorReturn_edit ( ids , visualEdit , fmt . Errorf ( "/remote:new error updating remote: %v" , err ) )
2022-10-03 03:52:55 +02:00
}
2023-04-04 00:55:36 +02:00
if visualEdit {
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2023-04-04 00:55:36 +02:00
RemoteView : & sstore . RemoteViewType {
PtyRemoteId : ids . Remote . RemoteCopy . RemoteId ,
} ,
} , nil
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2022-10-03 03:52:55 +02:00
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "remote %q updated" , ids . Remote . DisplayName ) ,
TimeoutMs : 2000 ,
} ,
}
return update , nil
2022-09-13 21:06:12 +02:00
}
2022-08-27 02:17:33 +02:00
func RemoteShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Remote )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , err
2022-08-23 23:01:52 +02:00
}
2022-08-30 01:31:06 +02:00
state := ids . Remote . RState
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2023-04-04 00:55:36 +02:00
RemoteView : & sstore . RemoteViewType {
2022-09-15 09:17:23 +02:00
PtyRemoteId : state . RemoteId ,
2022-08-28 23:24:05 +02:00
} ,
} , nil
}
func RemoteShowAllCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
stateArr := remote . GetAllRemoteRuntimeState ( )
2022-08-28 23:24:05 +02:00
var buf bytes . Buffer
for _ , rstate := range stateArr {
var name string
if rstate . RemoteAlias == "" {
name = rstate . RemoteCanonicalName
} else {
name = fmt . Sprintf ( "%s (%s)" , rstate . RemoteCanonicalName , rstate . RemoteAlias )
}
buf . WriteString ( fmt . Sprintf ( "%-12s %-5s %8s %s\n" , rstate . Status , rstate . RemoteType , rstate . RemoteId [ 0 : 8 ] , name ) )
}
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2023-04-04 00:55:36 +02:00
RemoteView : & sstore . RemoteViewType {
2022-09-25 04:54:06 +02:00
RemoteShowAll : true ,
2022-08-27 02:17:33 +02:00
} ,
} , nil
2022-08-23 23:01:52 +02:00
}
2022-12-24 00:56:29 +01:00
func ScreenShowAllCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session )
2023-03-13 20:10:23 +01:00
screenArr , err := sstore . GetSessionScreens ( ctx , ids . SessionId )
2022-12-24 00:56:29 +01:00
if err != nil {
return nil , fmt . Errorf ( "/screen:showall error getting screen list: %v" , err )
}
var buf bytes . Buffer
for _ , screen := range screenArr {
2022-12-25 22:21:48 +01:00
var archivedStr string
2022-12-25 22:03:11 +01:00
if screen . Archived {
2022-12-25 22:21:48 +01:00
archivedStr = " (archived)"
2022-12-24 00:56:29 +01:00
}
screenIdxStr := "-"
if screen . ScreenIdx != 0 {
screenIdxStr = strconv . Itoa ( int ( screen . ScreenIdx ) )
}
2022-12-27 01:09:21 +01:00
outStr := fmt . Sprintf ( "%-30s %s %s\n" , screen . Name + archivedStr , screen . ScreenId , screenIdxStr )
2022-12-24 00:56:29 +01:00
buf . WriteString ( outStr )
}
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2022-12-24 00:56:29 +01:00
Info : & sstore . InfoMsgType {
InfoTitle : fmt . Sprintf ( "all screens for session" ) ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
} , nil
}
2023-02-01 02:56:56 +01:00
func ScreenResetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
if err != nil {
return nil , err
}
localRemote := remote . GetLocalRemote ( )
if localRemote == nil {
return nil , fmt . Errorf ( "error getting local remote (not found)" )
}
rptr := sstore . RemotePtrType { RemoteId : localRemote . RemoteId }
sessionUpdate := & sstore . SessionType { SessionId : ids . SessionId }
2023-03-13 09:52:30 +01:00
ris , err := sstore . ScreenReset ( ctx , ids . ScreenId )
if err != nil {
2023-03-15 00:37:22 +01:00
return nil , fmt . Errorf ( "error resetting screen: %v" , err )
2023-03-13 09:52:30 +01:00
}
sessionUpdate . Remotes = append ( sessionUpdate . Remotes , ris ... )
err = sstore . UpdateCurRemote ( ctx , ids . ScreenId , rptr )
if err != nil {
return nil , fmt . Errorf ( "cannot reset screen remote back to local: %w" , err )
2023-02-01 02:56:56 +01:00
}
outputStr := "reset screen state (all remote state reset)"
cmd , err := makeStaticCmd ( ctx , "screen:reset" , ids , pk . GetRawStr ( ) , [ ] byte ( outputStr ) )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
2023-09-17 23:10:35 +02:00
update , err := addLineForCmd ( ctx , "/screen:reset" , false , ids , cmd , "" , nil )
2023-02-01 02:56:56 +01:00
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
update . Interactive = pk . Interactive
update . Sessions = [ ] * sstore . SessionType { sessionUpdate }
return update , nil
}
2022-09-13 21:06:12 +02:00
func RemoteArchiveCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Remote )
2022-09-13 21:06:12 +02:00
if err != nil {
return nil , err
}
2022-09-14 02:11:36 +02:00
err = remote . ArchiveRemote ( ctx , ids . Remote . RemotePtr . RemoteId )
if err != nil {
return nil , fmt . Errorf ( "archiving remote: %v" , err )
}
2022-09-13 21:06:12 +02:00
update := sstore . InfoMsgUpdate ( "remote [%s] archived" , ids . Remote . DisplayName )
2022-10-04 20:45:24 +02:00
localRemote := remote . GetLocalRemote ( )
2023-03-13 09:52:30 +01:00
rptr := sstore . RemotePtrType { RemoteId : localRemote . GetRemoteId ( ) }
err = sstore . UpdateCurRemote ( ctx , ids . ScreenId , rptr )
if err != nil {
return nil , fmt . Errorf ( "cannot switch remote back to local: %w" , err )
}
screen , err := sstore . GetScreenById ( ctx , ids . ScreenId )
if err != nil {
return nil , fmt . Errorf ( "cannot get updated screen: %w" , err )
2022-10-04 20:45:24 +02:00
}
2023-03-13 09:52:30 +01:00
update . Screens = [ ] * sstore . ScreenType { screen }
2022-09-13 21:06:12 +02:00
return update , nil
}
2022-08-27 02:17:33 +02:00
func RemoteCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
return nil , fmt . Errorf ( "/remote requires a subcommand: %s" , formatStrs ( [ ] string { "show" } , "or" , false ) )
2022-08-23 01:00:25 +02:00
}
2022-12-31 02:01:17 +01:00
func crShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType , ids resolvedIds ) ( sstore . UpdatePacket , error ) {
var buf bytes . Buffer
2023-03-15 00:37:22 +01:00
riArr , err := sstore . GetRIsForScreen ( ctx , ids . SessionId , ids . ScreenId )
2022-12-31 02:01:17 +01:00
if err != nil {
return nil , fmt . Errorf ( "cannot get remote instances: %w" , err )
}
rmap := remote . GetRemoteMap ( )
for _ , ri := range riArr {
rptr := sstore . RemotePtrType { RemoteId : ri . RemoteId , Name : ri . Name }
msh := rmap [ ri . RemoteId ]
if msh == nil {
continue
}
baseDisplayName := msh . GetDisplayName ( )
displayName := rptr . GetDisplayName ( baseDisplayName )
cwdStr := "-"
2023-04-12 08:54:18 +02:00
if ri . FeState [ "cwd" ] != "" {
cwdStr = ri . FeState [ "cwd" ]
2022-12-31 02:01:17 +01:00
}
buf . WriteString ( fmt . Sprintf ( "%-30s %-50s\n" , displayName , cwdStr ) )
}
riBaseMap := make ( map [ string ] bool )
for _ , ri := range riArr {
if ri . Name == "" {
riBaseMap [ ri . RemoteId ] = true
}
}
for remoteId , msh := range rmap {
if riBaseMap [ remoteId ] {
continue
}
feState := msh . GetDefaultFeState ( )
if feState == nil {
continue
}
cwdStr := "-"
2023-04-12 08:54:18 +02:00
if feState [ "cwd" ] != "" {
cwdStr = feState [ "cwd" ]
2022-12-31 02:01:17 +01:00
}
buf . WriteString ( fmt . Sprintf ( "%-30s %-50s (default)\n" , msh . GetDisplayName ( ) , cwdStr ) )
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2022-12-31 02:01:17 +01:00
Info : & sstore . InfoMsgType {
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
}
return update , nil
}
2023-03-01 02:50:42 +01:00
func GetFullRemoteDisplayName ( rptr * sstore . RemotePtrType , rstate * remote . RemoteRuntimeState ) string {
if rptr == nil {
return "(invalid)"
}
if rstate . RemoteAlias != "" {
fullName := rstate . RemoteAlias
if rptr . Name != "" {
fullName = fullName + ":" + rptr . Name
}
return fmt . Sprintf ( "[%s] (%s)" , fullName , rstate . RemoteCanonicalName )
} else {
if rptr . Name != "" {
return fmt . Sprintf ( "[%s:%s]" , rstate . RemoteCanonicalName , rptr . Name )
}
return fmt . Sprintf ( "[%s]" , rstate . RemoteCanonicalName )
}
}
2023-05-04 10:01:13 +02:00
func writeErrorToPty ( cmd * sstore . CmdType , errStr string , outputPos int64 ) {
errPk := openai . CreateErrorPacket ( errStr )
errBytes , err := packet . MarshalPacket ( errPk )
if err != nil {
log . Printf ( "error writing error packet to openai response: %v\n" , err )
return
}
errCtx , cancelFn := context . WithTimeout ( context . Background ( ) , 5 * time . Second )
defer cancelFn ( )
2023-07-31 02:16:43 +02:00
update , err := sstore . AppendToCmdPtyBlob ( errCtx , cmd . ScreenId , cmd . LineId , errBytes , outputPos )
2023-05-04 10:01:13 +02:00
if err != nil {
log . Printf ( "error writing ptyupdate for openai response: %v\n" , err )
return
}
sstore . MainBus . SendScreenUpdate ( cmd . ScreenId , update )
return
}
func writePacketToPty ( ctx context . Context , cmd * sstore . CmdType , pk packet . PacketType , outputPos * int64 ) error {
outBytes , err := packet . MarshalPacket ( pk )
if err != nil {
return err
}
2023-07-31 02:16:43 +02:00
update , err := sstore . AppendToCmdPtyBlob ( ctx , cmd . ScreenId , cmd . LineId , outBytes , * outputPos )
2023-05-04 10:01:13 +02:00
if err != nil {
return err
}
* outputPos += int64 ( len ( outBytes ) )
sstore . MainBus . SendScreenUpdate ( cmd . ScreenId , update )
return nil
}
func doOpenAICompletion ( cmd * sstore . CmdType , opts * sstore . OpenAIOptsType , prompt [ ] sstore . OpenAIPromptMessageType ) {
var outputPos int64
var hadError bool
startTime := time . Now ( )
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , 30 * time . Second )
defer cancelFn ( )
defer func ( ) {
r := recover ( )
if r != nil {
panicMsg := fmt . Sprintf ( "panic: %v" , r )
log . Printf ( "panic in doOpenAICompletion: %s\n" , panicMsg )
writeErrorToPty ( cmd , panicMsg , outputPos )
hadError = true
}
duration := time . Since ( startTime )
cmdStatus := sstore . CmdStatusDone
2023-07-31 02:16:43 +02:00
var exitCode int
2023-05-04 10:01:13 +02:00
if hadError {
cmdStatus = sstore . CmdStatusError
exitCode = 1
}
2023-07-31 02:16:43 +02:00
ck := base . MakeCommandKey ( cmd . ScreenId , cmd . LineId )
donePk := packet . MakeCmdDonePacket ( ck )
donePk . Ts = time . Now ( ) . UnixMilli ( )
donePk . ExitCode = exitCode
donePk . DurationMs = duration . Milliseconds ( )
update , err := sstore . UpdateCmdDoneInfo ( context . Background ( ) , ck , donePk , cmdStatus )
2023-05-04 10:01:13 +02:00
if err != nil {
// nothing to do
log . Printf ( "error updating cmddoneinfo (in openai): %v\n" , err )
return
}
sstore . MainBus . SendScreenUpdate ( cmd . ScreenId , update )
} ( )
respPks , err := openai . RunCompletion ( ctx , opts , prompt )
if err != nil {
writeErrorToPty ( cmd , fmt . Sprintf ( "error calling OpenAI API: %v" , err ) , outputPos )
return
}
for _ , pk := range respPks {
err = writePacketToPty ( ctx , cmd , pk , & outputPos )
if err != nil {
writeErrorToPty ( cmd , fmt . Sprintf ( "error writing response to ptybuffer: %v" , err ) , outputPos )
return
}
}
return
}
func doOpenAIStreamCompletion ( cmd * sstore . CmdType , opts * sstore . OpenAIOptsType , prompt [ ] sstore . OpenAIPromptMessageType ) {
var outputPos int64
var hadError bool
startTime := time . Now ( )
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , 30 * time . Second )
defer cancelFn ( )
defer func ( ) {
r := recover ( )
if r != nil {
panicMsg := fmt . Sprintf ( "panic: %v" , r )
log . Printf ( "panic in doOpenAICompletion: %s\n" , panicMsg )
writeErrorToPty ( cmd , panicMsg , outputPos )
hadError = true
}
duration := time . Since ( startTime )
cmdStatus := sstore . CmdStatusDone
2023-07-31 02:16:43 +02:00
var exitCode int
2023-05-04 10:01:13 +02:00
if hadError {
cmdStatus = sstore . CmdStatusError
exitCode = 1
}
2023-07-31 02:16:43 +02:00
ck := base . MakeCommandKey ( cmd . ScreenId , cmd . LineId )
donePk := packet . MakeCmdDonePacket ( ck )
donePk . Ts = time . Now ( ) . UnixMilli ( )
donePk . ExitCode = exitCode
donePk . DurationMs = duration . Milliseconds ( )
update , err := sstore . UpdateCmdDoneInfo ( context . Background ( ) , ck , donePk , cmdStatus )
2023-05-04 10:01:13 +02:00
if err != nil {
// nothing to do
log . Printf ( "error updating cmddoneinfo (in openai): %v\n" , err )
return
}
sstore . MainBus . SendScreenUpdate ( cmd . ScreenId , update )
} ( )
ch , err := openai . RunCompletionStream ( ctx , opts , prompt )
if err != nil {
writeErrorToPty ( cmd , fmt . Sprintf ( "error calling OpenAI API: %v" , err ) , outputPos )
return
}
for pk := range ch {
err = writePacketToPty ( ctx , cmd , pk , & outputPos )
if err != nil {
writeErrorToPty ( cmd , fmt . Sprintf ( "error writing response to ptybuffer: %v" , err ) , outputPos )
return
}
}
return
}
func OpenAICommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
if err != nil {
return nil , fmt . Errorf ( "/%s error: %w" , GetCmdStr ( pk ) , err )
}
2023-05-09 01:06:51 +02:00
clientData , err := sstore . EnsureClientData ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve client data: %v" , err )
}
if clientData . OpenAIOpts == nil || clientData . OpenAIOpts . APIToken == "" {
return nil , fmt . Errorf ( "no openai API token found, configure in client settings" )
}
opts := clientData . OpenAIOpts
if opts . Model == "" {
opts . Model = openai . DefaultModel
}
if opts . MaxTokens == 0 {
opts . MaxTokens = openai . DefaultMaxTokens
2023-05-04 10:01:13 +02:00
}
promptStr := firstArg ( pk )
if promptStr == "" {
2023-05-09 02:54:38 +02:00
return nil , fmt . Errorf ( "openai error, prompt string is blank" )
2023-05-04 10:01:13 +02:00
}
ptermVal := defaultStr ( pk . Kwargs [ "pterm" ] , DefaultPTERM )
pkTermOpts , err := GetUITermOpts ( pk . UIContext . WinSize , ptermVal )
if err != nil {
2023-05-09 02:54:38 +02:00
return nil , fmt . Errorf ( "openai error, invalid 'pterm' value %q: %v" , ptermVal , err )
2023-05-04 10:01:13 +02:00
}
termOpts := convertTermOpts ( pkTermOpts )
cmd , err := makeDynCmd ( ctx , GetCmdStr ( pk ) , ids , pk . GetRawStr ( ) , * termOpts )
if err != nil {
2023-05-09 02:54:38 +02:00
return nil , fmt . Errorf ( "openai error, cannot make dyn cmd" )
2023-05-04 10:01:13 +02:00
}
line , err := sstore . AddOpenAILine ( ctx , ids . ScreenId , DefaultUserId , cmd )
if err != nil {
return nil , fmt . Errorf ( "cannot add new line: %v" , err )
}
prompt := [ ] sstore . OpenAIPromptMessageType { { Role : sstore . OpenAIRoleUser , Content : promptStr } }
2023-05-09 01:06:51 +02:00
if resolveBool ( pk . Kwargs [ "stream" ] , true ) {
2023-05-04 10:01:13 +02:00
go doOpenAIStreamCompletion ( cmd , opts , prompt )
} else {
go doOpenAICompletion ( cmd , opts , prompt )
}
updateHistoryContext ( ctx , line , cmd )
updateMap := make ( map [ string ] interface { } )
updateMap [ sstore . ScreenField_SelectedLine ] = line . LineNum
updateMap [ sstore . ScreenField_Focus ] = sstore . ScreenFocusInput
screen , err := sstore . UpdateScreen ( ctx , ids . ScreenId , updateMap )
if err != nil {
// ignore error again (nothing to do)
2023-05-09 02:54:38 +02:00
log . Printf ( "openai error updating screen selected line: %v\n" , err )
2023-05-04 10:01:13 +02:00
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate { Line : line , Cmd : cmd , Screens : [ ] * sstore . ScreenType { screen } }
2023-05-04 10:01:13 +02:00
return update , nil
}
2022-08-17 21:24:09 +02:00
func CrCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-08-17 21:24:09 +02:00
if err != nil {
2023-03-01 02:50:42 +01:00
return nil , fmt . Errorf ( "/%s error: %w" , GetCmdStr ( pk ) , err )
2022-08-17 21:24:09 +02:00
}
newRemote := firstArg ( pk )
if newRemote == "" {
2022-12-31 02:01:17 +01:00
return crShowCommand ( ctx , pk , ids )
2022-08-17 21:24:09 +02:00
}
2023-03-15 00:37:22 +01:00
_ , rptr , rstate , err := resolveRemote ( ctx , newRemote , ids . SessionId , ids . ScreenId )
2022-08-17 21:24:09 +02:00
if err != nil {
return nil , err
}
2022-08-24 11:14:16 +02:00
if rptr == nil {
2023-03-01 02:50:42 +01:00
return nil , fmt . Errorf ( "/%s error: remote %q not found" , GetCmdStr ( pk ) , newRemote )
2022-10-01 02:22:28 +02:00
}
if rstate . Archived {
2023-03-01 02:50:42 +01:00
return nil , fmt . Errorf ( "/%s error: remote %q cannot switch to archived remote" , GetCmdStr ( pk ) , newRemote )
2022-08-17 21:24:09 +02:00
}
2023-03-13 09:52:30 +01:00
err = sstore . UpdateCurRemote ( ctx , ids . ScreenId , * rptr )
2022-08-17 21:24:09 +02:00
if err != nil {
2023-03-01 02:50:42 +01:00
return nil , fmt . Errorf ( "/%s error: cannot update curremote: %w" , GetCmdStr ( pk ) , err )
2022-08-17 21:24:09 +02:00
}
2023-10-24 08:22:18 +02:00
noHist := resolveBool ( pk . Kwargs [ "nohist" ] , false )
if noHist {
screen , err := sstore . GetScreenById ( ctx , ids . ScreenId )
if err != nil {
return nil , fmt . Errorf ( "/% error: cannot resolve screen for update: %w" , err )
}
update := & sstore . ModelUpdate {
Screens : [ ] * sstore . ScreenType { screen } ,
Interactive : pk . Interactive ,
}
return update , nil
}
2023-03-01 02:50:42 +01:00
outputStr := fmt . Sprintf ( "connected to %s" , GetFullRemoteDisplayName ( rptr , rstate ) )
cmd , err := makeStaticCmd ( ctx , GetCmdStr ( pk ) , ids , pk . GetRawStr ( ) , [ ] byte ( outputStr ) )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
2022-08-17 21:24:09 +02:00
}
2023-09-17 23:10:35 +02:00
update , err := addLineForCmd ( ctx , "/" + GetCmdStr ( pk ) , false , ids , cmd , "" , nil )
2023-03-01 02:50:42 +01:00
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
update . Interactive = pk . Interactive
2022-08-17 21:24:09 +02:00
return update , nil
}
2023-05-04 10:01:13 +02:00
func makeDynCmd ( ctx context . Context , metaCmd string , ids resolvedIds , cmdStr string , termOpts sstore . TermOpts ) ( * sstore . CmdType , error ) {
cmd := & sstore . CmdType {
ScreenId : ids . ScreenId ,
2023-11-01 09:26:19 +01:00
LineId : scbase . GenWaveUUID ( ) ,
2023-05-04 10:01:13 +02:00
CmdStr : cmdStr ,
RawCmdStr : cmdStr ,
Remote : ids . Remote . RemotePtr ,
TermOpts : termOpts ,
Status : sstore . CmdStatusRunning ,
RunOut : nil ,
}
if ids . Remote . StatePtr != nil {
cmd . StatePtr = * ids . Remote . StatePtr
}
if ids . Remote . FeState != nil {
cmd . FeState = ids . Remote . FeState
}
2023-07-31 02:16:43 +02:00
err := sstore . CreateCmdPtyFile ( ctx , cmd . ScreenId , cmd . LineId , cmd . TermOpts . MaxPtySize )
2023-05-04 10:01:13 +02:00
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , fmt . Errorf ( "cannot create local ptyout file for %s command: %w" , metaCmd , err )
}
return cmd , nil
}
2022-10-19 03:03:02 +02:00
func makeStaticCmd ( ctx context . Context , metaCmd string , ids resolvedIds , cmdStr string , cmdOutput [ ] byte ) ( * sstore . CmdType , error ) {
cmd := & sstore . CmdType {
2023-03-16 02:12:55 +01:00
ScreenId : ids . ScreenId ,
2023-11-01 09:26:19 +01:00
LineId : scbase . GenWaveUUID ( ) ,
2022-10-19 03:03:02 +02:00
CmdStr : cmdStr ,
2023-03-21 03:20:57 +01:00
RawCmdStr : cmdStr ,
2022-10-19 03:03:02 +02:00
Remote : ids . Remote . RemotePtr ,
TermOpts : sstore . TermOpts { Rows : shexec . DefaultTermRows , Cols : shexec . DefaultTermCols , FlexRows : true , MaxPtySize : remote . DefaultMaxPtySize } ,
Status : sstore . CmdStatusDone ,
RunOut : nil ,
}
2022-11-29 03:03:02 +01:00
if ids . Remote . StatePtr != nil {
cmd . StatePtr = * ids . Remote . StatePtr
}
if ids . Remote . FeState != nil {
2023-04-12 08:54:18 +02:00
cmd . FeState = ids . Remote . FeState
2022-10-19 03:03:02 +02:00
}
2023-07-31 02:16:43 +02:00
err := sstore . CreateCmdPtyFile ( ctx , cmd . ScreenId , cmd . LineId , cmd . TermOpts . MaxPtySize )
2022-10-19 03:03:02 +02:00
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , fmt . Errorf ( "cannot create local ptyout file for %s command: %w" , metaCmd , err )
}
// can ignore ptyupdate
2023-07-31 02:16:43 +02:00
_ , err = sstore . AppendToCmdPtyBlob ( ctx , ids . ScreenId , cmd . LineId , cmdOutput , 0 )
2022-10-19 03:03:02 +02:00
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , fmt . Errorf ( "cannot append to local ptyout file for %s command: %v" , metaCmd , err )
}
return cmd , nil
}
2023-09-17 23:10:35 +02:00
func addLineForCmd ( ctx context . Context , metaCmd string , shouldFocus bool , ids resolvedIds , cmd * sstore . CmdType , renderer string , lineState map [ string ] any ) ( * sstore . ModelUpdate , error ) {
rtnLine , err := sstore . AddCmdLine ( ctx , ids . ScreenId , DefaultUserId , cmd , renderer , lineState )
2022-10-19 03:03:02 +02:00
if err != nil {
return nil , err
}
2023-03-13 09:52:30 +01:00
screen , err := sstore . GetScreenById ( ctx , ids . ScreenId )
2022-10-19 03:03:02 +02:00
if err != nil {
// ignore error here, because the command has already run (nothing to do)
2023-03-13 09:52:30 +01:00
log . Printf ( "%s error getting screen: %v\n" , metaCmd , err )
2022-10-19 03:03:02 +02:00
}
2023-03-13 09:52:30 +01:00
if screen != nil {
2022-10-19 03:03:02 +02:00
updateMap := make ( map [ string ] interface { } )
2023-03-13 09:52:30 +01:00
updateMap [ sstore . ScreenField_SelectedLine ] = rtnLine . LineNum
2022-10-19 03:03:02 +02:00
if shouldFocus {
2023-04-13 21:53:15 +02:00
updateMap [ sstore . ScreenField_Focus ] = sstore . ScreenFocusCmd
2022-10-19 03:03:02 +02:00
}
2023-03-13 09:52:30 +01:00
screen , err = sstore . UpdateScreen ( ctx , ids . ScreenId , updateMap )
2022-10-19 03:03:02 +02:00
if err != nil {
// ignore error again (nothing to do)
2023-03-13 09:52:30 +01:00
log . Printf ( "%s error updating screen selected line: %v\n" , metaCmd , err )
2022-10-19 03:03:02 +02:00
}
}
update := & sstore . ModelUpdate {
2023-03-13 09:52:30 +01:00
Line : rtnLine ,
Cmd : cmd ,
Screens : [ ] * sstore . ScreenType { screen } ,
2022-10-19 03:03:02 +02:00
}
updateHistoryContext ( ctx , rtnLine , cmd )
return update , nil
}
func updateHistoryContext ( ctx context . Context , line * sstore . LineType , cmd * sstore . CmdType ) {
ctxVal := ctx . Value ( historyContextKey )
if ctxVal == nil {
return
}
hctx := ctxVal . ( * historyContextType )
if line != nil {
hctx . LineId = line . LineId
2023-03-18 05:36:49 +01:00
hctx . LineNum = line . LineNum
2022-10-19 03:03:02 +02:00
}
if cmd != nil {
hctx . RemotePtr = & cmd . Remote
}
}
2022-08-11 03:33:32 +02:00
func makeInfoFromComps ( compType string , comps [ ] string , hasMore bool ) sstore . UpdatePacket {
2022-08-24 02:26:42 +02:00
sort . Slice ( comps , func ( i int , j int ) bool {
c1 := comps [ i ]
c2 := comps [ j ]
c1mc := strings . HasPrefix ( c1 , "^" )
c2mc := strings . HasPrefix ( c2 , "^" )
if c1mc && ! c2mc {
return true
}
if ! c1mc && c2mc {
return false
}
return c1 < c2
} )
2022-08-23 22:37:08 +02:00
if len ( comps ) == 0 {
comps = [ ] string { "(no completions)" }
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2022-08-11 03:33:32 +02:00
Info : & sstore . InfoMsgType {
2022-08-11 19:21:45 +02:00
InfoTitle : fmt . Sprintf ( "%s completions" , compType ) ,
InfoComps : comps ,
InfoCompsMore : hasMore ,
2022-08-11 03:33:32 +02:00
} ,
}
return update
}
2022-11-23 20:12:05 +01:00
func simpleCompCommandMeta ( ctx context . Context , prefix string , compCtx comp . CompContext , args [ ] interface { } ) ( * comp . CompReturn , error ) {
2022-11-11 03:51:20 +01:00
if strings . HasPrefix ( prefix , "/" ) {
compsCmd , _ := comp . DoSimpleComp ( ctx , comp . CGTypeCommand , prefix , compCtx , nil )
2022-11-23 20:12:05 +01:00
compsMeta , _ := simpleCompMeta ( ctx , prefix , compCtx , nil )
2022-11-11 03:51:20 +01:00
return comp . CombineCompReturn ( comp . CGTypeCommandMeta , compsCmd , compsMeta ) , nil
} else {
2023-10-26 09:01:52 +02:00
compsCmd , _ := comp . DoSimpleComp ( ctx , comp . CGTypeCommand , prefix , compCtx , nil )
compsBareCmd , _ := simpleCompBareCmds ( ctx , prefix , compCtx , nil )
return comp . CombineCompReturn ( comp . CGTypeCommand , compsCmd , compsBareCmd ) , nil
2022-11-11 03:51:20 +01:00
}
2022-11-10 22:52:51 +01:00
}
2023-10-26 09:01:52 +02:00
func simpleCompBareCmds ( ctx context . Context , prefix string , compCtx comp . CompContext , args [ ] interface { } ) ( * comp . CompReturn , error ) {
rtn := comp . CompReturn { }
for _ , bmc := range BareMetaCmds {
if strings . HasPrefix ( bmc . CmdStr , prefix ) {
rtn . Entries = append ( rtn . Entries , comp . CompEntry { Word : bmc . CmdStr , IsMetaCmd : true } )
}
}
return & rtn , nil
}
2022-11-23 20:12:05 +01:00
func simpleCompMeta ( ctx context . Context , prefix string , compCtx comp . CompContext , args [ ] interface { } ) ( * comp . CompReturn , error ) {
2022-11-10 22:52:51 +01:00
rtn := comp . CompReturn { }
validCommands := getValidCommands ( )
for _ , cmd := range validCommands {
2022-11-23 20:12:05 +01:00
if strings . HasPrefix ( cmd , "/_" ) && ! strings . HasPrefix ( prefix , "/_" ) {
continue
}
2022-11-10 22:52:51 +01:00
if strings . HasPrefix ( cmd , prefix ) {
rtn . Entries = append ( rtn . Entries , comp . CompEntry { Word : cmd , IsMetaCmd : true } )
}
}
return & rtn , nil
}
2022-08-30 01:31:06 +02:00
func doMetaCompGen ( ctx context . Context , pk * scpacket . FeCommandPacketType , prefix string , forDisplay bool ) ( [ ] string , bool , error ) {
ids , err := resolveUiIds ( ctx , pk , 0 ) // best effort
var comps [ ] string
var hasMore bool
if ids . Remote != nil && ids . Remote . RState . IsConnected ( ) {
comps , hasMore , err = doCompGen ( ctx , pk , prefix , "file" , forDisplay )
if err != nil {
return nil , false , err
}
2022-08-23 22:37:08 +02:00
}
2022-08-27 02:17:33 +02:00
validCommands := getValidCommands ( )
for _ , cmd := range validCommands {
2022-08-11 03:33:32 +02:00
if strings . HasPrefix ( cmd , prefix ) {
2022-08-24 02:26:42 +02:00
if forDisplay {
comps = append ( comps , "^" + cmd )
} else {
comps = append ( comps , cmd )
}
2022-08-11 03:33:32 +02:00
}
}
2022-08-23 22:37:08 +02:00
return comps , hasMore , nil
2022-08-11 03:33:32 +02:00
}
2022-08-30 01:31:06 +02:00
func doCompGen ( ctx context . Context , pk * scpacket . FeCommandPacketType , prefix string , compType string , forDisplay bool ) ( [ ] string , bool , error ) {
2022-08-11 03:33:32 +02:00
if compType == "metacommand" {
2022-08-30 01:31:06 +02:00
return doMetaCompGen ( ctx , pk , prefix , forDisplay )
2022-08-09 23:24:57 +02:00
}
if ! packet . IsValidCompGenType ( compType ) {
2022-11-23 20:12:05 +01:00
return nil , false , fmt . Errorf ( "/_compgen invalid type '%s'" , compType )
2022-08-09 23:24:57 +02:00
}
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_RemoteConnected )
2022-08-30 01:31:06 +02:00
if err != nil {
2022-11-23 20:12:05 +01:00
return nil , false , fmt . Errorf ( "/_compgen error: %w" , err )
2022-08-30 01:31:06 +02:00
}
2022-08-09 23:24:57 +02:00
cgPacket := packet . MakeCompGenPacket ( )
cgPacket . ReqId = uuid . New ( ) . String ( )
cgPacket . CompType = compType
cgPacket . Prefix = prefix
2023-04-12 08:54:18 +02:00
cgPacket . Cwd = ids . Remote . FeState [ "cwd" ]
2022-08-30 01:31:06 +02:00
resp , err := ids . Remote . MShell . PacketRpc ( ctx , cgPacket )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-11 03:33:32 +02:00
return nil , false , err
}
if err = resp . Err ( ) ; err != nil {
return nil , false , err
2022-07-16 02:37:32 +02:00
}
2022-11-10 22:52:51 +01:00
comps := utilfn . GetStrArr ( resp . Data , "comps" )
hasMore := utilfn . GetBool ( resp . Data , "hasmore" )
2022-08-11 03:33:32 +02:00
return comps , hasMore , nil
}
func CompGenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-11-11 03:51:20 +01:00
ids , err := resolveUiIds ( ctx , pk , 0 ) // best-effort
if err != nil {
2022-11-23 20:12:05 +01:00
return nil , fmt . Errorf ( "/_compgen error: %w" , err )
2022-11-11 03:51:20 +01:00
}
2022-08-11 03:33:32 +02:00
cmdLine := firstArg ( pk )
pos := len ( cmdLine )
if pk . Kwargs [ "comppos" ] != "" {
posArg , err := strconv . Atoi ( pk . Kwargs [ "comppos" ] )
if err != nil {
2022-11-23 20:12:05 +01:00
return nil , fmt . Errorf ( "/_compgen invalid comppos '%s': %w" , pk . Kwargs [ "comppos" ] , err )
2022-08-11 03:33:32 +02:00
}
pos = posArg
}
if pos < 0 {
pos = 0
}
if pos > len ( cmdLine ) {
pos = len ( cmdLine )
}
showComps := resolveBool ( pk . Kwargs [ "compshow" ] , false )
2022-11-22 09:26:41 +01:00
cmdSP := utilfn . StrWithPos { Str : cmdLine , Pos : pos }
2022-11-11 03:51:20 +01:00
compCtx := comp . CompContext { }
if ids . Remote != nil {
rptr := ids . Remote . RemotePtr
compCtx . RemotePtr = & rptr
2022-11-29 03:03:02 +01:00
if ids . Remote . FeState != nil {
2023-04-12 08:54:18 +02:00
compCtx . Cwd = ids . Remote . FeState [ "cwd" ]
2022-11-29 03:03:02 +01:00
}
2022-11-11 03:51:20 +01:00
}
compCtx . ForDisplay = showComps
crtn , newSP , err := comp . DoCompGen ( ctx , cmdSP , compCtx )
2022-08-11 03:33:32 +02:00
if err != nil {
return nil , err
}
2022-11-11 03:51:20 +01:00
if crtn == nil {
2022-11-22 09:26:41 +01:00
return nil , nil
2022-08-11 03:33:32 +02:00
}
2022-11-22 09:26:41 +01:00
if showComps {
2022-11-11 03:51:20 +01:00
compStrs := crtn . GetCompDisplayStrs ( )
return makeInfoFromComps ( crtn . CompType , compStrs , crtn . HasMore ) , nil
}
2022-11-22 09:26:41 +01:00
if newSP == nil || cmdSP == * newSP {
return nil , nil
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2022-11-11 03:51:20 +01:00
CmdLine : & sstore . CmdLineType { CmdLine : newSP . Str , CursorPos : newSP . Pos } ,
}
return update , nil
2022-07-16 02:37:32 +02:00
}
func CommentCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/comment error: %w" , err )
2022-07-16 02:37:32 +02:00
}
text := firstArg ( pk )
if strings . TrimSpace ( text ) == "" {
return nil , fmt . Errorf ( "cannot post empty comment" )
}
2023-03-21 03:20:57 +01:00
rtnLine , err := sstore . AddCommentLine ( ctx , ids . ScreenId , DefaultUserId , text )
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
2023-03-03 07:26:15 +01:00
updateHistoryContext ( ctx , rtnLine , nil )
2022-10-11 02:30:48 +02:00
updateMap := make ( map [ string ] interface { } )
2023-03-13 09:52:30 +01:00
updateMap [ sstore . ScreenField_SelectedLine ] = rtnLine . LineNum
updateMap [ sstore . ScreenField_Focus ] = sstore . ScreenFocusInput
screen , err := sstore . UpdateScreen ( ctx , ids . ScreenId , updateMap )
2022-10-11 02:30:48 +02:00
if err != nil {
// ignore error again (nothing to do)
2023-03-15 00:37:22 +01:00
log . Printf ( "/comment error updating screen selected line: %v\n" , err )
2022-10-11 02:30:48 +02:00
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate { Line : rtnLine , Screens : [ ] * sstore . ScreenType { screen } }
2022-10-11 02:30:48 +02:00
return update , nil
2022-07-16 02:37:32 +02:00
}
2022-07-16 02:53:23 +02:00
2022-08-27 01:21:19 +02:00
func maybeQuote ( s string , quote bool ) string {
if quote {
return fmt . Sprintf ( "%q" , s )
}
return s
}
2022-08-27 02:17:33 +02:00
func mapToStrs ( m map [ string ] bool ) [ ] string {
var rtn [ ] string
for key , val := range m {
if val {
rtn = append ( rtn , key )
}
}
return rtn
}
2022-08-27 01:21:19 +02:00
func formatStrs ( strs [ ] string , conj string , quote bool ) string {
if len ( strs ) == 0 {
return "(none)"
}
if len ( strs ) == 1 {
return maybeQuote ( strs [ 0 ] , quote )
}
if len ( strs ) == 2 {
return fmt . Sprintf ( "%s %s %s" , maybeQuote ( strs [ 0 ] , quote ) , conj , maybeQuote ( strs [ 1 ] , quote ) )
}
var buf bytes . Buffer
for idx := 0 ; idx < len ( strs ) - 1 ; idx ++ {
buf . WriteString ( maybeQuote ( strs [ idx ] , quote ) )
buf . WriteString ( ", " )
}
buf . WriteString ( conj )
buf . WriteString ( " " )
buf . WriteString ( maybeQuote ( strs [ len ( strs ) - 1 ] , quote ) )
return buf . String ( )
}
func validateName ( name string , typeStr string ) error {
if len ( name ) > MaxNameLen {
return fmt . Errorf ( "%s name too long, max length is %d" , typeStr , MaxNameLen )
}
if ! genericNameRe . MatchString ( name ) {
return fmt . Errorf ( "invalid %s name" , typeStr )
}
return nil
}
2023-03-24 18:34:07 +01:00
func validateShareName ( name string ) error {
if len ( name ) > MaxShareNameLen {
return fmt . Errorf ( "share name too long, max length is %d" , MaxShareNameLen )
}
for _ , ch := range name {
if ! unicode . IsPrint ( ch ) {
return fmt . Errorf ( "invalid character %q in share name" , string ( ch ) )
}
}
return nil
}
2023-03-17 05:46:10 +01:00
func validateRenderer ( renderer string ) error {
if renderer == "" {
return nil
}
if len ( renderer ) > MaxRendererLen {
return fmt . Errorf ( "renderer name too long, max length is %d" , MaxRendererLen )
}
if ! rendererRe . MatchString ( renderer ) {
return fmt . Errorf ( "invalid renderer format" )
}
return nil
}
2022-08-27 06:44:18 +02:00
func validateColor ( color string , typeStr string ) error {
for _ , c := range ColorNames {
if color == c {
return nil
}
}
return fmt . Errorf ( "invalid %s, valid colors are: %s" , typeStr , formatStrs ( ColorNames , "or" , false ) )
}
2023-11-07 09:04:25 +01:00
func validateIcon ( icon string , typeStr string ) error {
for _ , c := range TabIcons {
if icon == c {
return nil
}
}
return fmt . Errorf ( "invalid %s, valid icons are: %s" , typeStr , formatStrs ( TabIcons , "or" , false ) )
}
2022-09-01 21:47:10 +02:00
func validateRemoteColor ( color string , typeStr string ) error {
for _ , c := range RemoteColorNames {
if color == c {
return nil
}
}
return fmt . Errorf ( "invalid %s, valid colors are: %s" , typeStr , formatStrs ( RemoteColorNames , "or" , false ) )
}
2023-02-22 07:41:56 +01:00
func SessionOpenSharedCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
activity := sstore . ActivityUpdate { ClickShared : 1 }
err := sstore . UpdateCurrentActivity ( ctx , activity )
if err != nil {
log . Printf ( "error updating click-shared: %v\n" , err )
}
return nil , fmt . Errorf ( "shared sessions are not available in this version of prompt (stay tuned)" )
}
2023-03-24 18:34:07 +01:00
func SessionOpenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-10 03:44:01 +01:00
activate := resolveBool ( pk . Kwargs [ "activate" ] , true )
newName := pk . Kwargs [ "name" ]
if newName != "" {
err := validateName ( newName , "session" )
if err != nil {
return nil , err
}
}
2023-07-26 19:10:27 +02:00
update , err := sstore . InsertSessionWithName ( ctx , newName , activate )
2023-03-10 03:44:01 +01:00
if err != nil {
return nil , err
}
return update , nil
}
2023-03-31 03:08:35 +02:00
func makeExternLink ( urlStr string ) string {
return fmt . Sprintf ( ` https://extern?%s ` , url . QueryEscape ( urlStr ) )
}
2023-03-24 18:34:07 +01:00
func ScreenWebShareCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-09-15 22:48:58 +02:00
return nil , fmt . Errorf ( "websharing is no longer available" )
2022-08-27 02:17:33 +02:00
}
2022-09-13 21:06:12 +02:00
func SessionDeleteCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-04-18 00:23:58 +02:00
ids , err := resolveUiIds ( ctx , pk , 0 ) // don't force R_Session
2022-09-13 21:06:12 +02:00
if err != nil {
return nil , err
}
2023-04-18 00:23:58 +02:00
sessionId := ""
if len ( pk . Args ) >= 1 {
ritem , err := resolveSession ( ctx , pk . Args [ 0 ] , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "/session:purge error resolving session %q: %w" , pk . Args [ 0 ] , err )
}
if ritem == nil {
return nil , fmt . Errorf ( "/session:purge session %q not found" , pk . Args [ 0 ] )
}
sessionId = ritem . Id
} else {
sessionId = ids . SessionId
}
if sessionId == "" {
return nil , fmt . Errorf ( "/session:purge no sessionid found" )
}
update , err := sstore . PurgeSession ( ctx , sessionId )
2022-09-13 21:06:12 +02:00
if err != nil {
return nil , fmt . Errorf ( "cannot delete session: %v" , err )
}
2022-12-27 01:09:21 +01:00
return update , nil
}
func SessionArchiveCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , 0 ) // don't force R_Session
if err != nil {
return nil , err
2022-09-13 21:06:12 +02:00
}
2022-12-27 01:09:21 +01:00
sessionId := ""
if len ( pk . Args ) >= 1 {
ritem , err := resolveSession ( ctx , pk . Args [ 0 ] , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "/session:archive error resolving session %q: %w" , pk . Args [ 0 ] , err )
2022-12-24 00:56:29 +01:00
}
2022-12-27 01:09:21 +01:00
if ritem == nil {
return nil , fmt . Errorf ( "/session:archive session %q not found" , pk . Args [ 0 ] )
}
sessionId = ritem . Id
} else {
sessionId = ids . SessionId
2022-09-13 21:06:12 +02:00
}
2022-12-27 01:09:21 +01:00
if sessionId == "" {
return nil , fmt . Errorf ( "/session:archive no sessionid found" )
}
archiveVal := true
if len ( pk . Args ) >= 2 {
archiveVal = resolveBool ( pk . Args [ 1 ] , true )
}
if archiveVal {
update , err := sstore . ArchiveSession ( ctx , sessionId )
if err != nil {
return nil , fmt . Errorf ( "cannot archive session: %v" , err )
}
2022-12-27 03:42:55 +01:00
update . Info = & sstore . InfoMsgType {
InfoMsg : "session archived" ,
}
2022-12-27 01:09:21 +01:00
return update , nil
} else {
2022-12-27 03:42:55 +01:00
activate := resolveBool ( pk . Kwargs [ "activate" ] , false )
update , err := sstore . UnArchiveSession ( ctx , sessionId , activate )
2022-12-27 01:09:21 +01:00
if err != nil {
return nil , fmt . Errorf ( "cannot un-archive session: %v" , err )
}
2022-12-27 03:42:55 +01:00
update . Info = & sstore . InfoMsgType {
InfoMsg : "session un-archived" ,
}
2022-12-27 01:09:21 +01:00
return update , nil
}
}
2022-12-27 04:06:46 +01:00
func SessionShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session )
if err != nil {
return nil , err
}
session , err := sstore . GetSessionById ( ctx , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "cannot get session: %w" , err )
}
if session == nil {
return nil , fmt . Errorf ( "session not found" )
}
var buf bytes . Buffer
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "sessionid" , session . SessionId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "name" , session . Name ) )
if session . SessionIdx != 0 {
buf . WriteString ( fmt . Sprintf ( " %-15s %d\n" , "index" , session . SessionIdx ) )
}
if session . Archived {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "archived" , "true" ) )
ts := time . UnixMilli ( session . ArchivedTs )
2023-09-01 07:04:31 +02:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "archivedts" , ts . Format ( TsFormatStr ) ) )
2022-12-27 04:06:46 +01:00
}
stats , err := sstore . GetSessionStats ( ctx , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "error getting session stats: %w" , err )
}
var screenArchiveStr string
if stats . NumArchivedScreens > 0 {
screenArchiveStr = fmt . Sprintf ( " (%d archived)" , stats . NumArchivedScreens )
}
buf . WriteString ( fmt . Sprintf ( " %-15s %d%s\n" , "screens" , stats . NumScreens , screenArchiveStr ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %d\n" , "lines" , stats . NumLines ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %d\n" , "cmds" , stats . NumCmds ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %0.2fM\n" , "disksize" , float64 ( stats . DiskStats . TotalSize ) / 1000000 ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "disk-location" , stats . DiskStats . Location ) )
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2022-12-27 04:06:46 +01:00
Info : & sstore . InfoMsgType {
InfoTitle : "session info" ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
} , nil
}
2022-12-27 01:09:21 +01:00
func SessionShowAllCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
sessions , err := sstore . GetBareSessions ( ctx )
if err != nil {
return nil , fmt . Errorf ( "error retrieving sessions: %v" , err )
}
var buf bytes . Buffer
for _ , session := range sessions {
var archivedStr string
if session . Archived {
archivedStr = " (archived)"
}
sessionIdxStr := "-"
if session . SessionIdx != 0 {
sessionIdxStr = strconv . Itoa ( int ( session . SessionIdx ) )
}
outStr := fmt . Sprintf ( "%-30s %s %s\n" , session . Name + archivedStr , session . SessionId , sessionIdxStr )
buf . WriteString ( outStr )
}
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2022-12-27 01:09:21 +01:00
Info : & sstore . InfoMsgType {
InfoTitle : "all sessions" ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
} , nil
2022-09-13 21:06:12 +02:00
}
2022-08-27 02:17:33 +02:00
func SessionSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , err
}
var varsUpdated [ ] string
if pk . Kwargs [ "name" ] != "" {
newName := pk . Kwargs [ "name" ]
err = validateName ( newName , "session" )
2022-08-26 22:12:17 +02:00
if err != nil {
return nil , err
}
2022-08-27 02:17:33 +02:00
err = sstore . SetSessionName ( ctx , ids . SessionId , newName )
2022-08-26 22:12:17 +02:00
if err != nil {
2022-08-27 02:17:33 +02:00
return nil , fmt . Errorf ( "setting session name: %v" , err )
2022-08-26 22:12:17 +02:00
}
2022-08-27 02:17:33 +02:00
varsUpdated = append ( varsUpdated , "name" )
}
if pk . Kwargs [ "pos" ] != "" {
2022-08-27 01:21:19 +02:00
2022-08-26 22:12:17 +02:00
}
2022-08-27 02:17:33 +02:00
if len ( varsUpdated ) == 0 {
return nil , fmt . Errorf ( "/session:set no updates, can set %s" , formatStrs ( [ ] string { "name" , "pos" } , "or" , false ) )
}
2022-08-27 02:51:28 +02:00
bareSession , err := sstore . GetBareSessionById ( ctx , ids . SessionId )
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2022-08-27 02:17:33 +02:00
Sessions : [ ] * sstore . SessionType { bareSession } ,
Info : & sstore . InfoMsgType {
2022-08-27 02:51:28 +02:00
InfoMsg : fmt . Sprintf ( "session updated %s" , formatStrs ( varsUpdated , "and" , false ) ) ,
2022-08-27 02:17:33 +02:00
TimeoutMs : 2000 ,
} ,
2022-08-09 01:21:46 +02:00
}
2022-08-27 02:17:33 +02:00
return update , nil
}
func SessionCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-09-13 21:06:12 +02:00
ids , err := resolveUiIds ( ctx , pk , 0 )
if err != nil {
return nil , err
}
2022-08-09 01:21:46 +02:00
firstArg := firstArg ( pk )
if firstArg == "" {
2022-08-27 01:21:19 +02:00
return nil , fmt . Errorf ( "usage /session [name|id|pos], no param specified" )
2022-08-09 01:21:46 +02:00
}
2022-12-27 01:09:21 +01:00
ritem , err := resolveSession ( ctx , firstArg , ids . SessionId )
2022-08-27 01:21:19 +02:00
if err != nil {
2022-08-27 02:17:33 +02:00
return nil , err
2022-08-27 01:21:19 +02:00
}
2022-08-30 01:31:06 +02:00
err = sstore . SetActiveSessionId ( ctx , ritem . Id )
if err != nil {
return nil , err
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2022-08-27 02:17:33 +02:00
ActiveSessionId : ritem . Id ,
2022-08-27 01:21:19 +02:00
Info : & sstore . InfoMsgType {
2022-08-27 02:17:33 +02:00
InfoMsg : fmt . Sprintf ( "switched to session %q" , ritem . Name ) ,
2022-08-27 01:21:19 +02:00
TimeoutMs : 2000 ,
} ,
}
return update , nil
2022-07-16 02:53:23 +02:00
}
2022-08-24 02:26:42 +02:00
2022-12-20 02:36:19 +01:00
func RemoteResetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-10-28 02:10:36 +02:00
if err != nil {
return nil , err
}
initPk , err := ids . Remote . MShell . ReInit ( ctx )
if err != nil {
return nil , err
}
if initPk == nil || initPk . State == nil {
return nil , fmt . Errorf ( "invalid initpk received from remote (no remote state)" )
}
2022-11-28 09:13:00 +01:00
feState := sstore . FeStateFromShellState ( initPk . State )
2023-04-12 08:54:18 +02:00
remoteInst , err := sstore . UpdateRemoteState ( ctx , ids . SessionId , ids . ScreenId , ids . Remote . RemotePtr , feState , initPk . State , nil )
2022-10-28 02:10:36 +02:00
if err != nil {
return nil , err
}
outputStr := "reset remote state"
cmd , err := makeStaticCmd ( ctx , "reset" , ids , pk . GetRawStr ( ) , [ ] byte ( outputStr ) )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
2023-09-17 23:10:35 +02:00
update , err := addLineForCmd ( ctx , "/reset" , false , ids , cmd , "" , nil )
2022-10-28 02:10:36 +02:00
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
update . Interactive = pk . Interactive
update . Sessions = sstore . MakeSessionsUpdateForRemote ( ids . SessionId , remoteInst )
return update , nil
2022-10-25 06:29:11 +02:00
}
2022-08-27 07:01:29 +02:00
func ClearCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-08-27 07:01:29 +02:00
if err != nil {
return nil , err
}
2023-01-02 21:09:19 +01:00
if resolveBool ( pk . Kwargs [ "purge" ] , false ) {
2023-03-13 09:52:30 +01:00
update , err := sstore . PurgeScreenLines ( ctx , ids . ScreenId )
2023-01-02 21:09:19 +01:00
if err != nil {
2023-03-13 09:52:30 +01:00
return nil , fmt . Errorf ( "clearing screen: %v" , err )
2023-01-02 21:09:19 +01:00
}
update . Info = & sstore . InfoMsgType {
2023-03-13 09:52:30 +01:00
InfoMsg : fmt . Sprintf ( "screen cleared (all lines purged)" ) ,
2023-01-02 21:09:19 +01:00
TimeoutMs : 2000 ,
}
return update , nil
} else {
2023-03-13 09:52:30 +01:00
update , err := sstore . ArchiveScreenLines ( ctx , ids . ScreenId )
2023-01-02 21:09:19 +01:00
if err != nil {
2023-03-13 09:52:30 +01:00
return nil , fmt . Errorf ( "clearing screen: %v" , err )
2023-01-02 21:09:19 +01:00
}
update . Info = & sstore . InfoMsgType {
2023-03-13 09:52:30 +01:00
InfoMsg : fmt . Sprintf ( "screen cleared" ) ,
2023-01-02 21:09:19 +01:00
TimeoutMs : 2000 ,
}
return update , nil
2022-08-27 07:01:29 +02:00
}
2023-01-02 21:09:19 +01:00
2022-08-27 07:01:29 +02:00
}
2023-03-03 22:31:16 +01:00
func HistoryPurgeCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/history:purge requires at least one argument (history id)" )
}
var historyIds [ ] string
for _ , historyArg := range pk . Args {
_ , err := uuid . Parse ( historyArg )
if err != nil {
return nil , fmt . Errorf ( "invalid historyid (must be uuid)" )
}
historyIds = append ( historyIds , historyArg )
}
historyItemsRemoved , err := sstore . PurgeHistoryByIds ( ctx , historyIds )
if err != nil {
return nil , fmt . Errorf ( "/history:purge error purging items: %v" , err )
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate { }
2023-03-03 22:31:16 +01:00
for _ , historyItem := range historyItemsRemoved {
if historyItem . LineId == "" {
continue
}
lineObj := & sstore . LineType {
2023-03-21 03:20:57 +01:00
ScreenId : historyItem . ScreenId ,
LineId : historyItem . LineId ,
Remove : true ,
2023-03-03 22:31:16 +01:00
}
update . Lines = append ( update . Lines , lineObj )
}
return update , nil
}
2023-03-03 07:26:15 +01:00
const HistoryViewPageSize = 50
2023-03-06 22:54:38 +01:00
var cmdFilterLs = regexp . MustCompile ( ` ^ls(\s|$) ` )
var cmdFilterCd = regexp . MustCompile ( ` ^cd(\s|$) ` )
func historyCmdFilter ( hitem * sstore . HistoryItemType ) bool {
cmdStr := hitem . CmdStr
if cmdStr == "" || strings . Index ( cmdStr , ";" ) != - 1 || strings . Index ( cmdStr , "\n" ) != - 1 {
return true
}
if cmdFilterLs . MatchString ( cmdStr ) {
return false
}
if cmdFilterCd . MatchString ( cmdStr ) {
return false
}
return true
}
2023-03-02 09:31:19 +01:00
func HistoryViewAllCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
_ , err := resolveUiIds ( ctx , pk , 0 )
if err != nil {
return nil , err
}
offset , err := resolveNonNegInt ( pk . Kwargs [ "offset" ] , 0 )
if err != nil {
return nil , err
}
2023-03-06 20:47:44 +01:00
rawOffset , err := resolveNonNegInt ( pk . Kwargs [ "rawoffset" ] , 0 )
if err != nil {
return nil , err
}
opts := sstore . HistoryQueryOpts { MaxItems : HistoryViewPageSize , Offset : offset , RawOffset : rawOffset }
2023-03-02 09:31:19 +01:00
if pk . Kwargs [ "text" ] != "" {
opts . SearchText = pk . Kwargs [ "text" ]
}
2023-03-04 03:12:24 +01:00
if pk . Kwargs [ "searchsession" ] != "" {
sessionId , err := resolveSessionArg ( pk . Kwargs [ "searchsession" ] )
if err != nil {
return nil , fmt . Errorf ( "invalid searchsession: %v" , err )
}
opts . SessionId = sessionId
}
if pk . Kwargs [ "searchremote" ] != "" {
rptr , err := resolveRemoteArg ( pk . Kwargs [ "searchremote" ] )
if err != nil {
return nil , fmt . Errorf ( "invalid searchremote: %v" , err )
}
if rptr != nil {
opts . RemoteId = rptr . RemoteId
}
}
if pk . Kwargs [ "fromts" ] != "" {
fromTs , err := resolvePosInt ( pk . Kwargs [ "fromts" ] , 0 )
if err != nil {
return nil , fmt . Errorf ( "invalid fromts (must be unixtime (milliseconds): %v" , err )
}
if fromTs > 0 {
opts . FromTs = int64 ( fromTs )
}
}
2023-03-05 22:53:24 +01:00
if pk . Kwargs [ "meta" ] != "" {
opts . NoMeta = ! resolveBool ( pk . Kwargs [ "meta" ] , true )
}
2023-03-06 22:54:38 +01:00
if resolveBool ( pk . Kwargs [ "filter" ] , false ) {
opts . FilterFn = historyCmdFilter
}
2023-03-04 03:12:24 +01:00
if err != nil {
return nil , fmt . Errorf ( "invalid meta arg (must be boolean): %v" , err )
}
2023-03-06 20:47:44 +01:00
hresult , err := sstore . GetHistoryItems ( ctx , opts )
2023-03-02 09:31:19 +01:00
if err != nil {
return nil , err
}
2023-03-06 20:47:44 +01:00
hvdata := & sstore . HistoryViewData {
Items : hresult . Items ,
Offset : hresult . Offset ,
2023-03-06 22:54:38 +01:00
RawOffset : hresult . RawOffset ,
2023-03-06 20:47:44 +01:00
NextRawOffset : hresult . NextRawOffset ,
HasMore : hresult . HasMore ,
2023-03-03 07:26:15 +01:00
}
lines , cmds , err := sstore . GetLineCmdsFromHistoryItems ( ctx , hvdata . Items )
if err != nil {
return nil , err
2023-03-02 09:31:19 +01:00
}
2023-03-03 07:26:15 +01:00
hvdata . Lines = lines
hvdata . Cmds = cmds
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-03-02 09:31:19 +01:00
HistoryViewData : hvdata ,
MainView : sstore . MainViewHistory ,
}
return update , nil
}
2022-08-28 23:24:05 +02:00
const DefaultMaxHistoryItems = 10000
func HistoryCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Remote )
2022-08-28 23:24:05 +02:00
if err != nil {
return nil , err
}
2022-10-01 22:23:36 +02:00
maxItems , err := resolvePosInt ( pk . Kwargs [ "maxitems" ] , DefaultMaxHistoryItems )
2022-08-28 23:24:05 +02:00
if err != nil {
return nil , fmt . Errorf ( "invalid maxitems value '%s' (must be a number): %v" , pk . Kwargs [ "maxitems" ] , err )
}
if maxItems < 0 {
2022-10-21 01:14:14 +02:00
return nil , fmt . Errorf ( "invalid maxitems value '%d' (cannot be negative)" , maxItems )
2022-08-28 23:24:05 +02:00
}
if maxItems == 0 {
maxItems = DefaultMaxHistoryItems
}
2023-03-15 00:37:22 +01:00
htype := HistoryTypeScreen
2022-08-31 22:28:52 +02:00
hSessionId := ids . SessionId
2023-03-15 00:37:22 +01:00
hScreenId := ids . ScreenId
2022-08-31 22:28:52 +02:00
if pk . Kwargs [ "type" ] != "" {
htype = pk . Kwargs [ "type" ]
2023-03-15 00:37:22 +01:00
if htype != HistoryTypeScreen && htype != HistoryTypeSession && htype != HistoryTypeGlobal {
return nil , fmt . Errorf ( "invalid history type '%s', valid types: %s" , htype , formatStrs ( [ ] string { HistoryTypeScreen , HistoryTypeSession , HistoryTypeGlobal } , "or" , false ) )
2022-08-31 22:28:52 +02:00
}
}
if htype == HistoryTypeGlobal {
hSessionId = ""
2023-03-15 00:37:22 +01:00
hScreenId = ""
2022-08-31 22:28:52 +02:00
} else if htype == HistoryTypeSession {
2023-03-15 00:37:22 +01:00
hScreenId = ""
2022-08-31 22:28:52 +02:00
}
2023-03-15 00:37:22 +01:00
hopts := sstore . HistoryQueryOpts { MaxItems : maxItems , SessionId : hSessionId , ScreenId : hScreenId }
2023-03-06 20:47:44 +01:00
hresult , err := sstore . GetHistoryItems ( ctx , hopts )
2022-08-28 23:24:05 +02:00
if err != nil {
return nil , err
}
2022-08-31 08:11:06 +02:00
show := ! resolveBool ( pk . Kwargs [ "noshow" ] , false )
2023-02-22 07:41:56 +01:00
if show {
err = sstore . UpdateCurrentActivity ( ctx , sstore . ActivityUpdate { HistoryView : 1 } )
if err != nil {
log . Printf ( "error updating current activity (history): %v\n" , err )
}
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate { }
2022-08-30 01:31:06 +02:00
update . History = & sstore . HistoryInfoType {
2022-08-31 22:28:52 +02:00
HistoryType : htype ,
SessionId : ids . SessionId ,
2023-03-15 00:37:22 +01:00
ScreenId : ids . ScreenId ,
2023-03-06 20:47:44 +01:00
Items : hresult . Items ,
2022-08-31 22:28:52 +02:00
Show : show ,
2022-08-30 01:31:06 +02:00
}
2022-08-28 23:24:05 +02:00
return update , nil
}
2022-08-24 02:26:42 +02:00
func splitLinesForInfo ( str string ) [ ] string {
rtn := strings . Split ( str , "\n" )
if rtn [ len ( rtn ) - 1 ] == "" {
return rtn [ : len ( rtn ) - 1 ]
}
return rtn
}
2022-09-06 05:08:59 +02:00
func resizeRunningCommand ( ctx context . Context , cmd * sstore . CmdType , newCols int ) error {
siPk := packet . MakeSpecialInputPacket ( )
2023-07-31 02:16:43 +02:00
siPk . CK = base . MakeCommandKey ( cmd . ScreenId , cmd . LineId )
2022-09-06 05:08:59 +02:00
siPk . WinSize = & packet . WinSize { Rows : int ( cmd . TermOpts . Rows ) , Cols : newCols }
msh := remote . GetRemoteById ( cmd . Remote . RemoteId )
if msh == nil {
return fmt . Errorf ( "cannot resize, cmd remote not found" )
}
err := msh . SendSpecialInput ( siPk )
if err != nil {
return err
}
newTermOpts := cmd . TermOpts
newTermOpts . Cols = int64 ( newCols )
2023-07-31 02:16:43 +02:00
err = sstore . UpdateCmdTermOpts ( ctx , cmd . ScreenId , cmd . LineId , newTermOpts )
2022-09-06 05:08:59 +02:00
if err != nil {
return err
}
return nil
}
2023-03-13 20:10:23 +01:00
func ScreenResizeCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-09-06 05:08:59 +02:00
if err != nil {
return nil , err
}
colsStr := pk . Kwargs [ "cols" ]
if colsStr == "" {
2023-03-15 00:37:22 +01:00
return nil , fmt . Errorf ( "/screen:resize requires a numeric 'cols' argument" )
2022-09-06 05:08:59 +02:00
}
cols , err := strconv . Atoi ( colsStr )
if err != nil {
2023-03-15 00:37:22 +01:00
return nil , fmt . Errorf ( "/screen:resize requires a numeric 'cols' argument: %v" , err )
2022-09-06 05:08:59 +02:00
}
if cols <= 0 {
2023-03-15 00:37:22 +01:00
return nil , fmt . Errorf ( "/screen:resize invalid zero/negative 'cols' argument" )
2022-09-06 05:08:59 +02:00
}
cols = base . BoundInt ( cols , shexec . MinTermCols , shexec . MaxTermCols )
2023-03-21 03:20:57 +01:00
runningCmds , err := sstore . GetRunningScreenCmds ( ctx , ids . ScreenId )
2022-09-06 05:08:59 +02:00
if err != nil {
2023-03-15 00:37:22 +01:00
return nil , fmt . Errorf ( "/screen:resize cannot get running commands: %v" , err )
2022-09-06 05:08:59 +02:00
}
if len ( runningCmds ) == 0 {
return nil , nil
}
for _ , cmd := range runningCmds {
if int ( cmd . TermOpts . Cols ) != cols {
resizeRunningCommand ( ctx , cmd , cols )
}
}
return nil , nil
}
2022-09-13 21:06:12 +02:00
func LineCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-17 22:47:30 +01:00
return nil , fmt . Errorf ( "/line requires a subcommand: %s" , formatStrs ( [ ] string { "show" , "star" , "hide" , "purge" , "setheight" , "set" } , "or" , false ) )
2023-02-01 07:21:19 +01:00
}
func LineSetHeightCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2023-02-01 07:21:19 +01:00
if err != nil {
return nil , err
}
if len ( pk . Args ) != 2 {
return nil , fmt . Errorf ( "/line:setheight requires 2 arguments (linearg and height)" )
}
lineArg := pk . Args [ 0 ]
2023-03-21 03:20:57 +01:00
lineId , err := sstore . FindLineIdByArg ( ctx , ids . ScreenId , lineArg )
2023-02-01 07:21:19 +01:00
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
heightVal , err := resolveNonNegInt ( pk . Args [ 1 ] , 0 )
2023-02-01 18:45:33 +01:00
if err != nil {
return nil , fmt . Errorf ( "/line:setheight invalid height val: %v" , err )
2023-02-01 07:21:19 +01:00
}
2023-02-06 09:30:23 +01:00
if heightVal > 10000 {
2023-02-01 07:21:19 +01:00
return nil , fmt . Errorf ( "/line:setheight invalid height val (too large): %d" , heightVal )
}
2023-03-31 09:32:38 +02:00
err = sstore . UpdateLineHeight ( ctx , ids . ScreenId , lineId , heightVal )
2023-02-01 07:21:19 +01:00
if err != nil {
return nil , fmt . Errorf ( "/line:setheight error updating height: %v" , err )
}
// we don't need to pass the updated line height (it is "write only")
return nil , nil
2022-09-13 21:06:12 +02:00
}
2023-03-17 22:47:30 +01:00
func LineSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
if err != nil {
return nil , err
}
if len ( pk . Args ) != 1 {
return nil , fmt . Errorf ( "/line:set requires 1 argument (linearg)" )
}
lineArg := pk . Args [ 0 ]
2023-03-21 03:20:57 +01:00
lineId , err := sstore . FindLineIdByArg ( ctx , ids . ScreenId , lineArg )
2023-03-17 22:47:30 +01:00
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
var varsUpdated [ ] string
2023-04-18 00:23:58 +02:00
if renderer , found := pk . Kwargs [ KwArgRenderer ] ; found {
2023-03-17 22:47:30 +01:00
if err = validateRenderer ( renderer ) ; err != nil {
return nil , fmt . Errorf ( "invalid renderer value: %w" , err )
}
2023-03-25 20:54:56 +01:00
err = sstore . UpdateLineRenderer ( ctx , ids . ScreenId , lineId , renderer )
2023-03-17 22:47:30 +01:00
if err != nil {
return nil , fmt . Errorf ( "error changing line renderer: %v" , err )
}
2023-04-18 00:23:58 +02:00
varsUpdated = append ( varsUpdated , KwArgRenderer )
}
if view , found := pk . Kwargs [ KwArgView ] ; found {
if err = validateRenderer ( view ) ; err != nil {
return nil , fmt . Errorf ( "invalid view value: %w" , err )
}
err = sstore . UpdateLineRenderer ( ctx , ids . ScreenId , lineId , view )
if err != nil {
return nil , fmt . Errorf ( "error changing line view: %v" , err )
}
varsUpdated = append ( varsUpdated , KwArgView )
2023-03-17 22:47:30 +01:00
}
2023-09-02 00:21:35 +02:00
if stateJson , found := pk . Kwargs [ KwArgState ] ; found {
if len ( stateJson ) > sstore . MaxLineStateSize {
return nil , fmt . Errorf ( "invalid state value (too large), size[%d], max[%d]" , len ( stateJson ) , sstore . MaxLineStateSize )
}
var stateMap map [ string ] any
err = json . Unmarshal ( [ ] byte ( stateJson ) , & stateMap )
if err != nil {
return nil , fmt . Errorf ( "invalid state value, cannot parse json: %v" , err )
}
err = sstore . UpdateLineState ( ctx , ids . ScreenId , lineId , stateMap )
if err != nil {
return nil , fmt . Errorf ( "cannot update linestate: %v" , err )
}
varsUpdated = append ( varsUpdated , KwArgState )
}
2023-03-17 22:47:30 +01:00
if len ( varsUpdated ) == 0 {
2023-09-02 00:21:35 +02:00
return nil , fmt . Errorf ( "/line:set requires a value to set: %s" , formatStrs ( [ ] string { KwArgView , KwArgState } , "or" , false ) )
2023-03-17 22:47:30 +01:00
}
2023-03-21 03:20:57 +01:00
updatedLine , err := sstore . GetLineById ( ctx , ids . ScreenId , lineId )
2023-03-17 22:47:30 +01:00
if err != nil {
return nil , fmt . Errorf ( "/line:set cannot retrieve updated line: %v" , err )
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-03-17 22:47:30 +01:00
Line : updatedLine ,
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "line updated %s" , formatStrs ( varsUpdated , "and" , false ) ) ,
TimeoutMs : 2000 ,
} ,
}
return update , nil
}
2023-03-03 08:24:01 +01:00
func LineViewCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) != 3 {
return nil , fmt . Errorf ( "usage /line:view [session] [screen] [line]" )
}
sessionArg := pk . Args [ 0 ]
screenArg := pk . Args [ 1 ]
lineArg := pk . Args [ 2 ]
sessionId , err := resolveSessionArg ( sessionArg )
if err != nil {
return nil , fmt . Errorf ( "/line:view invalid session arg: %v" , err )
}
2023-04-05 09:25:22 +02:00
if sessionId == "" {
return nil , fmt . Errorf ( "/line:view no session found" )
}
2023-03-03 08:24:01 +01:00
screenRItem , err := resolveSessionScreen ( ctx , sessionId , screenArg , "" )
if err != nil {
return nil , fmt . Errorf ( "/line:view invalid screen arg: %v" , err )
}
2023-04-05 09:25:22 +02:00
if screenRItem == nil {
return nil , fmt . Errorf ( "/line:view no screen found" )
}
2023-03-13 09:52:30 +01:00
screen , err := sstore . GetScreenById ( ctx , screenRItem . Id )
2023-03-03 08:24:01 +01:00
if err != nil {
return nil , fmt . Errorf ( "/line:view could not get screen: %v" , err )
}
2023-03-15 00:37:22 +01:00
lineRItem , err := resolveLine ( ctx , sessionId , screen . ScreenId , lineArg , "" )
2023-03-03 08:24:01 +01:00
if err != nil {
return nil , fmt . Errorf ( "/line:view invalid line arg: %v" , err )
}
update , err := sstore . SwitchScreenById ( ctx , sessionId , screenRItem . Id )
if err != nil {
return nil , err
}
2023-04-05 09:25:22 +02:00
if lineRItem != nil {
updateMap := make ( map [ string ] interface { } )
updateMap [ sstore . ScreenField_SelectedLine ] = lineRItem . Num
updateMap [ sstore . ScreenField_AnchorLine ] = lineRItem . Num
updateMap [ sstore . ScreenField_AnchorOffset ] = 0
screen , err = sstore . UpdateScreen ( ctx , screenRItem . Id , updateMap )
if err != nil {
return nil , err
}
update . Screens = [ ] * sstore . ScreenType { screen }
2023-03-03 08:24:01 +01:00
}
return update , nil
}
2023-02-21 07:00:07 +01:00
func BookmarksShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
// no resolve ui ids!
var tagName string // defaults to ''
if len ( pk . Args ) > 0 {
tagName = pk . Args [ 0 ]
}
bms , err := sstore . GetBookmarks ( ctx , tagName )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve bookmarks: %v" , err )
}
2023-02-22 07:41:56 +01:00
err = sstore . UpdateCurrentActivity ( ctx , sstore . ActivityUpdate { BookmarksView : 1 } )
if err != nil {
log . Printf ( "error updating current activity (bookmarks): %v\n" , err )
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-03-02 09:31:19 +01:00
MainView : sstore . MainViewBookmarks ,
Bookmarks : bms ,
2023-02-21 07:00:07 +01:00
}
2023-02-21 07:08:23 +01:00
return update , nil
2023-02-21 07:00:07 +01:00
}
2023-02-22 07:11:06 +01:00
func BookmarkSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-02-22 03:03:13 +01:00
if len ( pk . Args ) == 0 {
2023-02-22 07:11:06 +01:00
return nil , fmt . Errorf ( "/bookmark:set requires one argument (bookmark id)" )
2023-02-22 03:03:13 +01:00
}
bookmarkArg := pk . Args [ 0 ]
bookmarkId , err := sstore . GetBookmarkIdByArg ( ctx , bookmarkArg )
if err != nil {
return nil , fmt . Errorf ( "error trying to resolve bookmark: %v" , err )
}
if bookmarkId == "" {
return nil , fmt . Errorf ( "bookmark not found" )
}
editMap := make ( map [ string ] interface { } )
if descStr , found := pk . Kwargs [ "desc" ] ; found {
editMap [ sstore . BookmarkField_Desc ] = descStr
}
if cmdStr , found := pk . Kwargs [ "cmdstr" ] ; found {
editMap [ sstore . BookmarkField_CmdStr ] = cmdStr
}
if len ( editMap ) == 0 {
return nil , fmt . Errorf ( "no fields set, can set %s" , formatStrs ( [ ] string { "desc" , "cmdstr" } , "or" , false ) )
}
err = sstore . EditBookmark ( ctx , bookmarkId , editMap )
if err != nil {
return nil , fmt . Errorf ( "error trying to edit bookmark: %v" , err )
}
bm , err := sstore . GetBookmarkById ( ctx , bookmarkId , "" )
if err != nil {
return nil , fmt . Errorf ( "error retrieving edited bookmark: %v" , err )
}
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2023-02-22 03:03:13 +01:00
Info : & sstore . InfoMsgType {
InfoMsg : "bookmark edited" ,
} ,
Bookmarks : [ ] * sstore . BookmarkType { bm } ,
} , nil
}
func BookmarkDeleteCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/bookmark:delete requires one argument (bookmark id)" )
}
bookmarkArg := pk . Args [ 0 ]
bookmarkId , err := sstore . GetBookmarkIdByArg ( ctx , bookmarkArg )
if err != nil {
return nil , fmt . Errorf ( "error trying to resolve bookmark: %v" , err )
}
if bookmarkId == "" {
return nil , fmt . Errorf ( "bookmark not found" )
}
err = sstore . DeleteBookmark ( ctx , bookmarkId )
if err != nil {
return nil , fmt . Errorf ( "error deleting bookmark: %v" , err )
}
bm := & sstore . BookmarkType { BookmarkId : bookmarkId , Remove : true }
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate {
2023-02-22 03:03:13 +01:00
Info : & sstore . InfoMsgType {
InfoMsg : "bookmark deleted" ,
} ,
Bookmarks : [ ] * sstore . BookmarkType { bm } ,
} , nil
}
2023-02-21 06:39:29 +01:00
func LineBookmarkCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2023-02-21 06:39:29 +01:00
if err != nil {
return nil , err
}
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/line:bookmark requires an argument (line number or id)" )
}
lineArg := pk . Args [ 0 ]
2023-03-21 03:20:57 +01:00
lineId , err := sstore . FindLineIdByArg ( ctx , ids . ScreenId , lineArg )
2023-02-21 06:39:29 +01:00
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
if lineId == "" {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
2023-03-24 18:34:07 +01:00
_ , cmdObj , err := sstore . GetLineCmdByLineId ( ctx , ids . ScreenId , lineId )
2023-02-21 06:39:29 +01:00
if err != nil {
return nil , fmt . Errorf ( "/line:bookmark error getting line: %v" , err )
}
if cmdObj == nil {
return nil , fmt . Errorf ( "cannot bookmark non-cmd line" )
}
2023-03-24 18:34:07 +01:00
existingBmIds , err := sstore . GetBookmarkIdsByCmdStr ( ctx , cmdObj . CmdStr )
2023-02-21 06:39:29 +01:00
if err != nil {
2023-03-24 18:34:07 +01:00
return nil , fmt . Errorf ( "error trying to retrieve current boookmarks: %v" , err )
2023-02-21 06:39:29 +01:00
}
2023-03-24 18:34:07 +01:00
var newBmId string
if len ( existingBmIds ) > 0 {
newBmId = existingBmIds [ 0 ]
} else {
newBm := & sstore . BookmarkType {
BookmarkId : uuid . New ( ) . String ( ) ,
CreatedTs : time . Now ( ) . UnixMilli ( ) ,
CmdStr : cmdObj . CmdStr ,
Alias : "" ,
Tags : nil ,
Description : "" ,
}
err = sstore . InsertBookmark ( ctx , newBm )
if err != nil {
return nil , fmt . Errorf ( "cannot insert bookmark: %v" , err )
}
newBmId = newBm . BookmarkId
2023-02-21 06:39:29 +01:00
}
2023-03-24 18:34:07 +01:00
bms , err := sstore . GetBookmarks ( ctx , "" )
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-03-24 18:34:07 +01:00
MainView : sstore . MainViewBookmarks ,
Bookmarks : bms ,
SelectedBookmark : newBmId ,
2023-02-21 06:39:29 +01:00
}
2023-03-24 18:34:07 +01:00
return update , nil
2023-02-21 06:39:29 +01:00
}
func LinePinCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
return nil , nil
}
2022-12-06 07:59:00 +01:00
func LineStarCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-12-06 07:59:00 +01:00
if err != nil {
return nil , err
}
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/line:star requires an argument (line number or id)" )
}
if len ( pk . Args ) > 2 {
return nil , fmt . Errorf ( "/line:star only takes up to 2 arguments (line-number and star-value)" )
}
lineArg := pk . Args [ 0 ]
2023-03-21 03:20:57 +01:00
lineId , err := sstore . FindLineIdByArg ( ctx , ids . ScreenId , lineArg )
2022-12-06 07:59:00 +01:00
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
if lineId == "" {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
starVal , err := resolveNonNegInt ( pk . Args [ 1 ] , 1 )
if err != nil {
return nil , fmt . Errorf ( "/line:star invalid star-value (not integer): %v" , err )
}
if starVal > 5 {
return nil , fmt . Errorf ( "/line:star invalid star-value must be in the range of 0-5" )
}
2023-09-02 00:21:35 +02:00
err = sstore . UpdateLineStar ( ctx , ids . ScreenId , lineId , starVal )
2022-12-06 07:59:00 +01:00
if err != nil {
return nil , fmt . Errorf ( "/line:star error updating star value: %v" , err )
}
2023-03-21 03:20:57 +01:00
lineObj , err := sstore . GetLineById ( ctx , ids . ScreenId , lineId )
2022-12-06 07:59:00 +01:00
if err != nil {
return nil , fmt . Errorf ( "/line:star error getting line: %v" , err )
}
if lineObj == nil {
// no line (which is strange given we checked for it above). just return a nop.
return nil , nil
}
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate { Line : lineObj } , nil
2022-12-06 07:59:00 +01:00
}
2022-12-28 08:12:27 +01:00
func LineArchiveCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-12-22 02:45:40 +01:00
if err != nil {
return nil , err
}
if len ( pk . Args ) == 0 {
2022-12-28 08:12:27 +01:00
return nil , fmt . Errorf ( "/line:archive requires an argument (line number or id)" )
2022-12-22 02:45:40 +01:00
}
lineArg := pk . Args [ 0 ]
2023-03-21 03:20:57 +01:00
lineId , err := sstore . FindLineIdByArg ( ctx , ids . ScreenId , lineArg )
2022-12-22 02:45:40 +01:00
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
if lineId == "" {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
2022-12-28 08:12:27 +01:00
shouldArchive := true
2022-12-22 02:45:40 +01:00
if len ( pk . Args ) >= 2 {
2022-12-28 08:12:27 +01:00
shouldArchive = resolveBool ( pk . Args [ 1 ] , true )
2022-12-22 02:45:40 +01:00
}
2023-03-25 20:54:56 +01:00
err = sstore . SetLineArchivedById ( ctx , ids . ScreenId , lineId , shouldArchive )
2022-12-22 02:45:40 +01:00
if err != nil {
2022-12-28 08:12:27 +01:00
return nil , fmt . Errorf ( "/line:archive error updating hidden status: %v" , err )
2022-12-22 02:45:40 +01:00
}
2023-03-21 03:20:57 +01:00
lineObj , err := sstore . GetLineById ( ctx , ids . ScreenId , lineId )
2022-12-22 02:45:40 +01:00
if err != nil {
2022-12-28 08:12:27 +01:00
return nil , fmt . Errorf ( "/line:archive error getting line: %v" , err )
2022-12-22 02:45:40 +01:00
}
if lineObj == nil {
// no line (which is strange given we checked for it above). just return a nop.
return nil , nil
}
2023-05-09 01:06:51 +02:00
return & sstore . ModelUpdate { Line : lineObj } , nil
2022-12-22 02:45:40 +01:00
}
func LinePurgeCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-12-22 02:45:40 +01:00
if err != nil {
return nil , err
}
if len ( pk . Args ) == 0 {
2023-03-03 22:31:16 +01:00
return nil , fmt . Errorf ( "/line:purge requires at least one argument (line number or id)" )
2022-12-22 02:45:40 +01:00
}
2023-03-03 22:31:16 +01:00
var lineIds [ ] string
for _ , lineArg := range pk . Args {
2023-03-21 03:20:57 +01:00
lineId , err := sstore . FindLineIdByArg ( ctx , ids . ScreenId , lineArg )
2023-03-03 22:31:16 +01:00
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
if lineId == "" {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
lineIds = append ( lineIds , lineId )
2022-12-22 02:45:40 +01:00
}
2023-03-21 03:20:57 +01:00
err = sstore . PurgeLinesByIds ( ctx , ids . ScreenId , lineIds )
2022-12-22 02:45:40 +01:00
if err != nil {
2023-03-03 22:31:16 +01:00
return nil , fmt . Errorf ( "/line:purge error purging lines: %v" , err )
2022-12-22 02:45:40 +01:00
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate { }
2023-03-03 22:31:16 +01:00
for _ , lineId := range lineIds {
lineObj := & sstore . LineType {
2023-03-21 03:20:57 +01:00
ScreenId : ids . ScreenId ,
LineId : lineId ,
Remove : true ,
2023-03-03 22:31:16 +01:00
}
update . Lines = append ( update . Lines , lineObj )
2022-12-22 02:45:40 +01:00
}
2023-03-03 22:31:16 +01:00
return update , nil
2022-12-22 02:45:40 +01:00
}
2022-09-13 21:06:12 +02:00
func LineShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-09-13 21:06:12 +02:00
if err != nil {
return nil , err
}
2022-09-21 06:50:36 +02:00
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/line:show requires an argument (line number or id)" )
}
lineArg := pk . Args [ 0 ]
2023-03-21 03:20:57 +01:00
lineId , err := sstore . FindLineIdByArg ( ctx , ids . ScreenId , lineArg )
2022-09-21 06:50:36 +02:00
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
if lineId == "" {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
2023-03-21 03:20:57 +01:00
line , cmd , err := sstore . GetLineCmdByLineId ( ctx , ids . ScreenId , lineId )
2022-09-21 06:50:36 +02:00
if err != nil {
return nil , fmt . Errorf ( "error getting line: %v" , err )
}
if line == nil {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
var buf bytes . Buffer
2023-09-01 07:04:31 +02:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "screenid" , line . ScreenId ) )
2022-09-21 06:50:36 +02:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "lineid" , line . LineId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "type" , line . LineType ) )
lineNumStr := strconv . FormatInt ( line . LineNum , 10 )
if line . LineNumTemp {
lineNumStr = "~" + lineNumStr
}
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "linenum" , lineNumStr ) )
ts := time . UnixMilli ( line . Ts )
2023-09-01 07:04:31 +02:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "ts" , ts . Format ( TsFormatStr ) ) )
2022-09-21 06:50:36 +02:00
if line . Ephemeral {
2022-10-21 01:14:14 +02:00
buf . WriteString ( fmt . Sprintf ( " %-15s %v\n" , "ephemeral" , true ) )
2022-09-21 06:50:36 +02:00
}
2023-03-17 05:46:10 +01:00
if line . Renderer != "" {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "renderer" , line . Renderer ) )
} else {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "renderer" , "terminal" ) )
}
2022-09-21 06:50:36 +02:00
if cmd != nil {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "remote" , cmd . Remote . MakeFullRemoteRef ( ) ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "status" , cmd . Status ) )
2023-04-12 08:54:18 +02:00
if cmd . FeState [ "cwd" ] != "" {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "cwd" , cmd . FeState [ "cwd" ] ) )
2022-09-21 06:50:36 +02:00
}
2022-09-21 21:39:55 +02:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "termopts" , formatTermOpts ( cmd . TermOpts ) ) )
2022-09-22 07:02:38 +02:00
if cmd . TermOpts != cmd . OrigTermOpts {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "orig-termopts" , formatTermOpts ( cmd . OrigTermOpts ) ) )
}
2022-10-28 02:10:36 +02:00
if cmd . RtnState {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "rtnstate" , "true" ) )
}
2023-07-31 02:16:43 +02:00
stat , _ := sstore . StatCmdPtyFile ( ctx , cmd . ScreenId , cmd . LineId )
2023-02-06 09:30:23 +01:00
if stat == nil {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "file" , "-" ) )
} else {
fileDataStr := fmt . Sprintf ( "v%d data=%d offset=%d max=%s" , stat . Version , stat . DataSize , stat . FileOffset , scbase . NumFormatB2 ( stat . MaxSize ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "file" , stat . Location ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "file-data" , fileDataStr ) )
}
2023-09-01 07:04:31 +02:00
if cmd . DoneTs != 0 {
doneTs := time . UnixMilli ( cmd . DoneTs )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "donets" , doneTs . Format ( TsFormatStr ) ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %d\n" , "exitcode" , cmd . ExitCode ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %dms\n" , "duration" , cmd . DurationMs ) )
}
2022-09-21 06:50:36 +02:00
}
2023-09-02 00:21:35 +02:00
stateStr := dbutil . QuickJson ( line . LineState )
if len ( stateStr ) > 80 {
stateStr = stateStr [ 0 : 77 ] + "..."
}
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "state" , stateStr ) )
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2022-09-21 06:50:36 +02:00
Info : & sstore . InfoMsgType {
InfoTitle : fmt . Sprintf ( "line %d info" , line . LineNum ) ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
}
return update , nil
2022-09-13 21:06:12 +02:00
}
2022-09-21 21:39:55 +02:00
2022-11-28 09:13:00 +01:00
func SetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
var setMap map [ string ] map [ string ] string
setMap = make ( map [ string ] map [ string ] string )
_ , err := resolveUiIds ( ctx , pk , 0 ) // best effort
if err != nil {
return nil , err
}
for argIdx , rawArgVal := range pk . Args {
eqIdx := strings . Index ( rawArgVal , "=" )
if eqIdx == - 1 {
return nil , fmt . Errorf ( "/set invalid argument %d, does not contain an '='" , argIdx )
}
argName := rawArgVal [ : eqIdx ]
argVal := rawArgVal [ eqIdx + 1 : ]
ok , scopeName , varName := resolveSetArg ( argName )
if ! ok {
return nil , fmt . Errorf ( "/set invalid setvar %q" , argName )
}
if _ , ok := setMap [ scopeName ] ; ! ok {
setMap [ scopeName ] = make ( map [ string ] string )
}
setMap [ scopeName ] [ varName ] = argVal
}
return nil , nil
}
2023-09-01 07:04:31 +02:00
func makeStreamFilePk ( ids resolvedIds , pk * scpacket . FeCommandPacketType ) ( * packet . StreamFilePacketType , error ) {
cwd := ids . Remote . FeState [ "cwd" ]
fileArg := pk . Args [ 0 ]
if fileArg == "" {
return nil , fmt . Errorf ( "/view:stat file argument must be set (cannot be empty)" )
}
streamPk := packet . MakeStreamFilePacket ( )
streamPk . ReqId = uuid . New ( ) . String ( )
if filepath . IsAbs ( fileArg ) {
streamPk . Path = fileArg
} else {
streamPk . Path = filepath . Join ( cwd , fileArg )
}
return streamPk , nil
}
func ViewStatCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/view:stat requires an argument (file name)" )
}
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_RemoteConnected )
if err != nil {
return nil , err
}
streamPk , err := makeStreamFilePk ( ids , pk )
if err != nil {
return nil , err
}
streamPk . StatOnly = true
msh := ids . Remote . MShell
iter , err := msh . StreamFile ( ctx , streamPk )
if err != nil {
return nil , fmt . Errorf ( "/view:stat error: %v" , err )
}
defer iter . Close ( )
respIf , err := iter . Next ( ctx )
if err != nil {
return nil , fmt . Errorf ( "/view:stat error getting response: %v" , err )
}
resp , ok := respIf . ( * packet . StreamFileResponseType )
if ! ok {
return nil , fmt . Errorf ( "/view:stat error, bad response packet type: %T" , respIf )
}
if resp . Error != "" {
return nil , fmt . Errorf ( "/view:stat error: %s" , resp . Error )
}
if resp . Info == nil {
return nil , fmt . Errorf ( "/view:stat error, no file info" )
}
var buf bytes . Buffer
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "path" , resp . Info . Name ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %d\n" , "size" , resp . Info . Size ) )
modTs := time . UnixMilli ( resp . Info . ModTs )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "modts" , modTs . Format ( TsFormatStr ) ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %v\n" , "isdir" , resp . Info . IsDir ) )
modeStr := fs . FileMode ( resp . Info . Perm ) . String ( )
if len ( modeStr ) > 9 {
modeStr = modeStr [ len ( modeStr ) - 9 : ]
}
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "perms" , modeStr ) )
update := & sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoTitle : fmt . Sprintf ( "view stat %q" , streamPk . Path ) ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
}
return update , nil
}
func ViewTestCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/view:test requires an argument (file name)" )
}
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_RemoteConnected )
if err != nil {
return nil , err
}
streamPk , err := makeStreamFilePk ( ids , pk )
if err != nil {
return nil , err
}
msh := ids . Remote . MShell
iter , err := msh . StreamFile ( ctx , streamPk )
if err != nil {
return nil , fmt . Errorf ( "/view:test error: %v" , err )
}
defer iter . Close ( )
respIf , err := iter . Next ( ctx )
if err != nil {
return nil , fmt . Errorf ( "/view:test error getting response: %v" , err )
}
resp , ok := respIf . ( * packet . StreamFileResponseType )
if ! ok {
return nil , fmt . Errorf ( "/view:test error, bad response packet type: %T" , respIf )
}
if resp . Error != "" {
return nil , fmt . Errorf ( "/view:test error: %s" , resp . Error )
}
if resp . Info == nil {
return nil , fmt . Errorf ( "/view:test error, no file info" )
}
var buf bytes . Buffer
var numPackets int
for {
dataPkIf , err := iter . Next ( ctx )
if err != nil {
return nil , fmt . Errorf ( "/view:test error while getting data: %w" , err )
}
if dataPkIf == nil {
break
}
dataPk , ok := dataPkIf . ( * packet . FileDataPacketType )
if ! ok {
return nil , fmt . Errorf ( "/view:test invalid data packet type: %T" , dataPkIf )
}
if dataPk . Error != "" {
return nil , fmt . Errorf ( "/view:test error returned while getting data: %s" , dataPk . Error )
}
numPackets ++
buf . Write ( dataPk . Data )
}
buf . WriteString ( fmt . Sprintf ( "\n\ntotal packets: %d\n" , numPackets ) )
update := & sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoTitle : fmt . Sprintf ( "view file %q" , streamPk . Path ) ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
}
return update , nil
}
2023-09-02 01:38:54 +02:00
func CodeEditCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
2023-09-06 07:09:24 +02:00
return nil , fmt . Errorf ( "%s requires an argument (file name)" , GetCmdStr ( pk ) )
2023-09-02 01:38:54 +02:00
}
// TODO more error checking on filename format?
if pk . Args [ 0 ] == "" {
2023-09-06 07:09:24 +02:00
return nil , fmt . Errorf ( "%s argument cannot be empty" , GetCmdStr ( pk ) )
2023-09-02 01:38:54 +02:00
}
2023-09-17 23:10:35 +02:00
langArg , err := getLangArg ( pk )
if err != nil {
return nil , fmt . Errorf ( "%s invalid 'lang': %v" , GetCmdStr ( pk ) , err )
}
2023-09-02 01:38:54 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_RemoteConnected )
if err != nil {
return nil , err
}
2023-09-06 07:09:24 +02:00
outputStr := fmt . Sprintf ( "%s %q" , GetCmdStr ( pk ) , pk . Args [ 0 ] )
2023-09-02 01:38:54 +02:00
cmd , err := makeStaticCmd ( ctx , GetCmdStr ( pk ) , ids , pk . GetRawStr ( ) , [ ] byte ( outputStr ) )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
// set the line state
lineState := make ( map [ string ] any )
2023-09-17 23:10:35 +02:00
lineState [ sstore . LineState_Source ] = "file"
lineState [ sstore . LineState_File ] = pk . Args [ 0 ]
2023-09-06 07:09:24 +02:00
if GetCmdStr ( pk ) == "codeview" {
2023-09-17 23:10:35 +02:00
lineState [ sstore . LineState_Mode ] = "view"
2023-09-06 07:09:24 +02:00
} else {
2023-09-17 23:10:35 +02:00
lineState [ sstore . LineState_Mode ] = "edit"
2023-09-06 07:09:24 +02:00
}
2023-09-17 23:10:35 +02:00
if langArg != "" {
lineState [ sstore . LineState_Lang ] = langArg
2023-09-06 07:09:24 +02:00
}
2023-09-17 23:10:35 +02:00
update , err := addLineForCmd ( ctx , "/" + GetCmdStr ( pk ) , true , ids , cmd , "code" , lineState )
2023-09-02 01:38:54 +02:00
if err != nil {
2023-09-17 23:10:35 +02:00
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
2023-09-02 01:38:54 +02:00
}
update . Interactive = pk . Interactive
return update , nil
}
2023-10-12 02:59:22 +02:00
func CSVViewCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "%s requires an argument (file name)" , GetCmdStr ( pk ) )
}
// TODO more error checking on filename format?
if pk . Args [ 0 ] == "" {
return nil , fmt . Errorf ( "%s argument cannot be empty" , GetCmdStr ( pk ) )
}
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_RemoteConnected )
if err != nil {
return nil , err
}
outputStr := fmt . Sprintf ( "%s %q" , GetCmdStr ( pk ) , pk . Args [ 0 ] )
cmd , err := makeStaticCmd ( ctx , GetCmdStr ( pk ) , ids , pk . GetRawStr ( ) , [ ] byte ( outputStr ) )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
// set the line state
lineState := make ( map [ string ] any )
lineState [ sstore . LineState_Source ] = "file"
lineState [ sstore . LineState_File ] = pk . Args [ 0 ]
update , err := addLineForCmd ( ctx , "/" + GetCmdStr ( pk ) , true , ids , cmd , "csv" , lineState )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
update . Interactive = pk . Interactive
return update , nil
}
2023-09-16 20:15:09 +02:00
func ImageViewCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "%s requires an argument (file name)" , GetCmdStr ( pk ) )
}
// TODO more error checking on filename format?
if pk . Args [ 0 ] == "" {
return nil , fmt . Errorf ( "%s argument cannot be empty" , GetCmdStr ( pk ) )
}
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_RemoteConnected )
if err != nil {
return nil , err
}
outputStr := fmt . Sprintf ( "%s %q" , GetCmdStr ( pk ) , pk . Args [ 0 ] )
cmd , err := makeStaticCmd ( ctx , GetCmdStr ( pk ) , ids , pk . GetRawStr ( ) , [ ] byte ( outputStr ) )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
// set the line state
lineState := make ( map [ string ] any )
2023-09-17 23:10:35 +02:00
lineState [ sstore . LineState_Source ] = "file"
lineState [ sstore . LineState_File ] = pk . Args [ 0 ]
update , err := addLineForCmd ( ctx , "/" + GetCmdStr ( pk ) , false , ids , cmd , "image" , lineState )
2023-09-16 20:15:09 +02:00
if err != nil {
2023-09-17 23:10:35 +02:00
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
2023-09-16 20:15:09 +02:00
}
update . Interactive = pk . Interactive
return update , nil
}
func MarkdownViewCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "%s requires an argument (file name)" , GetCmdStr ( pk ) )
}
// TODO more error checking on filename format?
if pk . Args [ 0 ] == "" {
return nil , fmt . Errorf ( "%s argument cannot be empty" , GetCmdStr ( pk ) )
}
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_RemoteConnected )
if err != nil {
return nil , err
}
outputStr := fmt . Sprintf ( "%s %q" , GetCmdStr ( pk ) , pk . Args [ 0 ] )
cmd , err := makeStaticCmd ( ctx , GetCmdStr ( pk ) , ids , pk . GetRawStr ( ) , [ ] byte ( outputStr ) )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
// set the line state
lineState := make ( map [ string ] any )
2023-09-17 23:10:35 +02:00
lineState [ sstore . LineState_Source ] = "file"
lineState [ sstore . LineState_File ] = pk . Args [ 0 ]
update , err := addLineForCmd ( ctx , "/" + GetCmdStr ( pk ) , false , ids , cmd , "markdown" , lineState )
2023-09-16 20:15:09 +02:00
if err != nil {
2023-09-17 23:10:35 +02:00
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
2023-09-16 20:15:09 +02:00
}
update . Interactive = pk . Interactive
return update , nil
}
2023-09-01 07:04:31 +02:00
func EditTestCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/edit:test requires an argument (file name)" )
}
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_RemoteConnected )
if err != nil {
return nil , err
}
content , ok := pk . Kwargs [ "content" ]
if ! ok {
return nil , fmt . Errorf ( "/edit:test no content for file specified" )
}
fileArg := pk . Args [ 0 ]
if fileArg == "" {
return nil , fmt . Errorf ( "/view:stat file argument must be set (cannot be empty)" )
}
writePk := packet . MakeWriteFilePacket ( )
writePk . ReqId = uuid . New ( ) . String ( )
writePk . UseTemp = true
cwd := ids . Remote . FeState [ "cwd" ]
if filepath . IsAbs ( fileArg ) {
writePk . Path = fileArg
} else {
writePk . Path = filepath . Join ( cwd , fileArg )
}
msh := ids . Remote . MShell
iter , err := msh . PacketRpcIter ( ctx , writePk )
if err != nil {
return nil , fmt . Errorf ( "/edit:test error: %v" , err )
}
// first packet should be WriteFileReady
readyIf , err := iter . Next ( ctx )
if err != nil {
return nil , fmt . Errorf ( "/edit:test error while getting ready response: %w" , err )
}
readyPk , ok := readyIf . ( * packet . WriteFileReadyPacketType )
if ! ok {
return nil , fmt . Errorf ( "/edit:test bad ready packet received: %T" , readyIf )
}
if readyPk . Error != "" {
return nil , fmt . Errorf ( "/edit:test %s" , readyPk . Error )
}
dataPk := packet . MakeFileDataPacket ( writePk . ReqId )
dataPk . Data = [ ] byte ( content )
dataPk . Eof = true
err = msh . SendFileData ( dataPk )
if err != nil {
return nil , fmt . Errorf ( "/edit:test error sending data packet: %v" , err )
}
doneIf , err := iter . Next ( ctx )
if err != nil {
return nil , fmt . Errorf ( "/edit:test error while getting done response: %w" , err )
}
donePk , ok := doneIf . ( * packet . WriteFileDonePacketType )
if ! ok {
return nil , fmt . Errorf ( "/edit:test bad done packet received: %T" , doneIf )
}
if donePk . Error != "" {
return nil , fmt . Errorf ( "/edit:test %s" , donePk . Error )
}
update := & sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoTitle : fmt . Sprintf ( "edit test, wrote %q" , writePk . Path ) ,
} ,
}
return update , nil
}
2022-12-21 06:58:58 +01:00
func SignalCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-03-15 00:37:22 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-12-21 06:58:58 +01:00
if err != nil {
return nil , err
}
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/signal requires a first argument (line number or id)" )
}
if len ( pk . Args ) == 1 {
return nil , fmt . Errorf ( "/signal requires a second argument (signal name)" )
}
lineArg := pk . Args [ 0 ]
2023-03-21 03:20:57 +01:00
lineId , err := sstore . FindLineIdByArg ( ctx , ids . ScreenId , lineArg )
2022-12-21 06:58:58 +01:00
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
2023-03-21 03:20:57 +01:00
line , cmd , err := sstore . GetLineCmdByLineId ( ctx , ids . ScreenId , lineId )
2022-12-21 06:58:58 +01:00
if err != nil {
return nil , fmt . Errorf ( "error getting line: %v" , err )
}
if line == nil {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
if cmd == nil {
return nil , fmt . Errorf ( "line %q does not have a command" , lineArg )
}
if cmd . Status != sstore . CmdStatusRunning {
return nil , fmt . Errorf ( "line %q command is not running, cannot send signal" , lineArg )
}
sigArg := pk . Args [ 1 ]
if isAllDigits ( sigArg ) {
val , _ := strconv . Atoi ( sigArg )
if val <= 0 || val > MaxSignalNum {
return nil , fmt . Errorf ( "signal number is out of bounds: %q" , sigArg )
}
} else if ! strings . HasPrefix ( sigArg , "SIG" ) {
sigArg = "SIG" + sigArg
}
sigArg = strings . ToUpper ( sigArg )
if len ( sigArg ) > 12 {
return nil , fmt . Errorf ( "invalid signal (too long): %q" , sigArg )
}
if ! sigNameRe . MatchString ( sigArg ) {
return nil , fmt . Errorf ( "invalid signal name/number: %q" , sigArg )
}
msh := remote . GetRemoteById ( cmd . Remote . RemoteId )
if msh == nil {
return nil , fmt . Errorf ( "cannot send signal, no remote found for command" )
}
if ! msh . IsConnected ( ) {
return nil , fmt . Errorf ( "cannot send signal, remote is not connected" )
}
siPk := packet . MakeSpecialInputPacket ( )
2023-07-31 02:16:43 +02:00
siPk . CK = base . MakeCommandKey ( cmd . ScreenId , cmd . LineId )
2022-12-21 06:58:58 +01:00
siPk . SigName = sigArg
err = msh . SendSpecialInput ( siPk )
if err != nil {
return nil , fmt . Errorf ( "cannot send signal: %v" , err )
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2022-12-21 06:58:58 +01:00
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "sent line %s signal %s" , lineArg , sigArg ) ,
} ,
}
return update , nil
}
2022-10-30 20:52:40 +01:00
func KillServerCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
go func ( ) {
2022-10-31 20:40:45 +01:00
log . Printf ( "received /killserver, shutting down\n" )
2022-10-30 20:52:40 +01:00
time . Sleep ( 1 * time . Second )
syscall . Kill ( syscall . Getpid ( ) , syscall . SIGINT )
} ( )
return nil , nil
}
2023-01-19 20:10:12 +01:00
func ClientCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-02-26 23:33:01 +01:00
return nil , fmt . Errorf ( "/client requires a subcommand: %s" , formatStrs ( [ ] string { "show" , "set" } , "or" , false ) )
2023-01-23 08:10:18 +01:00
}
2023-03-31 22:25:57 +02:00
func ClientNotifyUpdateWriterCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-04-06 00:09:51 +02:00
pcloud . ResetUpdateWriterNumFailures ( )
2023-03-31 22:25:57 +02:00
sstore . NotifyUpdateWriter ( )
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-03-31 22:25:57 +02:00
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "notified update writer" ) ,
} ,
}
return update , nil
}
2023-01-23 08:10:18 +01:00
func boolToStr ( v bool , trueStr string , falseStr string ) string {
if v {
return trueStr
}
return falseStr
}
2023-04-05 06:52:20 +02:00
func ClientAcceptTosCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
clientData , err := sstore . EnsureClientData ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve client data: %v" , err )
}
clientOpts := clientData . ClientOpts
clientOpts . AcceptedTos = time . Now ( ) . UnixMilli ( )
err = sstore . SetClientOpts ( ctx , clientOpts )
if err != nil {
return nil , fmt . Errorf ( "error updating client data: %v" , err )
}
clientData , err = sstore . EnsureClientData ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve updated client data: %v" , err )
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-04-05 06:52:20 +02:00
ClientData : clientData ,
}
return update , nil
}
2023-05-09 01:06:51 +02:00
func validateOpenAIAPIToken ( key string ) error {
if len ( key ) == 0 {
return fmt . Errorf ( "invalid openai token, zero length" )
}
if len ( key ) > MaxOpenAIAPITokenLen {
return fmt . Errorf ( "invalid openai token, too long" )
}
for idx , ch := range key {
if ! unicode . IsPrint ( ch ) {
return fmt . Errorf ( "invalid openai token, char at idx:%d is invalid %q" , idx , string ( ch ) )
}
}
return nil
}
func validateOpenAIModel ( model string ) error {
if len ( model ) == 0 {
return nil
}
if len ( model ) > MaxOpenAIModelLen {
return fmt . Errorf ( "invalid openai model, too long" )
}
for idx , ch := range model {
if ! unicode . IsPrint ( ch ) {
return fmt . Errorf ( "invalid openai model, char at idx:%d is invalid %q" , idx , string ( ch ) )
}
}
return nil
}
2023-02-26 23:33:01 +01:00
func ClientSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
clientData , err := sstore . EnsureClientData ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve client data: %v" , err )
}
var varsUpdated [ ] string
if fontSizeStr , found := pk . Kwargs [ "termfontsize" ] ; found {
newFontSize , err := resolveNonNegInt ( fontSizeStr , 0 )
if err != nil {
return nil , fmt . Errorf ( "invalid termfontsize, must be a number between 8-15: %v" , err )
}
if newFontSize < 8 || newFontSize > 15 {
2023-03-08 18:49:13 +01:00
return nil , fmt . Errorf ( "invalid termfontsize, must be a number between 8-15" )
2023-02-26 23:33:01 +01:00
}
feOpts := clientData . FeOpts
feOpts . TermFontSize = newFontSize
err = sstore . UpdateClientFeOpts ( ctx , feOpts )
if err != nil {
return nil , fmt . Errorf ( "error updating client feopts: %v" , err )
}
varsUpdated = append ( varsUpdated , "termfontsize" )
}
2023-05-09 01:06:51 +02:00
if apiToken , found := pk . Kwargs [ "openaiapitoken" ] ; found {
err = validateOpenAIAPIToken ( apiToken )
if err != nil {
return nil , err
}
varsUpdated = append ( varsUpdated , "openaiapitoken" )
aiOpts := clientData . OpenAIOpts
if aiOpts == nil {
aiOpts = & sstore . OpenAIOptsType { }
clientData . OpenAIOpts = aiOpts
}
aiOpts . APIToken = apiToken
err = sstore . UpdateClientOpenAIOpts ( ctx , * aiOpts )
if err != nil {
return nil , fmt . Errorf ( "error updating client openai api token: %v" , err )
}
}
if aiModel , found := pk . Kwargs [ "openaimodel" ] ; found {
err = validateOpenAIModel ( aiModel )
if err != nil {
return nil , err
}
varsUpdated = append ( varsUpdated , "openaimodel" )
aiOpts := clientData . OpenAIOpts
if aiOpts == nil {
aiOpts = & sstore . OpenAIOptsType { }
clientData . OpenAIOpts = aiOpts
}
aiOpts . Model = aiModel
err = sstore . UpdateClientOpenAIOpts ( ctx , * aiOpts )
if err != nil {
return nil , fmt . Errorf ( "error updating client openai model: %v" , err )
}
}
if maxTokensStr , found := pk . Kwargs [ "openaimaxtokens" ] ; found {
maxTokens , err := strconv . Atoi ( maxTokensStr )
if err != nil {
return nil , fmt . Errorf ( "error updating client openai maxtokens, invalid number: %v" , err )
}
if maxTokens < 0 || maxTokens > 1000000 {
return nil , fmt . Errorf ( "error updating client openai maxtokens, out of range: %d" , maxTokens )
}
varsUpdated = append ( varsUpdated , "openaimaxtokens" )
aiOpts := clientData . OpenAIOpts
if aiOpts == nil {
aiOpts = & sstore . OpenAIOptsType { }
clientData . OpenAIOpts = aiOpts
}
aiOpts . MaxTokens = maxTokens
err = sstore . UpdateClientOpenAIOpts ( ctx , * aiOpts )
if err != nil {
return nil , fmt . Errorf ( "error updating client openai maxtokens: %v" , err )
}
}
if maxChoicesStr , found := pk . Kwargs [ "openaimaxchoices" ] ; found {
maxChoices , err := strconv . Atoi ( maxChoicesStr )
if err != nil {
return nil , fmt . Errorf ( "error updating client openai maxchoices, invalid number: %v" , err )
}
if maxChoices < 0 || maxChoices > 10 {
return nil , fmt . Errorf ( "error updating client openai maxchoices, out of range: %d" , maxChoices )
}
varsUpdated = append ( varsUpdated , "openaimaxchoices" )
aiOpts := clientData . OpenAIOpts
if aiOpts == nil {
aiOpts = & sstore . OpenAIOptsType { }
clientData . OpenAIOpts = aiOpts
}
aiOpts . MaxChoices = maxChoices
err = sstore . UpdateClientOpenAIOpts ( ctx , * aiOpts )
if err != nil {
return nil , fmt . Errorf ( "error updating client openai maxchoices: %v" , err )
}
}
2023-02-26 23:33:01 +01:00
if len ( varsUpdated ) == 0 {
2023-05-09 01:06:51 +02:00
return nil , fmt . Errorf ( "/client:set requires a value to set: %s" , formatStrs ( [ ] string { "termfontsize" , "openaiapitoken" , "openaimodel" , "openaimaxtokens" , "openaimaxchoices" } , "or" , false ) )
2023-02-26 23:33:01 +01:00
}
clientData , err = sstore . EnsureClientData ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve updated client data: %v" , err )
}
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-02-26 23:33:01 +01:00
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "client updated %s" , formatStrs ( varsUpdated , "and" , false ) ) ,
TimeoutMs : 2000 ,
} ,
ClientData : clientData ,
}
return update , nil
}
2023-01-23 08:10:18 +01:00
func ClientShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
clientData , err := sstore . EnsureClientData ( ctx )
if err != nil {
2023-02-26 23:33:01 +01:00
return nil , fmt . Errorf ( "cannot retrieve client data: %v" , err )
2023-01-23 08:10:18 +01:00
}
2023-02-21 00:41:39 +01:00
dbVersion , err := sstore . GetDBVersion ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve db version: %v\n" , err )
}
2023-02-24 00:17:47 +01:00
clientVersion := "-"
if pk . UIContext != nil && pk . UIContext . Build != "" {
clientVersion = pk . UIContext . Build
}
2023-01-23 08:10:18 +01:00
var buf bytes . Buffer
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "userid" , clientData . UserId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "clientid" , clientData . ClientId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "telemetry" , boolToStr ( clientData . ClientOpts . NoTelemetry , "off" , "on" ) ) )
2023-02-21 00:41:39 +01:00
buf . WriteString ( fmt . Sprintf ( " %-15s %d\n" , "db-version" , dbVersion ) )
2023-02-24 00:17:47 +01:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "client-version" , clientVersion ) )
2023-11-01 09:26:19 +01:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s %s\n" , "server-version" , scbase . WaveVersion , scbase . BuildTime ) )
2023-02-24 00:17:47 +01:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s (%s)\n" , "arch" , scbase . ClientArch ( ) , scbase . MacOSRelease ( ) ) )
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-01-23 08:10:18 +01:00
Info : & sstore . InfoMsgType {
InfoTitle : fmt . Sprintf ( "client info" ) ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
}
return update , nil
2023-01-19 20:10:12 +01:00
}
2023-01-23 21:54:32 +01:00
func TelemetryCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
return nil , fmt . Errorf ( "/telemetry requires a subcommand: %s" , formatStrs ( [ ] string { "show" , "on" , "off" , "send" } , "or" , false ) )
}
func setNoTelemetry ( ctx context . Context , clientData * sstore . ClientData , noTelemetryVal bool ) error {
clientOpts := clientData . ClientOpts
clientOpts . NoTelemetry = noTelemetryVal
err := sstore . SetClientOpts ( ctx , clientOpts )
if err != nil {
return fmt . Errorf ( "error trying to update client telemetry: %v" , err )
}
log . Printf ( "client no-telemetry setting updated to %v\n" , noTelemetryVal )
2023-10-31 06:39:51 +01:00
go func ( ) {
err := pcloud . SendNoTelemetryUpdate ( ctx , clientOpts . NoTelemetry )
if err != nil {
log . Printf ( "[error] sending no-telemetry update: %v\n" , err )
log . Printf ( "note that telemetry update has still taken effect locally, and will be respected by the client\n" )
}
} ( )
2023-01-23 21:54:32 +01:00
return nil
}
func TelemetryOnCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2023-01-23 08:10:18 +01:00
clientData , err := sstore . EnsureClientData ( ctx )
if err != nil {
2023-02-26 23:33:01 +01:00
return nil , fmt . Errorf ( "cannot retrieve client data: %v" , err )
2023-01-23 08:10:18 +01:00
}
2023-01-23 21:54:32 +01:00
if ! clientData . ClientOpts . NoTelemetry {
return sstore . InfoMsgUpdate ( "telemetry is already on" ) , nil
2023-01-23 08:10:18 +01:00
}
2023-01-23 21:54:32 +01:00
err = setNoTelemetry ( ctx , clientData , false )
if err != nil {
return nil , err
}
2023-10-31 06:39:51 +01:00
go func ( ) {
err := pcloud . SendTelemetry ( ctx , false )
if err != nil {
// ignore error, but log
log . Printf ( "[error] sending telemetry update (in /telemetry:on): %v\n" , err )
}
} ( )
2023-03-23 20:08:03 +01:00
clientData , err = sstore . EnsureClientData ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve updated client data: %v" , err )
}
update := sstore . InfoMsgUpdate ( "telemetry is now on" )
update . ClientData = clientData
return update , nil
2023-01-23 21:54:32 +01:00
}
func TelemetryOffCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
clientData , err := sstore . EnsureClientData ( ctx )
if err != nil {
2023-02-26 23:33:01 +01:00
return nil , fmt . Errorf ( "cannot retrieve client data: %v" , err )
2023-01-23 08:10:18 +01:00
}
2023-01-23 21:54:32 +01:00
if clientData . ClientOpts . NoTelemetry {
return sstore . InfoMsgUpdate ( "telemetry is already off" ) , nil
}
err = setNoTelemetry ( ctx , clientData , true )
if err != nil {
return nil , err
}
2023-03-23 20:08:03 +01:00
clientData , err = sstore . EnsureClientData ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve updated client data: %v" , err )
}
update := sstore . InfoMsgUpdate ( "telemetry is now off" )
update . ClientData = clientData
return update , nil
2023-01-23 21:54:32 +01:00
}
func TelemetryShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
clientData , err := sstore . EnsureClientData ( ctx )
if err != nil {
2023-02-26 23:33:01 +01:00
return nil , fmt . Errorf ( "cannot retrieve client data: %v" , err )
2023-01-23 21:54:32 +01:00
}
var buf bytes . Buffer
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "telemetry" , boolToStr ( clientData . ClientOpts . NoTelemetry , "off" , "on" ) ) )
2023-05-09 01:06:51 +02:00
update := & sstore . ModelUpdate {
2023-01-23 08:10:18 +01:00
Info : & sstore . InfoMsgType {
2023-01-23 21:54:32 +01:00
InfoTitle : fmt . Sprintf ( "telemetry info" ) ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
2023-01-23 08:10:18 +01:00
} ,
}
return update , nil
2023-01-19 20:10:12 +01:00
}
2023-01-23 21:54:32 +01:00
func TelemetrySendCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
clientData , err := sstore . EnsureClientData ( ctx )
if err != nil {
2023-02-26 23:33:01 +01:00
return nil , fmt . Errorf ( "cannot retrieve client data: %v" , err )
2023-01-23 21:54:32 +01:00
}
force := resolveBool ( pk . Kwargs [ "force" ] , false )
if clientData . ClientOpts . NoTelemetry && ! force {
return nil , fmt . Errorf ( "cannot send telemetry, telemetry is off. pass force=1 to force the send, or turn on telemetry with /telemetry:on" )
}
err = pcloud . SendTelemetry ( ctx , force )
if err != nil {
return nil , fmt . Errorf ( "failed to send telemetry: %v" , err )
}
return sstore . InfoMsgUpdate ( "telemetry sent" ) , nil
}
2022-09-21 21:39:55 +02:00
func formatTermOpts ( termOpts sstore . TermOpts ) string {
if termOpts . Cols == 0 {
return "???"
}
rtnStr := fmt . Sprintf ( "%dx%d" , termOpts . Rows , termOpts . Cols )
if termOpts . FlexRows {
rtnStr += " flexrows"
}
if termOpts . MaxPtySize > 0 {
rtnStr += " maxbuf=" + scbase . NumFormatB2 ( termOpts . MaxPtySize )
}
return rtnStr
}
2022-09-25 04:54:06 +02:00
type ColMeta struct {
Title string
MinCols int
MaxCols int
}
func toInterfaceArr ( sarr [ ] string ) [ ] interface { } {
rtn := make ( [ ] interface { } , len ( sarr ) )
for idx , s := range sarr {
rtn [ idx ] = s
}
return rtn
}
func formatTextTable ( totalCols int , data [ ] [ ] string , colMeta [ ] ColMeta ) [ ] string {
numCols := len ( colMeta )
maxColLen := make ( [ ] int , len ( colMeta ) )
for i , cm := range colMeta {
maxColLen [ i ] = cm . MinCols
}
for _ , row := range data {
for i := 0 ; i < numCols && i < len ( row ) ; i ++ {
dlen := len ( row [ i ] )
if dlen > maxColLen [ i ] {
maxColLen [ i ] = dlen
}
}
}
fmtStr := ""
for idx , clen := range maxColLen {
if idx != 0 {
fmtStr += " "
}
fmtStr += fmt . Sprintf ( "%%%ds" , clen )
}
var rtn [ ] string
for _ , row := range data {
sval := fmt . Sprintf ( fmtStr , toInterfaceArr ( row ) ... )
rtn = append ( rtn , sval )
}
return rtn
}
2022-10-21 01:14:14 +02:00
2022-11-28 09:13:00 +01:00
func isValidInScope ( scopeName string , varName string ) bool {
for _ , varScope := range SetVarScopes {
if varScope . ScopeName == scopeName {
return utilfn . ContainsStr ( varScope . VarNames , varName )
}
}
return false
}
// returns (is-valid, scope, name)
2023-03-15 00:37:22 +01:00
// TODO write a full resolver to allow for indexed arguments. e.g. session[1].screen[1].screen.pterm="25x80"
2022-11-28 09:13:00 +01:00
func resolveSetArg ( argName string ) ( bool , string , string ) {
dotIdx := strings . Index ( argName , "." )
if dotIdx == - 1 {
argName = SetVarNameMap [ argName ]
dotIdx = strings . Index ( argName , "." )
}
if argName == "" {
return false , "" , ""
}
scopeName := argName [ 0 : dotIdx ]
varName := argName [ dotIdx + 1 : ]
if ! isValidInScope ( scopeName , varName ) {
return false , "" , ""
}
return true , scopeName , varName
}