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  }