github.com/jmigpin/editor@v1.6.0/core/internalcmd.go (about) 1 package core 2 3 import ( 4 "context" 5 "path/filepath" 6 "strings" 7 8 "github.com/jmigpin/editor/core/toolbarparser" 9 "github.com/jmigpin/editor/ui" 10 "github.com/jmigpin/editor/util/uiutil/widget" 11 ) 12 13 //---------- 14 15 type InternalCmd struct { 16 Name string 17 Fn InternalCmdFn 18 NeedsERow bool 19 Detach bool // run outside UI goroutine (care must be taken) 20 } 21 22 type InternalCmdFn func(args *InternalCmdArgs) error 23 24 type InternalCmdArgs struct { 25 Ctx context.Context 26 Ed *Editor 27 ERow *ERow // could be nil 28 Part *toolbarparser.Part 29 } 30 31 //---------- 32 33 // cmds added via init() from "internalcmds" pkg 34 var InternalCmds = internalCmds{} 35 36 type internalCmds map[string]*InternalCmd 37 38 func (ic *internalCmds) Set(cmd *InternalCmd) { 39 (*ic)[cmd.Name] = cmd 40 } 41 42 //---------- 43 44 func InternalCmdFromRootTb(ed *Editor, tb *ui.Toolbar) { 45 tbdata := toolbarparser.Parse(tb.Str()) 46 part, ok := tbdata.PartAtIndex(int(tb.CursorIndex())) 47 if !ok { 48 ed.Errorf("missing part at index") 49 return 50 } 51 if len(part.Args) == 0 { 52 ed.Errorf("part at index has no args") 53 return 54 } 55 56 internalCmd(ed, part, nil) 57 } 58 59 //---------- 60 61 func InternalCmdFromRowTb(erow *ERow) { 62 part, ok := erow.TbData.PartAtIndex(int(erow.Row.Toolbar.CursorIndex())) 63 if !ok { 64 erow.Ed.Errorf("missing part at index") 65 return 66 } 67 if len(part.Args) == 0 { 68 erow.Ed.Errorf("part at index has no args") 69 return 70 } 71 72 // first part cmd 73 if part == erow.TbData.Parts[0] { 74 if !internalCmdFromRowTbFirstPart(erow, part) { 75 erow.Ed.Errorf("no cmd was run") 76 } 77 return 78 } 79 80 internalCmd(erow.Ed, part, erow) 81 } 82 83 func internalCmdFromRowTbFirstPart(erow *ERow, part *toolbarparser.Part) bool { 84 a0 := part.Args[0] 85 ci := erow.Row.Toolbar.CursorIndex() 86 87 // cursor index beyond arg0 88 if ci > a0.End() { 89 return false 90 } 91 92 // get path up to cursor index 93 a0ci := ci - a0.Pos() 94 filename := a0.String() 95 i := strings.Index(filename[a0ci:], string(filepath.Separator)) 96 if i >= 0 { 97 filename = filename[:a0ci+i] 98 } 99 100 // decode filename 101 filename = erow.Ed.HomeVars.Decode(filename) 102 103 // create new row 104 info := erow.Ed.ReadERowInfo(filename) 105 erow2, err := NewLoadedERow(info, erow.Row.PosBelow()) 106 if err != nil { 107 erow.Ed.Error(err) 108 return true 109 } 110 111 erow2.Flash() 112 113 // set same offset if not dir 114 if erow2.Info.IsFileButNotDir() { 115 ta := erow.Row.TextArea 116 ta2 := erow2.Row.TextArea 117 ta2.SetCursorIndex(ta.CursorIndex()) 118 ta2.SetRuneOffset(ta.RuneOffset()) 119 } 120 121 return true 122 } 123 124 //---------- 125 126 // erow can be nil (ex: a root toolbar cmd) 127 func internalCmd(ed *Editor, part *toolbarparser.Part, erow *ERow) { 128 arg0 := part.Args[0].UnquotedString() 129 noERowErr := func() { 130 ed.Errorf("%s: no active row", arg0) 131 } 132 133 // util functions 134 135 currentERow := func() *ERow { 136 if erow != nil { 137 return erow 138 } 139 e, ok := ed.ActiveERow() 140 if ok { 141 return e 142 } 143 return nil 144 } 145 run := func(detach bool, node widget.Node, fn func()) { 146 if detach { 147 ed.RunAsyncBusyCursor(node, func(done func()) { 148 defer done() 149 fn() 150 }) 151 } else { 152 fn() 153 } 154 } 155 156 curERow := currentERow() // possibly != erow, could be nil 157 158 // internal cmds 159 cmd, ok := InternalCmds[arg0] 160 if ok { 161 ctx := context.Background() // TODO: editor ctx 162 args := &InternalCmdArgs{ctx, ed, curERow, part} 163 if cmd.NeedsERow && args.ERow == nil { 164 noERowErr() 165 return 166 } 167 168 // feedback node 169 node := widget.Node(ed.UI.Root) 170 if erow != nil && args.ERow == erow { 171 node = erow.Row 172 } 173 174 run(cmd.Detach, node, func() { 175 if cmd.NeedsERow { 176 ctx, cancel := args.ERow.newInternalCmdCtx() 177 defer cancel() 178 args.Ctx = ctx 179 } 180 if err := cmd.Fn(args); err != nil { 181 ed.Errorf("%v: %v", arg0, err) 182 } 183 }) 184 return 185 } 186 187 // have a plugin handle the cmd 188 handled := ed.Plugins.RunToolbarCmd(curERow, part) 189 if handled { 190 return 191 } 192 193 // run external cmd (needs erow) 194 if curERow == nil { 195 noERowErr() 196 return 197 } 198 run(false, curERow.Row, func() { 199 ExternalCmd(curERow, part) 200 }) 201 }