github.com/jmigpin/editor@v1.6.0/core/externalcmd.go (about) 1 package core 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/jmigpin/editor/core/toolbarparser" 12 "github.com/jmigpin/editor/util/iout/iorw" 13 "github.com/jmigpin/editor/util/osutil" 14 "github.com/jmigpin/editor/util/parseutil" 15 ) 16 17 func ExternalCmd(erow *ERow, part *toolbarparser.Part) { 18 env := []string{} 19 env = append(env, toolbarVarsEnv(part)...) 20 cargs := cmdPartArgs(part) 21 22 ExternalCmdFromArgs(erow, cargs, nil, env) 23 } 24 25 func ExternalCmdFromArgs(erow *ERow, cargs []string, fend func(error), env []string) { 26 env = append(env, populateEdEnvVars(erow, cargs)...) 27 28 switch { 29 case erow.Info.IsDir(): 30 externalCmdFromDir(erow, cargs, fend, env) 31 case erow.Info.IsFileButNotDir(): 32 // create a row with the file dir and run the cmd 33 dir := filepath.Dir(erow.Info.Name()) 34 info := erow.Ed.ReadERowInfo(dir) 35 rowPos := erow.Row.PosBelow() 36 erow2 := NewBasicERow(info, rowPos) 37 externalCmdFromDir(erow2, cargs, fend, env) 38 default: 39 erow.Ed.Errorf("unable to run external cmd for erow: %v", erow.Info.Name()) 40 } 41 } 42 43 //---------- 44 45 func externalCmdFromDir(erow *ERow, cargs []string, fend func(error), env []string) { 46 if !erow.Info.IsDir() { 47 panic("not a directory") 48 } 49 erow.Exec.RunAsync(func(ctx context.Context, rw io.ReadWriter) error { 50 err := externalCmdDir2(ctx, erow, cargs, env, rw) 51 if fend != nil { 52 fend(err) 53 } 54 return err 55 }) 56 } 57 58 func externalCmdDir2(ctx context.Context, erow *ERow, cargs []string, env []string, rw io.ReadWriter) error { 59 cmd := osutil.NewCmd(ctx, cargs...) 60 cmd.Dir = erow.Info.Name() 61 cmd.Env = env 62 63 if err := cmd.SetupStdio(rw, rw, rw); err != nil { 64 return err 65 } 66 67 // output pid before any output 68 cmd.PreOutputCallback = func() { 69 cargsStr := strings.Join(cargs, " ") 70 fmt.Fprintf(rw, "# pid %d: %s\n", cmd.Process.Pid, cargsStr) 71 } 72 73 if err := cmd.Start(); err != nil { 74 return err 75 } 76 return cmd.Wait() 77 } 78 79 //---------- 80 //---------- 81 //---------- 82 83 func cmdPartArgs(part *toolbarparser.Part) []string { 84 var u []string 85 for _, a := range part.Args { 86 s := a.String() 87 if !parseutil.IsQuoted(s) { 88 s = parseutil.RemoveEscapesEscapable(s, osutil.EscapeRune, "|") 89 } 90 u = append(u, s) 91 } 92 return osutil.ShellRunArgs(u...) 93 } 94 95 //---------- 96 //---------- 97 //---------- 98 99 func populateEdEnvVars(erow *ERow, cargs []string) []string { 100 // Can't use os.Expand() to replace (and show the values in cargs) since the idea is for the variable to be available in scripting if wanted. 101 102 // supported env vars 103 m := map[string]func() string{ 104 "edName": erow.Info.Name, // filename 105 "edDir": erow.Info.Dir, // directory 106 "edFileOffset": func() string { // filename + offset "filename:#123" 107 return cmdVar_edFileOffset(erow) 108 }, 109 "edFileLine": func() string { // cursor line 110 return cmdVar_edFileLine(erow) 111 }, 112 "edFileWord": func() string { 113 return cmdVar_edFileWord(erow) 114 }, 115 } 116 117 // Deprecated: allow continued usage 118 m["edPosOffset"] = m["edFileOffset"] 119 m["edLine"] = m["edFileLine"] 120 121 // populate env vars only if detected 122 env := os.Environ() 123 for k, v := range m { 124 for _, s := range cargs { 125 if parseutil.DetectEnvVar(s, k) { 126 env = append(env, k+"="+v()) 127 break 128 } 129 } 130 } 131 132 return env 133 } 134 135 func cmdVar_edFileOffset(erow *ERow) string { 136 offset := erow.Row.TextArea.CursorIndex() 137 posOffset := fmt.Sprintf("%v:#%v", erow.Info.Name(), offset) 138 return posOffset 139 } 140 141 func cmdVar_edFileLine(erow *ERow) string { 142 ta := erow.Row.TextArea 143 l, _, err := parseutil.IndexLineColumn(ta.RW(), ta.CursorIndex()) 144 if err != nil { 145 return "" 146 } 147 return fmt.Sprintf("%v", l) 148 } 149 150 func cmdVar_edFileWord(erow *ERow) string { 151 ta := erow.Row.TextArea 152 b, _, err := iorw.WordAtIndex(ta.RW(), ta.CursorIndex()) 153 if err != nil { 154 return "" 155 } 156 return string(b) 157 } 158 159 //---------- 160 161 func toolbarVarsEnv(part *toolbarparser.Part) []string { 162 data2 := *part.Data // copy 163 164 // use data only up to the selected part 165 for k, part2 := range data2.Parts { 166 if part2 == part { 167 data2.Parts = data2.Parts[:k+1] 168 break 169 } 170 } 171 172 env := []string{} 173 vmap := toolbarparser.ParseVars(&data2) 174 for k, v := range vmap { 175 if strings.HasPrefix(k, "$") { 176 u := k[1:] 177 env = append(env, u+"="+v) 178 } 179 } 180 return env 181 }