2015-03-20 21:11:11 +00:00
package terminal
2021-09-28 19:07:42 +00:00
//lint:file-ignore ST1005 errors here can be capitalized
2015-03-20 21:11:11 +00:00
import (
2022-01-27 21:18:25 +00:00
"bufio"
2015-03-20 21:11:11 +00:00
"fmt"
"io"
2019-07-17 22:54:15 +00:00
"net/rpc"
2015-03-20 21:11:11 +00:00
"os"
"os/signal"
"strings"
2018-06-21 11:07:37 +00:00
"sync"
2016-01-15 05:26:54 +00:00
"syscall"
2015-03-20 21:11:11 +00:00
2021-08-05 17:55:27 +00:00
"github.com/derekparker/trie"
2022-02-14 17:42:43 +00:00
"github.com/go-delve/liner"
2016-02-27 23:02:55 +00:00
2019-01-04 18:39:25 +00:00
"github.com/go-delve/delve/pkg/config"
2020-10-26 12:36:52 +00:00
"github.com/go-delve/delve/pkg/locspec"
2021-01-28 15:08:14 +00:00
"github.com/go-delve/delve/pkg/terminal/colorize"
2019-07-02 17:55:27 +00:00
"github.com/go-delve/delve/pkg/terminal/starbind"
2019-01-04 18:39:25 +00:00
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/api"
2015-03-20 21:11:11 +00:00
)
2015-09-30 05:42:06 +00:00
const (
2018-10-10 16:42:32 +00:00
historyFile string = ".dbg_history"
terminalHighlightEscapeCode string = "\033[%2dm"
terminalResetEscapeCode string = "\033[0m"
)
const (
ansiBlack = 30
ansiRed = 31
ansiGreen = 32
ansiYellow = 33
ansiBlue = 34
ansiMagenta = 35
ansiCyan = 36
ansiWhite = 37
ansiBrBlack = 90
ansiBrRed = 91
ansiBrGreen = 92
ansiBrYellow = 93
ansiBrBlue = 94
ansiBrMagenta = 95
ansiBrCyan = 96
ansiBrWhite = 97
2015-09-30 05:42:06 +00:00
)
2015-03-20 21:11:11 +00:00
2016-01-10 08:57:52 +00:00
// Term represents the terminal running dlv.
2015-03-20 21:11:11 +00:00
type Term struct {
2022-01-27 21:18:25 +00:00
client service . Client
conf * config . Config
prompt string
line * liner . State
cmds * Commands
stdout * transcriptWriter
InitFile string
displays [ ] displayEntry
2018-06-21 11:07:37 +00:00
2020-03-19 16:15:23 +00:00
historyFile * os . File
2019-07-02 17:55:27 +00:00
starlarkEnv * starbind . Env
2020-10-26 12:36:52 +00:00
substitutePathRulesCache [ ] [ 2 ] string
2018-06-21 11:07:37 +00:00
// quitContinue is set to true by exitCommand to signal that the process
// should be resumed before quitting.
quitContinue bool
2021-01-04 16:54:39 +00:00
longCommandMu sync . Mutex
longCommandCancelFlag bool
2018-06-21 11:07:37 +00:00
quittingMutex sync . Mutex
quitting bool
2022-08-04 08:10:54 +00:00
traceNonInteractive bool
2015-03-20 21:11:11 +00:00
}
2021-03-25 16:45:30 +00:00
type displayEntry struct {
expr string
fmtstr string
}
2016-01-10 08:57:52 +00:00
// New returns a new Term.
2015-08-17 03:41:25 +00:00
func New ( client service . Client , conf * config . Config ) * Term {
2016-02-27 23:02:55 +00:00
cmds := DebugCommands ( client )
if conf != nil && conf . Aliases != nil {
cmds . Merge ( conf . Aliases )
}
2016-03-24 10:55:48 +00:00
2018-05-05 14:40:58 +00:00
if conf == nil {
conf = & config . Config { }
}
2019-07-02 17:55:27 +00:00
t := & Term {
2016-10-25 17:01:38 +00:00
client : client ,
conf : conf ,
2015-03-20 21:11:11 +00:00
prompt : "(dlv) " ,
line : liner . NewLiner ( ) ,
2016-02-27 23:02:55 +00:00
cmds : cmds ,
2022-01-27 21:18:25 +00:00
stdout : & transcriptWriter { w : os . Stdout } ,
2021-01-28 15:08:14 +00:00
}
2022-02-10 17:50:55 +00:00
t . line . SetCtrlZStop ( true )
2021-01-28 15:08:14 +00:00
if strings . ToLower ( os . Getenv ( "TERM" ) ) != "dumb" {
2022-01-27 21:18:25 +00:00
t . stdout . w = getColorableWriter ( )
t . stdout . colorEscapes = make ( map [ colorize . Style ] string )
t . stdout . colorEscapes [ colorize . NormalStyle ] = terminalResetEscapeCode
2021-01-28 15:08:14 +00:00
wd := func ( s string , defaultCode int ) string {
if s == "" {
return fmt . Sprintf ( terminalHighlightEscapeCode , defaultCode )
}
return s
}
2022-01-27 21:18:25 +00:00
t . stdout . colorEscapes [ colorize . KeywordStyle ] = conf . SourceListKeywordColor
t . stdout . colorEscapes [ colorize . StringStyle ] = wd ( conf . SourceListStringColor , ansiGreen )
t . stdout . colorEscapes [ colorize . NumberStyle ] = conf . SourceListNumberColor
t . stdout . colorEscapes [ colorize . CommentStyle ] = wd ( conf . SourceListCommentColor , ansiBrMagenta )
t . stdout . colorEscapes [ colorize . ArrowStyle ] = wd ( conf . SourceListArrowColor , ansiYellow )
2021-01-28 15:08:14 +00:00
switch x := conf . SourceListLineColor . ( type ) {
case string :
2022-01-27 21:18:25 +00:00
t . stdout . colorEscapes [ colorize . LineNoStyle ] = x
2021-01-28 15:08:14 +00:00
case int :
if ( x > ansiWhite && x < ansiBrBlack ) || x < ansiBlack || x > ansiBrWhite {
x = ansiBlue
}
2022-01-27 21:18:25 +00:00
t . stdout . colorEscapes [ colorize . LineNoStyle ] = fmt . Sprintf ( terminalHighlightEscapeCode , x )
2021-01-28 15:08:14 +00:00
case nil :
2022-01-27 21:18:25 +00:00
t . stdout . colorEscapes [ colorize . LineNoStyle ] = fmt . Sprintf ( terminalHighlightEscapeCode , ansiBlue )
2021-01-28 15:08:14 +00:00
}
2015-03-20 21:11:11 +00:00
}
2019-07-02 17:55:27 +00:00
2019-07-08 17:27:31 +00:00
if client != nil {
lcfg := t . loadConfig ( )
client . SetReturnValuesLoadConfig ( & lcfg )
}
2022-01-27 21:18:25 +00:00
t . starlarkEnv = starbind . New ( starlarkContext { t } , t . stdout )
2019-07-02 17:55:27 +00:00
return t
2015-03-20 21:11:11 +00:00
}
2022-08-04 08:10:54 +00:00
func ( t * Term ) SetTraceNonInteractive ( ) {
t . traceNonInteractive = true
}
func ( t * Term ) IsTraceNonInteractive ( ) bool {
return t . traceNonInteractive
}
2016-02-27 23:02:55 +00:00
// Close returns the terminal to its previous mode.
2016-02-02 11:48:54 +00:00
func ( t * Term ) Close ( ) {
t . line . Close ( )
2022-01-27 21:18:25 +00:00
if err := t . stdout . CloseTranscript ( ) ; err != nil {
fmt . Fprintf ( os . Stderr , "error closing transcript file: %v\n" , err )
}
2016-02-02 11:48:54 +00:00
}
2018-06-21 11:07:37 +00:00
func ( t * Term ) sigintGuard ( ch <- chan os . Signal , multiClient bool ) {
for range ch {
2021-01-04 16:54:39 +00:00
t . longCommandCancel ( )
2019-07-02 17:55:27 +00:00
t . starlarkEnv . Cancel ( )
2020-03-24 16:09:28 +00:00
state , err := t . client . GetStateNonBlocking ( )
if err == nil && state . Recording {
2022-01-27 21:18:25 +00:00
fmt . Fprintf ( t . stdout , "received SIGINT, stopping recording (will not forward signal)\n" )
2020-03-24 16:09:28 +00:00
err := t . client . StopRecording ( )
if err != nil {
fmt . Fprintf ( os . Stderr , "%v\n" , err )
}
continue
}
2021-01-29 21:39:33 +00:00
if err == nil && state . CoreDumping {
2022-01-27 21:18:25 +00:00
fmt . Fprintf ( t . stdout , "received SIGINT, stopping dump\n" )
2021-01-29 21:39:33 +00:00
err := t . client . CoreDumpCancel ( )
if err != nil {
fmt . Fprintf ( os . Stderr , "%v\n" , err )
}
continue
}
2018-06-21 11:07:37 +00:00
if multiClient {
2020-04-26 15:40:42 +00:00
answer , err := t . line . Prompt ( "Would you like to [p]ause the target (returning to Delve's prompt) or [q]uit this client (leaving the target running) [p/q]? " )
2018-06-21 11:07:37 +00:00
if err != nil {
fmt . Fprintf ( os . Stderr , "%v" , err )
continue
}
answer = strings . TrimSpace ( answer )
switch answer {
2020-04-26 15:40:42 +00:00
case "p" :
2018-06-21 11:07:37 +00:00
_ , err := t . client . Halt ( )
if err != nil {
fmt . Fprintf ( os . Stderr , "%v" , err )
}
case "q" :
t . quittingMutex . Lock ( )
t . quitting = true
t . quittingMutex . Unlock ( )
err := t . client . Disconnect ( false )
if err != nil {
fmt . Fprintf ( os . Stderr , "%v" , err )
} else {
t . Close ( )
}
default :
2022-01-27 21:18:25 +00:00
fmt . Fprintln ( t . stdout , "only p or q allowed" )
2018-06-21 11:07:37 +00:00
}
2015-03-20 21:11:11 +00:00
2018-06-21 11:07:37 +00:00
} else {
2022-01-27 21:18:25 +00:00
fmt . Fprintf ( t . stdout , "received SIGINT, stopping process (will not forward signal)\n" )
2015-03-20 21:11:11 +00:00
_ , err := t . client . Halt ( )
if err != nil {
2022-01-27 21:18:25 +00:00
fmt . Fprintf ( t . stdout , "%v" , err )
2015-03-20 21:11:11 +00:00
}
}
2018-06-21 11:07:37 +00:00
}
}
// Run begins running dlv in the terminal.
func ( t * Term ) Run ( ) ( int , error ) {
defer t . Close ( )
multiClient := t . client . IsMulticlient ( )
// Send the debugger a halt command on SIGINT
2020-03-19 18:17:50 +00:00
ch := make ( chan os . Signal , 1 )
2018-06-21 11:07:37 +00:00
signal . Notify ( ch , syscall . SIGINT )
go t . sigintGuard ( ch , multiClient )
2015-03-20 21:11:11 +00:00
2021-08-05 17:55:27 +00:00
fns := trie . New ( )
cmds := trie . New ( )
funcs , _ := t . client . ListFunctions ( "" )
for _ , fn := range funcs {
fns . Add ( fn , nil )
}
for _ , cmd := range t . cmds . cmds {
for _ , alias := range cmd . aliases {
cmds . Add ( alias , nil )
2019-07-12 17:43:42 +00:00
}
2021-08-05 17:55:27 +00:00
}
2022-05-18 16:18:28 +00:00
var locs * trie . Trie
2021-08-05 17:55:27 +00:00
t . line . SetCompleter ( func ( line string ) ( c [ ] string ) {
cmd := t . cmds . Find ( strings . Split ( line , " " ) [ 0 ] , noPrefix )
switch cmd . aliases [ 0 ] {
case "break" , "trace" , "continue" :
if spc := strings . LastIndex ( line , " " ) ; spc > 0 {
prefix := line [ : spc ] + " "
funcs := fns . FuzzySearch ( line [ spc + 1 : ] )
for _ , f := range funcs {
c = append ( c , prefix + f )
2015-09-10 14:09:32 +00:00
}
}
2021-08-05 17:55:27 +00:00
case "nullcmd" , "nocmd" :
commands := cmds . FuzzySearch ( strings . ToLower ( line ) )
c = append ( c , commands ... )
2022-05-18 16:18:28 +00:00
case "print" , "whatis" :
if locs == nil {
localVars , err := t . client . ListLocalVariables (
api . EvalScope { GoroutineID : - 1 , Frame : t . cmds . frame , DeferredCall : 0 } ,
api . LoadConfig { } ,
)
if err != nil {
fmt . Fprintf ( os . Stderr , "Unable to get local variables: %s\n" , err )
break
}
locs = trie . New ( )
for _ , loc := range localVars {
locs . Add ( loc . Name , nil )
}
}
if spc := strings . LastIndex ( line , " " ) ; spc > 0 {
prefix := line [ : spc ] + " "
locals := locs . FuzzySearch ( line [ spc + 1 : ] )
for _ , l := range locals {
c = append ( c , prefix + l )
}
}
2015-09-10 14:09:32 +00:00
}
return
} )
2015-08-17 03:41:25 +00:00
fullHistoryFile , err := config . GetConfigFilePath ( historyFile )
2015-03-20 21:11:11 +00:00
if err != nil {
2015-05-04 00:56:38 +00:00
fmt . Printf ( "Unable to load history file: %v." , err )
2015-03-20 21:11:11 +00:00
}
2015-05-04 00:56:38 +00:00
2020-03-19 16:15:23 +00:00
t . historyFile , err = os . OpenFile ( fullHistoryFile , os . O_RDWR | os . O_CREATE , 0600 )
2015-05-04 00:56:38 +00:00
if err != nil {
2020-03-19 16:15:23 +00:00
fmt . Printf ( "Unable to open history file: %v. History will not be saved for this session." , err )
}
if _ , err := t . line . ReadHistory ( t . historyFile ) ; err != nil {
fmt . Printf ( "Unable to read history file: %v" , err )
2015-05-04 00:56:38 +00:00
}
2015-03-20 21:11:11 +00:00
fmt . Println ( "Type 'help' for list of commands." )
2015-09-29 16:40:12 +00:00
if t . InitFile != "" {
2016-02-27 23:02:55 +00:00
err := t . cmds . executeFile ( t , t . InitFile )
2015-09-29 16:40:12 +00:00
if err != nil {
2018-11-30 08:53:29 +00:00
if _ , ok := err . ( ExitRequestError ) ; ok {
return t . handleExit ( )
}
2015-09-29 16:40:12 +00:00
fmt . Fprintf ( os . Stderr , "Error executing init file: %s\n" , err )
}
}
2020-03-14 21:34:56 +00:00
var lastCmd string
2020-03-24 16:09:28 +00:00
// Ensure that the target process is neither running nor recording by
// making a blocking call.
_ , _ = t . client . GetState ( )
2015-03-20 21:11:11 +00:00
for {
2022-05-18 16:18:28 +00:00
locs = nil
2015-03-20 21:11:11 +00:00
cmdstr , err := t . promptForInput ( )
if err != nil {
if err == io . EOF {
2022-01-27 21:18:25 +00:00
fmt . Fprintln ( t . stdout , "exit" )
2015-07-29 23:19:06 +00:00
return t . handleExit ( )
2015-03-20 21:11:11 +00:00
}
2016-03-09 15:11:58 +00:00
return 1 , fmt . Errorf ( "Prompt for input failed.\n" )
2015-03-20 21:11:11 +00:00
}
2022-01-27 21:18:25 +00:00
t . stdout . Echo ( t . prompt + cmdstr + "\n" )
2015-03-20 21:11:11 +00:00
2020-03-14 21:34:56 +00:00
if strings . TrimSpace ( cmdstr ) == "" {
cmdstr = lastCmd
}
lastCmd = cmdstr
2017-04-19 10:17:21 +00:00
if err := t . cmds . Call ( cmdstr , t ) ; err != nil {
2015-07-29 23:19:06 +00:00
if _ , ok := err . ( ExitRequestError ) ; ok {
return t . handleExit ( )
}
2015-06-21 18:08:14 +00:00
// The type information gets lost in serialization / de-serialization,
// so we do a string compare on the error message to see if the process
// has exited, or if the command actually failed.
if strings . Contains ( err . Error ( ) , "exited" ) {
fmt . Fprintln ( os . Stderr , err . Error ( ) )
} else {
2018-06-21 11:07:37 +00:00
t . quittingMutex . Lock ( )
quitting := t . quitting
t . quittingMutex . Unlock ( )
if quitting {
return t . handleExit ( )
}
2015-03-20 21:11:11 +00:00
fmt . Fprintf ( os . Stderr , "Command failed: %s\n" , err )
}
}
2022-01-27 21:18:25 +00:00
t . stdout . Flush ( )
2015-03-20 21:11:11 +00:00
}
}
2018-03-20 10:05:35 +00:00
// Substitutes directory to source file.
2016-10-25 17:01:38 +00:00
//
2018-03-20 10:05:35 +00:00
// Ensures that only directory is substituted, for example:
2016-10-25 17:01:38 +00:00
// substitute from `/dir/subdir`, substitute to `/new`
// for file path `/dir/subdir/file` will return file path `/new/file`.
// for file path `/dir/subdir-2/file` substitution will not be applied.
//
// If more than one substitution rule is defined, the rules are applied
// in the order they are defined, first rule that matches is used for
// substitution.
func ( t * Term ) substitutePath ( path string ) string {
if t . conf == nil {
return path
}
2020-10-26 12:36:52 +00:00
return locspec . SubstitutePath ( path , t . substitutePathRules ( ) )
}
2018-11-27 21:42:21 +00:00
2020-10-26 12:36:52 +00:00
func ( t * Term ) substitutePathRules ( ) [ ] [ 2 ] string {
if t . substitutePathRulesCache != nil {
return t . substitutePathRulesCache
2018-11-27 21:42:21 +00:00
}
2020-10-26 12:36:52 +00:00
if t . conf == nil || t . conf . SubstitutePath == nil {
return nil
}
spr := make ( [ ] [ 2 ] string , 0 , len ( t . conf . SubstitutePath ) )
2016-10-25 17:01:38 +00:00
for _ , r := range t . conf . SubstitutePath {
2020-10-26 12:36:52 +00:00
spr = append ( spr , [ 2 ] string { r . From , r . To } )
2016-10-25 17:01:38 +00:00
}
2020-10-26 12:36:52 +00:00
t . substitutePathRulesCache = spr
return spr
2016-10-25 17:01:38 +00:00
}
2020-10-26 12:36:52 +00:00
// formatPath applies path substitution rules and shortens the resulting
// path by replacing the current directory with './'
func ( t * Term ) formatPath ( path string ) string {
path = t . substitutePath ( path )
workingDir , _ := os . Getwd ( )
return strings . Replace ( path , workingDir , "." , 1 )
2016-10-25 17:01:38 +00:00
}
2015-04-30 13:38:00 +00:00
func ( t * Term ) promptForInput ( ) ( string , error ) {
l , err := t . line . Prompt ( t . prompt )
if err != nil {
return "" , err
}
l = strings . TrimSuffix ( l , "\n" )
if l != "" {
t . line . AppendHistory ( l )
}
return l , nil
}
2018-06-21 11:07:37 +00:00
func yesno ( line * liner . State , question string ) ( bool , error ) {
for {
answer , err := line . Prompt ( question )
if err != nil {
return false , err
}
answer = strings . ToLower ( strings . TrimSpace ( answer ) )
switch answer {
case "n" , "no" :
return false , nil
case "y" , "yes" :
return true , nil
}
}
}
2016-01-10 08:57:52 +00:00
func ( t * Term ) handleExit ( ) ( int , error ) {
2020-03-19 16:15:23 +00:00
if t . historyFile != nil {
if _ , err := t . line . WriteHistory ( t . historyFile ) ; err != nil {
fmt . Println ( "readline history error:" , err )
}
if err := t . historyFile . Close ( ) ; err != nil {
fmt . Printf ( "error closing history file: %s\n" , err )
2015-03-20 21:11:11 +00:00
}
}
2018-06-21 11:07:37 +00:00
t . quittingMutex . Lock ( )
quitting := t . quitting
t . quittingMutex . Unlock ( )
if quitting {
return 0 , nil
}
2015-09-26 12:59:27 +00:00
s , err := t . client . GetState ( )
2015-03-20 21:11:11 +00:00
if err != nil {
2019-12-02 16:16:18 +00:00
if isErrProcessExited ( err ) {
if t . client . IsMulticlient ( ) {
answer , err := yesno ( t . line , "Remote process has exited. Would you like to kill the headless instance? [Y/n] " )
if err != nil {
return 2 , io . EOF
}
if answer {
if err := t . client . Detach ( true ) ; err != nil {
return 1 , err
}
2019-07-17 22:54:15 +00:00
}
2019-12-02 16:16:18 +00:00
return 0 , err
2019-07-17 22:54:15 +00:00
}
2019-12-02 16:16:18 +00:00
return 0 , nil
2019-07-17 22:54:15 +00:00
}
2016-01-10 08:57:52 +00:00
return 1 , err
2015-03-20 21:11:11 +00:00
}
2015-09-26 12:59:27 +00:00
if ! s . Exited {
2018-06-21 11:07:37 +00:00
if t . quitContinue {
err := t . client . Disconnect ( true )
if err != nil {
return 2 , err
}
return 0 , nil
}
doDetach := true
if t . client . IsMulticlient ( ) {
answer , err := yesno ( t . line , "Would you like to kill the headless instance? [Y/n] " )
2015-09-26 12:59:27 +00:00
if err != nil {
2016-01-10 08:57:52 +00:00
return 2 , io . EOF
2015-09-26 12:59:27 +00:00
}
2018-06-21 11:07:37 +00:00
doDetach = answer
2015-09-26 12:59:27 +00:00
}
2018-06-21 11:07:37 +00:00
if doDetach {
kill := true
if t . client . AttachedToExistingProcess ( ) {
answer , err := yesno ( t . line , "Would you like to kill the process? [Y/n] " )
if err != nil {
return 2 , io . EOF
}
kill = answer
}
if err := t . client . Detach ( kill ) ; err != nil {
return 1 , err
}
2015-09-26 12:59:27 +00:00
}
}
2016-01-10 08:57:52 +00:00
return 0 , nil
2015-03-20 21:11:11 +00:00
}
2017-07-29 04:11:06 +00:00
2022-05-18 16:18:28 +00:00
// loadConfig returns an api.LoadConfig with the parameters specified in
2017-07-29 04:11:06 +00:00
// the configuration file.
func ( t * Term ) loadConfig ( ) api . LoadConfig {
2020-03-18 16:25:32 +00:00
r := api . LoadConfig { FollowPointers : true , MaxVariableRecurse : 1 , MaxStringLen : 64 , MaxArrayValues : 64 , MaxStructFields : - 1 }
2017-07-29 04:11:06 +00:00
2018-05-03 16:36:12 +00:00
if t . conf != nil && t . conf . MaxStringLen != nil {
2017-07-29 04:11:06 +00:00
r . MaxStringLen = * t . conf . MaxStringLen
}
2018-05-03 16:36:12 +00:00
if t . conf != nil && t . conf . MaxArrayValues != nil {
2017-07-29 04:11:06 +00:00
r . MaxArrayValues = * t . conf . MaxArrayValues
}
2019-07-23 21:40:35 +00:00
if t . conf != nil && t . conf . MaxVariableRecurse != nil {
r . MaxVariableRecurse = * t . conf . MaxVariableRecurse
}
2017-07-29 04:11:06 +00:00
return r
}
2019-07-17 22:54:15 +00:00
2020-03-19 18:58:40 +00:00
func ( t * Term ) removeDisplay ( n int ) error {
if n < 0 || n >= len ( t . displays ) {
return fmt . Errorf ( "%d is out of range" , n )
}
2021-03-25 16:45:30 +00:00
t . displays [ n ] = displayEntry { "" , "" }
2020-03-19 18:58:40 +00:00
for i := len ( t . displays ) - 1 ; i >= 0 ; i -- {
2021-03-25 16:45:30 +00:00
if t . displays [ i ] . expr != "" {
2020-03-19 18:58:40 +00:00
t . displays = t . displays [ : i + 1 ]
return nil
}
}
t . displays = t . displays [ : 0 ]
return nil
}
2021-03-25 16:45:30 +00:00
func ( t * Term ) addDisplay ( expr , fmtstr string ) {
t . displays = append ( t . displays , displayEntry { expr : expr , fmtstr : fmtstr } )
2020-03-19 18:58:40 +00:00
}
func ( t * Term ) printDisplay ( i int ) {
2021-03-25 16:45:30 +00:00
expr , fmtstr := t . displays [ i ] . expr , t . displays [ i ] . fmtstr
2020-03-19 18:58:40 +00:00
val , err := t . client . EvalVariable ( api . EvalScope { GoroutineID : - 1 } , expr , ShortLoadConfig )
if err != nil {
if isErrProcessExited ( err ) {
return
}
2022-01-27 21:18:25 +00:00
fmt . Fprintf ( t . stdout , "%d: %s = error %v\n" , i , expr , err )
2020-03-19 18:58:40 +00:00
return
}
2022-01-27 21:18:25 +00:00
fmt . Fprintf ( t . stdout , "%d: %s = %s\n" , i , val . Name , val . SinglelineStringFormatted ( fmtstr ) )
2020-03-19 18:58:40 +00:00
}
func ( t * Term ) printDisplays ( ) {
for i := range t . displays {
2021-03-25 16:45:30 +00:00
if t . displays [ i ] . expr != "" {
2020-03-19 18:58:40 +00:00
t . printDisplay ( i )
}
}
}
func ( t * Term ) onStop ( ) {
t . printDisplays ( )
}
2021-01-04 16:54:39 +00:00
func ( t * Term ) longCommandCancel ( ) {
t . longCommandMu . Lock ( )
defer t . longCommandMu . Unlock ( )
t . longCommandCancelFlag = true
}
func ( t * Term ) longCommandStart ( ) {
t . longCommandMu . Lock ( )
defer t . longCommandMu . Unlock ( )
t . longCommandCancelFlag = false
}
func ( t * Term ) longCommandCanceled ( ) bool {
t . longCommandMu . Lock ( )
defer t . longCommandMu . Unlock ( )
return t . longCommandCancelFlag
}
2022-01-27 21:18:25 +00:00
// RedirectTo redirects the output of this terminal to the specified writer.
func ( t * Term ) RedirectTo ( w io . Writer ) {
t . stdout . w = w
}
2019-07-17 22:54:15 +00:00
// isErrProcessExited returns true if `err` is an RPC error equivalent of proc.ErrProcessExited
func isErrProcessExited ( err error ) bool {
rpcError , ok := err . ( rpc . ServerError )
return ok && strings . Contains ( rpcError . Error ( ) , "has exited with status" )
}
2022-01-27 21:18:25 +00:00
// transcriptWriter writes to a io.Writer and also, optionally, to a
// buffered file.
type transcriptWriter struct {
fileOnly bool
w io . Writer
file * bufio . Writer
fh io . Closer
colorEscapes map [ colorize . Style ] string
}
func ( w * transcriptWriter ) Write ( p [ ] byte ) ( nn int , err error ) {
if ! w . fileOnly {
nn , err = w . w . Write ( p )
}
if err == nil {
if w . file != nil {
return w . file . Write ( p )
}
}
return
}
// ColorizePrint prints to out a syntax highlighted version of the text read from
// reader, between lines startLine and endLine.
func ( w * transcriptWriter ) ColorizePrint ( path string , reader io . ReadSeeker , startLine , endLine , arrowLine int ) error {
var err error
if ! w . fileOnly {
err = colorize . Print ( w . w , path , reader , startLine , endLine , arrowLine , w . colorEscapes )
}
if err == nil {
if w . file != nil {
reader . Seek ( 0 , io . SeekStart )
return colorize . Print ( w . file , path , reader , startLine , endLine , arrowLine , nil )
}
}
return err
}
// Echo outputs str only to the optional transcript file.
func ( w * transcriptWriter ) Echo ( str string ) {
if w . file != nil {
w . file . WriteString ( str )
}
}
// Flush flushes the optional transcript file.
func ( w * transcriptWriter ) Flush ( ) {
if w . file != nil {
w . file . Flush ( )
}
}
// CloseTranscript closes the optional transcript file.
func ( w * transcriptWriter ) CloseTranscript ( ) error {
if w . file == nil {
return nil
}
w . file . Flush ( )
w . fileOnly = false
err := w . fh . Close ( )
w . file = nil
w . fh = nil
return err
}
// TranscribeTo starts transcribing the output to the specified file. If
// fileOnly is true the output will only go to the file, output to the
// io.Writer will be suppressed.
func ( w * transcriptWriter ) TranscribeTo ( fh io . WriteCloser , fileOnly bool ) {
if w . file == nil {
w . CloseTranscript ( )
}
w . fh = fh
w . file = bufio . NewWriter ( fh )
w . fileOnly = fileOnly
}