This commit is contained in:
Casual 2024-05-11 04:09:51 +03:00
parent beb47854d6
commit 7f7ae26f4e
9 changed files with 472 additions and 100 deletions

View File

@ -1,3 +1,3 @@
# Procurator
My Git project manager TUI
My Git project manager TUI

8
go.mod
View File

@ -3,12 +3,17 @@ module git.sual.in/casual/Procurator
go 1.22.2
require (
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.26.1
github.com/charmbracelet/lipgloss v0.10.0
github.com/fsnotify/fsnotify v1.7.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/projectdiscovery/goflags v0.1.50
)
require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
@ -27,7 +32,8 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/projectdiscovery/blackrock v0.0.1 // indirect
github.com/projectdiscovery/utils v0.0.90 // indirect
github.com/rivo/uniseg v0.4.6 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/tidwall/gjson v1.14.3 // indirect
github.com/tidwall/match v1.1.1 // indirect

18
go.sum
View File

@ -1,19 +1,31 @@
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.26.1 h1:xujcQeF73rh4jwu3+zhfQsvV18x+7zIjlw7/CYbzGJ0=
github.com/charmbracelet/bubbletea v0.26.1/go.mod h1:FzKr7sKoO8iFVcdIBM9J0sJOcQv5nDQaYwsee3kpbgo=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
@ -47,8 +59,10 @@ github.com/projectdiscovery/utils v0.0.90 h1:ZBClkrtC6U2xuT8AI6/MrYgm3cxQnwSZmM8
github.com/projectdiscovery/utils v0.0.90/go.mod h1:laZCMcUTT1vTmclUCW+zpi/fjeYsgU1gMwgv2CxNcRk=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=

34
main.go
View File

@ -9,33 +9,25 @@ import (
)
var waitGroup sync.WaitGroup
var TUI *tea.Program
func main() {
options, err := ParseOptions()
_, err := ParseOptions()
// options, err := ParseOptions()
errHandler(err, "Error parsing options")
fmt.Println("hello world", options)
// // start workers in parallel
// for i := 0; i < options.Threads; i++ {
// waitGroup.Add(1)
// go func() {
// fmt.Println("do parallel stuff")
//
// defer waitGroup.Done()
// }()
// }
// waitGroup.Wait()
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
fmt.Printf("Alas, there's been an error: %v", err)
os.Exit(1)
}
model := initialModel()
TUI = tea.NewProgram(
&model,
tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer"
// tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel
)
go watchFilesystem()
if _, err := TUI.Run(); err != nil {
errHandler(err, "Tui error:")
}
}
func errHandler(err error, message string) {

View File

@ -24,9 +24,9 @@ func ParseOptions() (*Options, error) {
flagSet.SetDescription("Procurator - your little Git TUI")
// flagSet.CreateGroup("input", "Input",
// flagSet.StringVarP(&options.URL, "u", "url", "", "target's url"),
// flagSet.IntVarP(&options.Threads, "t", "threads", 10, "threads to run"), //TODO add estimate counter to packets/s
// flagSet.StringVarP(&options.URL, "u", "url", "", "verbose"),
// flagSet.StringVarP(&options.URL, "u", "url", "", "target's url"),
// flagSet.IntVarP(&options.Threads, "t", "threads", 10, "threads to run"), //TODO add estimate counter to packets/s
// flagSet.StringVarP(&options.URL, "u", "url", "", "verbose"),
// )
_ = flagSet.Parse()

223
tui.go
View File

@ -2,104 +2,183 @@ package main
import (
"fmt"
// "os"
// "path/filepath"
// "github.com/kardianos/osext"
// "unicode"
// "strings"
// "time"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
// "github.com/fsnotify/fsnotify"
)
var skipNextViewportUpdate bool
type model struct {
choices []string // items on the to-do list
cursor int // which to-do list item our cursor is pointing at
selected map[int]struct{} // which to-do items are selected
viewport viewport.Model
ready bool
// choices []string // items on the to-do list
list list.Model
// choice string
quitting bool
// cursor int // which to-do list item our cursor is pointing at
// selected map[int]struct{} // which to-do items are selected
}
func initialModel() model {
items := []list.Item{
item("fmt code"),
item("git add ."),
// item("git commit"), TODO
item("git push"),
// item("add to gitignore"), TODO
// item("go mod init github_repo"), TODO
// item("remove file from git history"), TODO
}
l := list.New(items, itemDelegate{}, defaultWidth, listHeight)
// l.Title = "What do you want for dinner?"
l.SetShowStatusBar(false)
l.SetShowTitle(false)
// l.SetShowStatusBar(true)
l.SetFilteringEnabled(false)
l.Styles.Title = titleStyle
l.Styles.PaginationStyle = paginationStyle
l.Styles.HelpStyle = helpStyle
return model{
// Our to-do list is a grocery list
choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
// choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
// A map which indicates which choices are selected. We're using
// the map like a mathematical set. The keys refer to the indexes
// of the `choices` slice, above.
selected: make(map[int]struct{}),
// selected: make(map[int]struct{}),
list: l,
}
}
func (m model) Init() tea.Cmd {
// Just return `nil`, which means "no I/O right now, please."
return nil
type dataMsg struct {
// reflex string
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
// Is it a key press?
case tea.KeyMsg:
// Cool, what was the actual key pressed?
switch msg.String() {
// These keys should exit the program.
case "ctrl+c", "q":
return m, tea.Quit
// The "up" and "k" keys move the cursor up
case "up", "k":
if m.cursor > 0 {
m.cursor--
}
// The "down" and "j" keys move the cursor down
case "down", "j":
if m.cursor < len(m.choices)-1 {
m.cursor++
}
// The "enter" key and the spacebar (a literal space) toggle
// the selected state for the item that the cursor is pointing at.
case "enter", " ":
_, ok := m.selected[m.cursor]
if ok {
delete(m.selected, m.cursor)
} else {
m.selected[m.cursor] = struct{}{}
}
}
}
// Return the updated model to the Bubble Tea runtime for processing.
// Note that we're not returning a command.
return m, nil
func (m *model) Init() tea.Cmd {
// Just return `nil`, which means "no I/O right now, please."
// return m.reflex("")
return nil
}
func (m model) View() string {
// The header
s := "What should we buy at the market?\n\n"
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// var (
// // cmd tea.Cmd
// cmds []tea.Cmd
// )
// Iterate over our choices
for i, choice := range m.choices {
switch msg := msg.(type) {
// Is the cursor pointing at this choice?
cursor := " " // no cursor
if m.cursor == i {
cursor = ">" // cursor!
}
// Is it a key press?
case tea.KeyMsg:
// Is this choice selected?
checked := " " // not selected
if _, ok := m.selected[i]; ok {
checked = "x" // selected!
}
// Cool, what was the actual key pressed?
switch msg.String() {
// Render the row
s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
}
case "ctrl+c", "q":
return m, tea.Quit
// The footer
s += "\nPress q to quit.\n"
// // The "up" and "k" keys move the cursor up
// case "up", "k":
// if m.cursor > 0 {
// m.cursor--
// }
//
// // The "down" and "j" keys move the cursor down
// case "down", "j":
// if m.cursor < len(m.choices)-1 {
// m.cursor++
// }
// The "enter" key and the spacebar (a literal space) toggle
// the selected state for the item that the cursor is pointing at.
case "enter", " ":
i, ok := m.list.SelectedItem().(item)
if ok {
// m.choice = string(i)
skipNextViewportUpdate = true
m.viewport.SetContent("[Running...]")
m.viewport.SetContent(choiceAction(string(i)))
m.viewport.GotoBottom()
}
}
case tea.WindowSizeMsg:
headerHeight := lipgloss.Height(m.headerView())
footerHeight := lipgloss.Height(m.footerView())
verticalMarginHeight := headerHeight + footerHeight
m.list.SetWidth(msg.Width)
m.list.SetHeight(msg.Height / 2)
if !m.ready {
// Since this program is using the full size of the viewport we
// need to wait until we've received the window dimensions before
// we can initialize the viewport. The initial dimensions come in
// quickly, though asynchronously, which is why we wait for them
// here.
m.viewport = viewport.New(msg.Width, msg.Height/2-verticalMarginHeight)
m.viewport.YPosition = headerHeight
m.viewport.HighPerformanceRendering = useHighPerformanceRenderer
m.viewport.SetContent("[READY]")
m.ready = true
// This is only necessary for high performance rendering, which in
// most cases you won't need.
//
// Render the viewport one line below the header.
m.viewport.YPosition = headerHeight + 1
} else {
m.viewport.Width = msg.Width
m.viewport.Height = msg.Height/2 - verticalMarginHeight
}
// if useHighPerformanceRenderer {
// // Render (or re-render) the whole viewport. Necessary both to
// // initialize the viewport and when the window is resized.
// //
// // This is needed for high-performance rendering only.
// cmds = append(cmds, viewport.Sync(m.viewport))
// }
case fileUpdated:
if !skipNextViewportUpdate {
m.viewport.SetContent("[Running...]")
m.viewport.SetContent(watchRun())
m.viewport.GotoBottom()
return m, nil
}
skipNextViewportUpdate = false
}
// Return the updated model to the Bubble Tea runtime for processing.
// Note that we're not returning a command.
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
// return m, nil
}
func (m *model) View() string {
if !m.ready {
return "\n Initializing..."
}
// return fmt.Sprintf("%s\n%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView(), m.choicesView())
// TODO if window too small, hide list.View
return fmt.Sprintf("%s\n%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView(), m.list.View())
// Send the UI for rendering
return s
}

126
tui_choices.go Normal file
View File

@ -0,0 +1,126 @@
package main
// https://github.com/charmbracelet/bubbletea/blob/master/examples/list-simple/main.go
import (
"fmt"
"io"
"os/exec"
"strings"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type item string
func (i item) FilterValue() string { return "" }
type itemDelegate struct{}
const listHeight = 14
const defaultWidth = 20
func (d itemDelegate) Height() int { return 1 }
func (d itemDelegate) Spacing() int { return 0 }
func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
i, ok := listItem.(item)
if !ok {
return
}
str := fmt.Sprintf("%d. %s", index+1, i)
fn := itemStyle.Render
if index == m.Index() {
fn = func(s ...string) string {
return selectedItemStyle.Render("> " + strings.Join(s, " "))
}
}
fmt.Fprint(w, fn(str))
}
var (
// titleStyle = lipgloss.NewStyle().MarginLeft(2)
itemStyle = lipgloss.NewStyle().PaddingLeft(4)
selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
)
// func (m *model) choicesView() string {
// // The header
// s := "What should we buy at the market?\n\n"
//
// // Iterate over our choices
// for i, choice := range m.choices {
//
// // Is the cursor pointing at this choice?
// cursor := " " // no cursor
// if m.cursor == i {
// cursor = ">" // cursor!
// }
//
// // Is this choice selected?
// checked := " " // not selected
// if _, ok := m.selected[i]; ok {
// checked = "x" // selected!
// }
//
// // Render the row
// s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
// }
//
// // The footer
// s += "\nPress q to quit.\n"
//
// // Send the UI for rendering
// // return s
// return s
// }
//
// type errorRunningCommand string
// const errorMsg=""
func choiceAction(input string) string {
switch input {
case "fmt code":
// TODO if go
output, err := exec.Command("go", "fmt").CombinedOutput()
if err != nil {
// var errorRnCmd errorRunningCommand
// errorRnCmd = errorMsg+string(output)
return "[ERROR] - executing command failed\n" + string(output)
// tui.Send(errorRnCmd)
}
return "[OK]\n" + string(output)
case "git add .":
output, err := exec.Command("git", "add", ".").CombinedOutput()
if err != nil {
return "[ERROR] - executing command failed\n" + string(output)
}
return "[OK]\n" + string(output)
case "git commit":
// TODO add input message box
case "git push":
output, err := exec.Command("git", "push").CombinedOutput()
if err != nil {
return "[ERROR] - executing command failed\n" + string(output)
}
return "[OK]\n" + string(output)
case "add file to gitignore":
// TODO add input message box with option to choose file or import filter
case "go mod init github_repo":
// TODO add input message box OR add fetching link via .git
case "remove file from git history":
// TODO add file chooser
default:
return "[ERROR] - unexisting option"
}
return "[ERROR] - unexisting option"
}

68
tui_viewport.go Normal file
View File

@ -0,0 +1,68 @@
package main
import (
// "fmt"
// "os"
// "path/filepath"
"github.com/kardianos/osext"
// "unicode"
"strings"
// "time"
// tea "github.com/charmbracelet/bubbletea"
// "github.com/charmbracelet/bubbles/viewport"
"github.com/charmbracelet/lipgloss"
// "github.com/fsnotify/fsnotify"
)
const useHighPerformanceRenderer = false
var (
titleStyle = func() lipgloss.Style {
b := lipgloss.RoundedBorder()
b.Right = "├"
return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)
}()
infoStyle = func() lipgloss.Style {
b := lipgloss.RoundedBorder()
b.Left = "┤"
return titleStyle.Copy().BorderStyle(b)
}()
)
func (m *model) headerView() string {
title := titleStyle.Render("Procurator - " + getCurrentFolder())
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title)))
return lipgloss.JoinHorizontal(lipgloss.Center, title, line)
}
func (m *model) footerView() string {
// info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100))
line := strings.Repeat("─", max(0, m.viewport.Width))
return lipgloss.JoinHorizontal(lipgloss.Center, line)
}
func getCurrentFolder() string {
folderPath, err := osext.ExecutableFolder()
// folderPath, err := filepath.Abs(filepath.Dir(os.Args[0]))
folders := strings.Split(folderPath, "/")
if err != nil {
errHandler(err, "Can't get current folder")
}
capitalizedCurFolder := folders[len(folders)-1]
capitalizedCurFolder = strings.ToUpper(capitalizedCurFolder[:1]) + capitalizedCurFolder[1:]
return capitalizedCurFolder
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
///////////

87
watchFilesystem.go Normal file
View File

@ -0,0 +1,87 @@
package main
// It's not an actual reflex (https://github.com/cespare/reflex). I just wrote some code as for reflex and couldn't manage to adapt it fast before moving to creating own solution
import (
// "errors"
// // "flag"
// "fmt"
// "io/ioutil"
// "log"
// "os"
// "os/exec"
// "path"
// "reflect"
// "regexp"
// "strconv"
// // "strings"
// "syscall"
// "time"
// "bytes"
"log"
"os/exec"
"github.com/fsnotify/fsnotify"
// tea "github.com/charmbracelet/bubbletea"
)
type fileUpdated bool
// func watchFilesystem(tui *tea.Program) {
func watchFilesystem() {
// Create new watcher.
watcher, err := fsnotify.NewWatcher()
if err != nil {
errHandler(err, "Can't setup filesystem watcher")
}
defer watcher.Close()
// Start listening for events.
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
// log.Println("event:", event)
if event.Has(fsnotify.Write) {
// log.Println("modified file:", event.Name)
var fileUpdated1 fileUpdated
TUI.Send(fileUpdated1)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
// Add a current path.
err = watcher.Add(".")
if err != nil {
errHandler(err, "Filesystem Watcher - Can't add current dir:")
}
// Block main goroutine forever.
<-make(chan struct{})
}
func watchRun() string {
//TODO if edited .go file
return GoVet()
}
func GoVet() string {
out1, _ := exec.Command("go", "mod", "tidy").CombinedOutput()
out2, _ := exec.Command("go", "vet").CombinedOutput()
output := string(out1) + string(out2)
if output == "" {
output = "[BUILD OK]"
}
return output
}