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-12-16 07:20:03 +01:00
"github.com/gorilla/websocket"
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"
2023-12-16 02:43:54 +01:00
"github.com/wavetermdev/waveterm/wavesrv/pkg/releasechecker"
2023-10-16 22:30:10 +02:00
"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"
2023-12-16 02:43:54 +01:00
"golang.org/x/mod/semver"
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
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
const MaxSidebarSections = 5
2022-08-27 01:21:19 +02:00
2023-11-17 07:49:59 +01:00
const TermFontSizeMin = 8
const TermFontSizeMax = 24
2023-09-01 07:04:31 +02:00
const TsFormatStr = "2006-01-02 15:04:05"
2023-12-16 07:20:03 +01:00
const OpenAIPacketTimeout = 10 * time . Second
const OpenAIStreamTimeout = 5 * time . Minute
const OpenAICloudCompletionTelemetryOffErrorMsg = "In order to protect against abuse, you must have telemetry turned on in order to use Wave's free AI features. If you do not want to turn telemetry on, you can still use Wave's AI features by adding your own OpenAI key in Settings. Note that when you use your own key, requests are not proxied through Wave's servers and will be sent directly to the OpenAI API."
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" ,
2023-12-15 06:50:47 +01:00
"index" : "screen.index" ,
2022-11-28 09:13:00 +01:00
}
var SetVarScopes = [ ] SetVarScope {
2023-12-05 19:49:30 +01:00
{ ScopeName : "global" , VarNames : [ ] string { } } ,
{ ScopeName : "client" , VarNames : [ ] string { "telemetry" } } ,
{ ScopeName : "session" , VarNames : [ ] string { "name" , "pos" } } ,
2023-12-15 06:50:47 +01:00
{ ScopeName : "screen" , VarNames : [ ] string { "name" , "tabcolor" , "tabicon" , "pos" , "pterm" , "anchor" , "focus" , "line" , "index" } } ,
2023-12-05 19:49:30 +01:00
{ ScopeName : "line" , VarNames : [ ] string { } } ,
2022-11-28 09:13:00 +01:00
// connection = remote, remote = remoteinstance
2023-12-05 19:49:30 +01:00
{ ScopeName : "connection" , VarNames : [ ] string { "alias" , "connectmode" , "key" , "password" , "autoinstall" , "color" } } ,
{ ScopeName : "remote" , VarNames : [ ] string { } } ,
2022-11-28 09:13:00 +01:00
}
2022-10-22 23:46:39 +02:00
2023-12-08 03:33:16 +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 )
2023-12-15 06:50:47 +01:00
registerCmdFn ( "screen:reorder" , ScreenReorderCommand )
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
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
registerCmdFn ( "sidebar:open" , SidebarOpenCommand )
registerCmdFn ( "sidebar:close" , SidebarCloseCommand )
registerCmdFn ( "sidebar:add" , SidebarAddCommand )
registerCmdFn ( "sidebar:remove" , SidebarRemoveCommand )
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-12-16 02:43:54 +01:00
registerCmdFn ( "releasecheck" , ReleaseCheckCommand )
registerCmdFn ( "releasecheck:autoon" , ReleaseCheckOnCommand )
registerCmdFn ( "releasecheck:autooff" , ReleaseCheckOffCommand )
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 ]
}
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
// will trim strings for whitespace
func resolveCommaSepListToMap ( arg string ) map [ string ] bool {
if arg == "" {
return nil
}
rtn := make ( map [ string ] bool )
fields := strings . Split ( arg , "," )
for _ , field := range fields {
field = strings . TrimSpace ( field )
rtn [ field ] = true
}
return rtn
}
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
2023-12-02 00:21:24 +01:00
ptermVal := defaultStr ( pk . Kwargs [ "wterm" ] , DefaultPTERM )
2023-04-13 06:42:16 +02:00
runPacket . TermOpts , err = GetUITermOpts ( pk . UIContext . WinSize , ptermVal )
if err != nil {
2023-12-02 00:21:24 +01:00
return nil , fmt . Errorf ( "/sync error, invalid 'wterm' value %q: %v" , ptermVal , err )
2023-04-13 06:42:16 +02:00
}
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
2023-12-02 00:21:24 +01:00
ptermVal := defaultStr ( pk . Kwargs [ "wterm" ] , DefaultPTERM )
2022-11-28 09:13:00 +01:00
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
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
// this update is sent asynchronously for timing issues. the cmd update comes async as well
// so if we return this directly it sometimes gets evaluated first. by pushing it on the MainBus
// it ensures it happens after the command creation event.
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
}
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
func implementRunInSidebar ( ctx context . Context , screenId string , lineId string ) ( * sstore . ScreenType , error ) {
screen , err := sidebarSetOpen ( ctx , "run" , screenId , true , "" )
if err != nil {
return nil , err
}
screen . ScreenViewOpts . Sidebar . SidebarLineId = lineId
err = sstore . ScreenUpdateViewOpts ( ctx , screenId , screen . ScreenViewOpts )
if err != nil {
return nil , fmt . Errorf ( "/run error updating screenviewopts: %v" , err )
}
return screen , nil
}
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 ) {
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
// TODO should this be "pk" or "newPk" (2nd arg)
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
}
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
var hasModelUpdate bool
var modelUpdate * sstore . ModelUpdate
if update == nil {
hasModelUpdate = true
modelUpdate = & sstore . ModelUpdate { }
update = modelUpdate
} else if mu , ok := update . ( * sstore . ModelUpdate ) ; ok {
hasModelUpdate = true
modelUpdate = mu
}
if resolveBool ( newPk . Kwargs [ "sidebar" ] , false ) && historyContext . LineId != "" && hasModelUpdate {
ids , resolveErr := resolveUiIds ( ctx , newPk , R_Session | R_Screen )
// we are ignoring resolveErr (if not nil). obviously can't add to sidebar and
// either another error already happened, or this command was never about the sidebar
if resolveErr == nil {
screen , sidebarErr := implementRunInSidebar ( ctx , ids . ScreenId , historyContext . LineId )
if sidebarErr == nil {
modelUpdate . UpdateScreen ( screen )
} else {
modelUpdate . AddInfoError ( fmt . Sprintf ( "cannot move command to sidebar: %v" , sidebarErr ) )
}
}
}
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
}
2023-12-15 06:50:47 +01:00
func ScreenReorderCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
// Resolve the UI IDs for the session and screen
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
if err != nil {
return nil , err
}
// Extract the screen ID and the new index from the packet
screenId := ids . ScreenId
newScreenIdxStr := pk . Kwargs [ "index" ]
newScreenIdx , err := resolvePosInt ( newScreenIdxStr , 1 )
if err != nil {
return nil , fmt . Errorf ( "invalid new screen index: %v" , err )
}
// Call SetScreenIdx to update the screen's index in the database
err = sstore . SetScreenIdx ( ctx , ids . SessionId , screenId , newScreenIdx )
if err != nil {
return nil , fmt . Errorf ( "error updating screen index: %v" , err )
}
// Retrieve all session screens
screens , err := sstore . GetSessionScreens ( ctx , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "error retrieving updated screen: %v" , err )
}
// Prepare the update packet to send back to the client
update := & sstore . ModelUpdate {
Screens : screens ,
Info : & sstore . InfoMsgType {
InfoMsg : "screen indices updated successfully" ,
TimeoutMs : 2000 ,
} ,
}
return update , nil
}
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
var screenAnchorRe = regexp . MustCompile ( "^(\\d+)(?::(-?\\d+))?$" )
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" ]
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
}
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
var sidebarWidthRe = regexp . MustCompile ( "^\\d+(px|%)$" )
func sidebarSetOpen ( ctx context . Context , cmdStr string , screenId string , open bool , width string ) ( * sstore . ScreenType , error ) {
if width != "" && ! sidebarWidthRe . MatchString ( width ) {
return nil , fmt . Errorf ( "/%s invalid width specified, must be either a px value or a percent (e.g. '300px' or '50%%')" , cmdStr )
}
if strings . HasSuffix ( width , "%" ) {
percentNum , _ := strconv . Atoi ( width [ : len ( width ) - 1 ] )
if percentNum < 10 || percentNum > 90 {
return nil , fmt . Errorf ( "/%s invalid width specified, percentage must be between 10%% and 90%%" , cmdStr )
}
}
if strings . HasSuffix ( width , "px" ) {
pxNum , _ := strconv . Atoi ( width [ : len ( width ) - 2 ] )
if pxNum < 200 {
return nil , fmt . Errorf ( "/%s invalid width specified, minimum sizebar width is 200px" , cmdStr )
}
}
screen , err := sstore . GetScreenById ( ctx , screenId )
if err != nil {
return nil , fmt . Errorf ( "/%s cannot get screen: %v" , cmdStr , err )
}
if screen . ScreenViewOpts . Sidebar == nil {
screen . ScreenViewOpts . Sidebar = & sstore . ScreenSidebarOptsType { }
}
screen . ScreenViewOpts . Sidebar . Open = open
if width != "" {
screen . ScreenViewOpts . Sidebar . Width = width
}
err = sstore . ScreenUpdateViewOpts ( ctx , screenId , screen . ScreenViewOpts )
if err != nil {
return nil , fmt . Errorf ( "/%s error updating screenviewopts: %v" , cmdStr , err )
}
return screen , nil
}
func SidebarOpenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Screen )
if err != nil {
return nil , err
}
screen , err := sidebarSetOpen ( ctx , GetCmdStr ( pk ) , ids . ScreenId , true , pk . Kwargs [ "width" ] )
if err != nil {
return nil , err
}
return & sstore . ModelUpdate { Screens : [ ] * sstore . ScreenType { screen } } , nil
}
func SidebarCloseCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Screen )
if err != nil {
return nil , err
}
screen , err := sidebarSetOpen ( ctx , GetCmdStr ( pk ) , ids . ScreenId , false , "" )
if err != nil {
return nil , err
}
return & sstore . ModelUpdate { Screens : [ ] * sstore . ScreenType { screen } } , nil
}
func SidebarAddCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Screen )
if err != nil {
return nil , err
}
var addLineId string
if lineArg , ok := pk . Kwargs [ "line" ] ; ok {
lineId , err := sstore . FindLineIdByArg ( ctx , ids . ScreenId , lineArg )
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
addLineId = lineId
}
if addLineId == "" {
return nil , fmt . Errorf ( "/%s must specify line=[lineid] to add to the sidebar" , GetCmdStr ( pk ) )
}
screen , err := sidebarSetOpen ( ctx , GetCmdStr ( pk ) , ids . ScreenId , true , pk . Kwargs [ "width" ] )
if err != nil {
return nil , err
}
screen . ScreenViewOpts . Sidebar . SidebarLineId = addLineId
err = sstore . ScreenUpdateViewOpts ( ctx , ids . ScreenId , screen . ScreenViewOpts )
if err != nil {
return nil , fmt . Errorf ( "/%s error updating screenviewopts: %v" , GetCmdStr ( pk ) , err )
}
return & sstore . ModelUpdate { Screens : [ ] * sstore . ScreenType { screen } } , nil
}
func SidebarRemoveCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Screen )
if err != nil {
return nil , err
}
screen , err := sstore . GetScreenById ( ctx , ids . ScreenId )
if err != nil {
return nil , fmt . Errorf ( "/%s cannot get screeen: %v" , GetCmdStr ( pk ) , err )
}
sidebar := screen . ScreenViewOpts . Sidebar
if sidebar == nil {
return nil , nil
}
sidebar . SidebarLineId = ""
sidebar . Open = false
err = sstore . ScreenUpdateViewOpts ( ctx , ids . ScreenId , screen . ScreenViewOpts )
if err != nil {
return nil , fmt . Errorf ( "/%s error updating screenviewopts: %v" , GetCmdStr ( pk ) , err )
}
return & sstore . ModelUpdate { Screens : [ ] * sstore . ScreenType { screen } } , nil
}
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
}
2023-12-16 07:43:59 +01:00
func doOpenAICompletion ( cmd * sstore . CmdType , opts * sstore . OpenAIOptsType , prompt [ ] packet . OpenAIPromptMessageType ) {
2023-05-04 10:01:13 +02:00
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 )
} ( )
2023-12-16 07:20:03 +01:00
var respPks [ ] * packet . OpenAIPacketType
var err error
// run open ai completion locally
respPks , err = openai . RunCompletion ( ctx , opts , prompt )
2023-05-04 10:01:13 +02:00
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
}
2023-12-16 07:43:59 +01:00
func doOpenAIStreamCompletion ( cmd * sstore . CmdType , clientId string , opts * sstore . OpenAIOptsType , prompt [ ] packet . OpenAIPromptMessageType ) {
2023-05-04 10:01:13 +02:00
var outputPos int64
var hadError bool
startTime := time . Now ( )
2023-12-16 07:20:03 +01:00
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , OpenAIStreamTimeout )
2023-05-04 10:01:13 +02:00
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 )
} ( )
2023-12-16 07:20:03 +01:00
var ch chan * packet . OpenAIPacketType
var err error
if opts . APIToken == "" {
var conn * websocket . Conn
2023-12-16 07:43:59 +01:00
ch , conn , err = openai . RunCloudCompletionStream ( ctx , clientId , opts , prompt )
2023-12-16 07:20:03 +01:00
if conn != nil {
defer conn . Close ( )
}
} else {
ch , err = openai . RunCompletionStream ( ctx , opts , prompt )
}
2023-05-04 10:01:13 +02:00
if err != nil {
writeErrorToPty ( cmd , fmt . Sprintf ( "error calling OpenAI API: %v" , err ) , outputPos )
return
}
2023-12-16 07:20:03 +01:00
doneWaitingForPackets := false
for ! doneWaitingForPackets {
select {
case <- time . After ( OpenAIPacketTimeout ) :
// timeout reading from channel
hadError = true
pk := openai . CreateErrorPacket ( fmt . Sprintf ( "timeout waiting for server response" ) )
err = writePacketToPty ( ctx , cmd , pk , & outputPos )
if err != nil {
log . Printf ( "error writing response to ptybuffer: %v" , err )
return
}
doneWaitingForPackets = true
break
case pk , ok := <- ch :
if ok {
// got a packet
if pk . Error != "" {
hadError = true
}
err = writePacketToPty ( ctx , cmd , pk , & outputPos )
if err != nil {
hadError = true
log . Printf ( "error writing response to ptybuffer: %v" , err )
return
}
} else {
// channel closed
doneWaitingForPackets = true
break
}
2023-05-04 10:01:13 +02:00
}
}
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 )
}
2023-12-16 07:20:03 +01:00
if clientData . OpenAIOpts == nil {
return nil , fmt . Errorf ( "error retrieving client open ai options" )
2023-05-09 01:06:51 +02:00
}
opts := clientData . OpenAIOpts
2023-12-16 07:20:03 +01:00
if opts . APIToken == "" {
if clientData . ClientOpts . NoTelemetry {
return nil , fmt . Errorf ( OpenAICloudCompletionTelemetryOffErrorMsg )
}
}
2023-05-09 01:06:51 +02:00
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
}
2023-12-02 00:21:24 +01:00
ptermVal := defaultStr ( pk . Kwargs [ "wterm" ] , DefaultPTERM )
2023-05-04 10:01:13 +02:00
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 )
}
2023-12-16 07:43:59 +01:00
prompt := [ ] packet . OpenAIPromptMessageType { { Role : sstore . OpenAIRoleUser , Content : promptStr } }
2023-05-09 01:06:51 +02:00
if resolveBool ( pk . Kwargs [ "stream" ] , true ) {
2023-12-16 07:43:59 +01:00
go doOpenAIStreamCompletion ( cmd , clientData . ClientId , opts , prompt )
2023-05-04 10:01:13 +02:00
} 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 {
2023-12-05 19:43:49 +01:00
return nil , fmt . Errorf ( "/%s error: cannot resolve screen for update: %w" , GetCmdStr ( pk ) , err )
2023-10-24 08:22:18 +02:00
}
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 {
2023-12-26 21:59:25 +01:00
CmdLine : & utilfn . StrWithPos { Str : newSP . Str , Pos : newSP . Pos } ,
2022-11-11 03:51:20 +01:00
}
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 ) )
}
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
}
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
includeMap := resolveCommaSepListToMap ( pk . Kwargs [ "include" ] )
excludeMap := resolveCommaSepListToMap ( pk . Kwargs [ "exclude" ] )
2022-09-06 05:08:59 +02:00
for _ , cmd := range runningCmds {
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
if excludeMap [ cmd . LineId ] {
continue
}
if len ( includeMap ) > 0 && ! includeMap [ cmd . LineId ] {
continue
}
2022-09-06 05:08:59 +02:00
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 ) > 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 )
}
2023-11-17 07:49:59 +01:00
if newFontSize < TermFontSizeMin || newFontSize > TermFontSizeMax {
return nil , fmt . Errorf ( "invalid termfontsize, must be a number between %d-%d" , TermFontSizeMin , TermFontSizeMax )
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-12-13 23:03:22 +01:00
if aiBaseURL , found := pk . Kwargs [ "openaibaseurl" ] ; found {
aiOpts := clientData . OpenAIOpts
if aiOpts == nil {
aiOpts = & sstore . OpenAIOptsType { }
clientData . OpenAIOpts = aiOpts
}
aiOpts . BaseURL = aiBaseURL
varsUpdated = append ( varsUpdated , "openaibaseurl" )
err = sstore . UpdateClientOpenAIOpts ( ctx , * aiOpts )
if err != nil {
return nil , fmt . Errorf ( "error updating client openai base url: %v" , err )
}
}
2023-02-26 23:33:01 +01:00
if len ( varsUpdated ) == 0 {
2023-12-13 23:03:22 +01:00
return nil , fmt . Errorf ( "/client:set requires a value to set: %s" , formatStrs ( [ ] string { "termfontsize" , "openaiapitoken" , "openaimodel" , "openaibaseurl" , "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-12-16 02:43:54 +01:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "release-check" , boolToStr ( clientData . ClientOpts . NoReleaseCheck , "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 ( ) {
2023-12-18 23:41:53 +01:00
cloudCtx , cancelFn := context . WithTimeout ( context . Background ( ) , 10 * time . Second )
defer cancelFn ( )
err := pcloud . SendNoTelemetryUpdate ( cloudCtx , clientOpts . NoTelemetry )
2023-10-31 06:39:51 +01:00
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 ( ) {
2023-12-18 23:41:53 +01:00
cloudCtx , cancelFn := context . WithTimeout ( context . Background ( ) , 10 * time . Second )
defer cancelFn ( )
err := pcloud . SendTelemetry ( cloudCtx , false )
2023-10-31 06:39:51 +01:00
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
}
2023-12-16 02:43:54 +01:00
func runReleaseCheck ( ctx context . Context , force bool ) error {
rslt , err := releasechecker . CheckNewRelease ( ctx , force )
if err != nil {
return fmt . Errorf ( "error checking for new release: %v" , err )
}
if rslt == releasechecker . Failure {
return fmt . Errorf ( "error checking for new release, see log for details" )
}
return nil
}
func setNoReleaseCheck ( ctx context . Context , clientData * sstore . ClientData , noReleaseCheckValue bool ) error {
clientOpts := clientData . ClientOpts
clientOpts . NoReleaseCheck = noReleaseCheckValue
err := sstore . SetClientOpts ( ctx , clientOpts )
if err != nil {
return fmt . Errorf ( "error trying to update client releaseCheck setting: %v" , err )
}
log . Printf ( "client no-release-check setting updated to %v\n" , noReleaseCheckValue )
return nil
}
func ReleaseCheckOnCommand ( 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 )
}
if ! clientData . ClientOpts . NoReleaseCheck {
return sstore . InfoMsgUpdate ( "release check is already on" ) , nil
}
err = setNoReleaseCheck ( ctx , clientData , false )
if err != nil {
return nil , err
}
2023-12-19 02:19:11 +01:00
go func ( ) {
releaseCheckCtx , cancelFn := context . WithTimeout ( context . Background ( ) , 5 * time . Second )
defer cancelFn ( )
releaseCheckErr := runReleaseCheck ( releaseCheckCtx , true )
if releaseCheckErr != nil {
log . Printf ( "error checking for new release after enabling auto release check: %v\n" , releaseCheckErr )
}
} ( )
2023-12-16 02:43:54 +01:00
clientData , err = sstore . EnsureClientData ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve updated client data: %v" , err )
}
update := sstore . InfoMsgUpdate ( "automatic release checking is now on" )
update . ClientData = clientData
return update , nil
}
func ReleaseCheckOffCommand ( 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 )
}
if clientData . ClientOpts . NoReleaseCheck {
return sstore . InfoMsgUpdate ( "release check is already off" ) , nil
}
err = setNoReleaseCheck ( ctx , clientData , true )
if err != nil {
return nil , err
}
clientData , err = sstore . EnsureClientData ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve updated client data: %v" , err )
}
update := sstore . InfoMsgUpdate ( "automatic release checking is now off" )
update . ClientData = clientData
return update , nil
}
func ReleaseCheckCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
err := runReleaseCheck ( ctx , true )
if err != nil {
return nil , err
}
clientData , err := sstore . EnsureClientData ( ctx )
if err != nil {
return nil , fmt . Errorf ( "cannot retrieve updated client data: %v" , err )
}
var rsp string
if semver . Compare ( scbase . WaveVersion , clientData . ReleaseInfo . LatestVersion ) < 0 {
rsp = "new release available to download: https://www.waveterm.dev/download"
} else {
rsp = "no new release available"
}
update := sstore . InfoMsgUpdate ( rsp )
update . ClientData = clientData
return update , 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
}