github.com/swaros/contxt/module/taskrun@v0.0.0-20240305083542-3dbd4436ac40/interactive.go (about) 1 // MIT License 2 // 3 // Copyright (c) 2020 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved. 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the Software), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 // 12 // The above copyright notice and this permission notice shall be included in all 13 // copies or substantial portions of the Software. 14 // 15 // THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 // SOFTWARE. 22 23 // AINC-NOTE-0815 24 25 package taskrun 26 27 import ( 28 "fmt" 29 "os" 30 31 "github.com/gdamore/tcell/v2" 32 "github.com/rivo/tview" 33 "github.com/spf13/cobra" 34 "github.com/swaros/contxt/module/configure" 35 "github.com/swaros/contxt/module/dirhandle" 36 "github.com/swaros/contxt/module/systools" 37 "github.com/swaros/manout" 38 ) 39 40 type CtxUi struct { 41 title string 42 app *tview.Application 43 pages *tview.Pages 44 menu *tview.List 45 cmd *cobra.Command 46 outscr *tview.TextView 47 args []string 48 LogOutMessage string 49 mainScr *tview.TextView 50 statusScr *tview.TextView 51 taskScr *tview.TextView 52 selectedtarget string 53 targetCtrl *tview.Form 54 pathList *tview.List 55 } 56 57 func InitWindow(cmd *cobra.Command, args []string) (*CtxUi, error) { 58 app := tview.NewApplication() 59 pages := tview.NewPages() 60 ui := &CtxUi{ 61 title: "con.txt", 62 app: app, 63 pages: pages, 64 cmd: cmd, 65 args: args, 66 } 67 // the main window 68 69 // create the main menu 70 menu := ui.createMenu() 71 menu.SetBorder(true) 72 73 status := tview.NewTextView() 74 status.SetText(ui.createStautsText()) 75 status.SetBorder(true) 76 status.SetDynamicColors(true) 77 ui.statusScr = status 78 79 mainWindow := tview.NewTextView() 80 mainWindow.SetBorder(true) 81 mainWindow.SetDynamicColors(true) 82 83 ui.mainScr = mainWindow 84 85 flex := tview.NewFlex(). 86 AddItem(menu, 0, 1, true). 87 AddItem(tview.NewFlex().SetDirection(tview.FlexRow). 88 AddItem(status, 0, 1, false). 89 AddItem(mainWindow, 0, 5, false), 0, 3, false) 90 91 pages.AddPage("main", flex, true, true) 92 93 stat := ui.createHeaderText() 94 95 frame := tview.NewFrame(pages) 96 frame.SetBorders(1, 1, 1, 1, 0, 0). 97 AddText(stat, true, tview.AlignCenter, tcell.ColorWhite). 98 AddText(configure.GetVersion()+" "+configure.GetBuild()+" "+configure.GetOs(), false, tview.AlignCenter, tcell.ColorWhite) 99 100 frame.SetBackgroundColor(tcell.ColorGray) 101 102 // define the root element 103 app.SetRoot(frame, true).EnableMouse(true) 104 105 ui.startCapture() 106 // register exist trigger to get the app closed before 107 systools.AddExitListener("interactive", func(exitCode int) systools.ExitBehavior { 108 app.Stop() 109 return systools.Continue 110 }) 111 if err := app.Run(); err != nil { 112 return ui, err 113 } 114 115 return ui, nil 116 } 117 118 func (ui *CtxUi) createHeaderText() string { 119 path := "" 120 if configure.GetGlobalConfig().UsedV2Config.CurrentSet != "" { 121 path = configure.GetGlobalConfig().GetActivePath("") 122 123 dir, err := dirhandle.Current() 124 if err != nil { 125 return "[red]" + err.Error() 126 } 127 128 if dir != path { 129 path = "[red]" + path + "[white](we are not in this path)" 130 } 131 } 132 header := "[blue]WORKSPACE [yellow]" + configure.GetGlobalConfig().UsedV2Config.CurrentSet + " [blue]current active dir[yellow] " + path 133 return header 134 } 135 136 func (ui *CtxUi) createStautsText() string { 137 template, path, exists, err := GetTemplate() 138 if err != nil { 139 return err.Error() 140 } 141 if !exists { 142 return "[yellow]no template in this location." 143 } 144 145 status := "[blue]path [yellow]" + path + " [blue]version[yellow] " + template.Version 146 return status 147 } 148 149 func (ui *CtxUi) UpdateAll() { 150 151 } 152 153 func (ui *CtxUi) UpdatePathList() { 154 ui.pathList.Clear() 155 ui.pathList.AddItem("[blue]<<< [green]BACK", "", 'x', func() { 156 ui.pages.SendToBack("paths") 157 }) 158 159 configure.GetGlobalConfig().PathWorkerNoCd(func(index string, name string) { 160 161 ui.pathList.AddItem(name, "", rune(index[0]), nil) 162 }) 163 } 164 165 // createMenu creates the main menu as a default tview.List 166 func (ui *CtxUi) createMenu() *tview.List { 167 168 menu := tview.NewList().AddItem("Task", "task in the current path", 't', func() { 169 ui.pages.SendToFront("target") 170 }).AddItem("Workspaces", "change workspaces", 'w', func() { 171 ui.pages.SendToFront("workspace") 172 }).AddItem("Paths", "change to path in workspace", 'w', func() { 173 ui.pages.SendToFront("paths") 174 }).AddItem("quit", "exit this application", 'q', func() { 175 ui.app.Stop() 176 }) 177 ui.pages.AddPage("target", ui.CreateRunPage(), true, true) 178 ui.pages.AddPage("workspace", ui.CreateWorkSpacePage(), true, true) 179 ui.pages.AddPage("paths", ui.CreatePathSelectPage(), true, true) 180 //CreatePathSelectPage 181 menu.SetHighlightFullLine(true) 182 ui.menu = menu 183 return menu 184 } 185 186 // FilterOutPut parses the content and handles 187 // all interface depending the Type differently 188 func (ui *CtxUi) FilterOutPut(caseHandle func(target string, msg []interface{}), msg ...interface{}) []interface{} { 189 var newMsh []interface{} // new hash for the output 190 haveTarget := "" 191 for _, chk := range msg { 192 switch v := chk.(type) { 193 case CtxOutCtrl: 194 if chk.(CtxOutCtrl).IgnoreCase { // if we have found this flag set to true, it means ignore the message 195 return newMsh 196 } 197 continue 198 case CtxOutLabel: 199 newMsh = append(newMsh, manout.Message(v.Message)) 200 continue 201 case CtxTargetOut: 202 haveTarget = v.Target 203 newMsh = append(newMsh, v.Target) 204 default: 205 newMsh = append(newMsh, chk) 206 } 207 208 } 209 if haveTarget != "" { 210 caseHandle(haveTarget, newMsh) 211 var dwMsh []interface{} 212 return dwMsh 213 } 214 return newMsh 215 } 216 217 // startCapture set up the output capturing. 218 // it is also the method that is the "tick" 219 // because it will be triggered on statusmessage 220 // So this is the place to update all components 221 func (ui *CtxUi) startCapture() { 222 // we set the PreHook so any Message that is send to 223 // CtxOut will be handled from now on by this function 224 PreHook = func(msg ...interface{}) bool { 225 msg = ui.FilterOutPut(func(target string, msg []interface{}) { 226 if ui.outscr != nil { 227 byte4main := []byte(tview.TranslateANSI(fmt.Sprintln(msg...))) 228 ui.outscr.Write(byte4main) 229 } 230 }, msg...) // filter output depending types of the content 231 232 if len(msg) > 0 && ui.mainScr != nil { 233 byteData := []byte(tview.TranslateANSI(fmt.Sprintln(msg...))) 234 ui.mainScr.Write(byteData) 235 236 if ui.outscr != nil { 237 byte4main := []byte(tview.TranslateANSI(fmt.Sprintln(msg...))) 238 ui.outscr.Write(byte4main) 239 } 240 } 241 242 return true 243 } 244 CtxOut("running target") 245 } 246 247 func (ui *CtxUi) updateTaskView() { 248 if ui.selectedtarget != "" && ui.taskScr != nil { 249 ui.taskScr.SetText(ui.selectedtarget) 250 251 } 252 253 if ui.targetCtrl != nil { 254 ui.targetCtrl.Clear(true) 255 if ui.selectedtarget != "" { 256 ui.targetCtrl.AddButton("Start "+ui.selectedtarget, func() { 257 go RunTargets(ui.selectedtarget, true) 258 }) 259 } 260 } 261 } 262 263 func (ui *CtxUi) CreateWorkSpacePage() *tview.Flex { 264 265 uiWsList := tview.NewList() 266 uiWsList.AddItem("[blue]<<< [green]BACK", "", 'x', func() { 267 ui.pages.SendToBack("workspace") 268 }) 269 configure.GetGlobalConfig().ExecOnWorkSpaces(func(index string, cfg configure.ConfigurationV2) { 270 uiWsList.AddItem(index, "", rune(index[0]), nil) 271 }) 272 273 uiWsList.SetHighlightFullLine(true) 274 uiWsList.ShowSecondaryText(false) 275 uiWsList.SetSelectedFunc(func(i int, s1, s2 string, r rune) { 276 doMagicParamOne(s1) 277 ui.UpdateAll() 278 ui.pages.SendToBack("workspace") 279 ui.updateTaskView() 280 }) 281 wsflex := tview.NewFlex().AddItem(uiWsList, 0, 1, true) 282 return wsflex 283 } 284 285 func (ui *CtxUi) CreatePathSelectPage() *tview.Flex { 286 287 ui.pathList = tview.NewList() 288 ui.UpdatePathList() 289 ui.pathList.SetHighlightFullLine(true) 290 ui.pathList.ShowSecondaryText(false) 291 ui.pathList.SetSelectedFunc(func(i int, s1, s2 string, r rune) { 292 //doMagicParamOne(s1) 293 294 configure.GetGlobalConfig().PathWorkerNoCd(func(index string, path string) { 295 if path == s1 { 296 configure.GetGlobalConfig().ChangeActivePath(index) 297 os.Chdir(path) 298 } 299 }) 300 ui.pages.SendToBack("paths") 301 }) 302 wsflex := tview.NewFlex().AddItem(ui.pathList, 0, 1, true) 303 return wsflex 304 } 305 306 // CreateRunPage builds the page that contains different elements 307 // to inspect and run the targets 308 func (ui *CtxUi) CreateRunPage() *tview.Flex { 309 // this uiTaskList contains any target and we use them as a menu 310 311 uiTaskList := tview.NewList() 312 uiTaskList.AddItem("[blue]<<< [green]BACK", "", 'x', func() { 313 ui.pages.SendToBack("target") 314 }) 315 var keyList string = "abcdefghijklmnopqrstuvwyz1234567890" // shortcuts definition 316 if targets, ok := GetAllTargets(); ok { // get all targets 317 for index, target := range targets { 318 if index <= len(keyList) { // we just print targets until we have chars to map 319 uiTaskList.AddItem(target, "", rune(keyList[index]), nil) // add the target as listitem 320 } 321 } 322 } 323 uiTaskList.SetHighlightFullLine(true) 324 uiTaskList.SetSelectedFunc(func(i int, target, s2 string, r rune) { 325 if r != 'x' { // we ignore the get-back button 326 if ui.outscr != nil { 327 ui.outscr.Clear() 328 } 329 ui.selectedtarget = target 330 go RunTargets(target, true) 331 } else { 332 ui.selectedtarget = "" 333 } 334 }) 335 336 uiTaskList.SetBorder(true) 337 uiTaskList.SetTitle("select target") 338 uiTaskList.ShowSecondaryText(false) 339 uiTaskList.SetChangedFunc(func(index int, target, emptyAnyway string, shortcut rune) { 340 if shortcut != 'x' { // ignore get back option 341 ui.selectedtarget = target 342 ui.updateTaskView() 343 } 344 }) 345 346 // create the log output 347 output := tview.NewTextView(). 348 SetDynamicColors(true). 349 SetChangedFunc(func() { 350 ui.app.Draw() 351 }) 352 output.SetBorder(true).SetTitle("log") 353 ui.outscr = output 354 355 // create a target overview 356 targetControl := tview.NewForm() 357 targetControl.SetBorder(true) 358 ui.targetCtrl = targetControl 359 360 // left side we have the list of task 361 // and the form that we use to start a task 362 leftCtrl := tview.NewFlex().SetDirection(tview.FlexRow) 363 leftCtrl.AddItem(uiTaskList, 0, 6, true). 364 AddItem(targetControl, 0, 1, false) 365 // this is the task overview where 366 // we display the current status of the task 367 targetView := tview.NewTextView() 368 targetView.SetDynamicColors(true).SetBorder(true) 369 ui.taskScr = targetView 370 371 // the right site of the page contains 372 // the target overview and the log output 373 rightCtrl := tview.NewFlex().SetDirection(tview.FlexRow) 374 rightCtrl.AddItem(targetView, 0, 1, false). 375 AddItem(output, 0, 1, false) 376 377 // compose the page content 378 targetflex := tview.NewFlex(). 379 AddItem(leftCtrl, 0, 1, true). 380 AddItem(rightCtrl, 0, 4, false) 381 return targetflex 382 383 }