diff --git a/core/ConfigDir.go b/core/ConfigDir.go new file mode 100644 index 0000000..f9d54a0 --- /dev/null +++ b/core/ConfigDir.go @@ -0,0 +1,22 @@ +package core + +import ( + "os" + "path/filepath" +) + +func ConfigDir() string { + home := os.Getenv("XDG_CONFIG_HOME") + + if home != "" { + return filepath.Join(home, "dash") + } + + home, err := os.UserHomeDir() + + if err != nil { + panic(err) + } + + return filepath.Join(home, ".config", "dash") +} diff --git a/core/Process.go b/core/Process.go new file mode 100644 index 0000000..671270b --- /dev/null +++ b/core/Process.go @@ -0,0 +1,28 @@ +package core + +import ( + "os/exec" + "regexp" + "strings" +) + +var ( + sh = regexp.MustCompile("\\{([^\\}]+)\\}") +) + +func Process(source string) string { + source = sh.ReplaceAllStringFunc(source, func(match string) string { + cmd := sh.FindStringSubmatch(match)[1] + args := strings.Fields(cmd) + shellArgs := []string{"-c", strings.Join(args, " ")} + output, err := exec.Command("sh", shellArgs...).CombinedOutput() + + if err != nil { + return err.Error() + } + + return strings.Trim(string(output), "\n") + }) + + return source +} diff --git a/editor/editor.go b/editor/editor.go index cd045ed..5e2c157 100644 --- a/editor/editor.go +++ b/editor/editor.go @@ -2,60 +2,155 @@ package editor import ( "os" - "path/filepath" + "git.akyoto.dev/cli/dash/core" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) -func New(app *tview.Application) *tview.TextArea { - view := tview.NewTextArea() - view.SetBackgroundColor(tcell.ColorDefault) - view.SetTextStyle(tcell.StyleDefault) - view.SetBorderPadding(0, 0, 1, 1) - source := "Having been erased,\nThe document you're seeking\nMust now be retyped." +type Editor struct { + Pages *tview.Pages + Source *tview.TextArea + Result *tview.TextView + FilePath string + IsDirty bool +} - home, err := os.UserHomeDir() - - if err != nil { - panic(err) +func New(app *tview.Application, filePath string) *Editor { + e := &Editor{ + FilePath: filePath, } - data, err := os.ReadFile(filepath.Join(home, ".dash")) + e.Pages = tview.NewPages() + e.Pages.SetBackgroundColor(tcell.ColorDefault) - if err == nil { - source = string(data) + source := e.newSourceEditor() + result := e.newResult() + + e.Pages.AddPage("Source", source, true, false) + e.Pages.AddPage("Result", result, true, true) + + return e +} + +func (e *Editor) toggle() { + front, _ := e.Pages.GetFrontPage() + + switch front { + case "Source": + e.Pages.SwitchToPage("Result") + case "Result": + e.Pages.SwitchToPage("Source") } +} - view.SetText(source, true) +func (e *Editor) newSourceEditor() *tview.TextArea { + e.Source = tview.NewTextArea() + e.Source.SetBackgroundColor(tcell.ColorDefault) + e.Source.SetTextStyle(tcell.StyleDefault) + e.Source.SetBorderPadding(0, 0, 1, 1) + e.load() - view.SetChangedFunc(func() { - source = view.GetText() - markDirty(view, true) + e.Source.SetChangedFunc(func() { + e.setDirty(true) }) - view.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + e.Source.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyESC { + e.updateResult() + e.toggle() + return nil + } + if event.Key() == tcell.KeyCtrlS { - err := os.WriteFile(filepath.Join(home, ".dash"), []byte(source), 0644) - - if err != nil { - panic(err) - } - - markDirty(view, false) + e.save() + e.updateResult() + e.toggle() return nil } return event }) - return view + return e.Source } -func markDirty(view *tview.TextArea, dirty bool) { +func (e *Editor) newResult() *tview.TextView { + e.Result = tview.NewTextView() + e.Result.SetBackgroundColor(tcell.ColorDefault) + e.Result.SetTextStyle(tcell.StyleDefault) + e.Result.SetBorderPadding(0, 0, 1, 1) + + e.Result.SetMouseCapture(func(action tview.MouseAction, event *tcell.EventMouse) (tview.MouseAction, *tcell.EventMouse) { + if action == tview.MouseLeftClick { + e.toggle() + } + + return action, event + }) + + e.Result.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyEnter: + e.toggle() + return nil + + case tcell.KeyRune: + if event.Rune() == 'i' { + e.toggle() + return nil + } + + case tcell.KeyCtrlS: + e.save() + return nil + } + + return event + }) + + e.updateResult() + return e.Result +} + +func (e *Editor) updateResult() { + source := e.Source.GetText() + result := core.Process(source) + e.Result.SetText(result) +} + +func (e *Editor) setDirty(dirty bool) { + e.IsDirty = dirty + if dirty { - view.SetBackgroundColor(tcell.ColorRed) + e.Source.SetBackgroundColor(tcell.ColorRed) } else { - view.SetBackgroundColor(tcell.ColorDefault) + e.Source.SetBackgroundColor(tcell.ColorDefault) } } + +func (e *Editor) load() { + source := "Press Enter to edit.\nPress Esc to preview.\nPress Ctrl + S to save.\n\n{whoami}@{hostname}" + data, err := os.ReadFile(e.FilePath) + + if err == nil { + source = string(data) + } + + e.Source.SetText(source, true) +} + +func (e *Editor) save() { + if !e.IsDirty { + return + } + + source := e.Source.GetText() + err := os.WriteFile(e.FilePath, []byte(source), 0600) + + if err != nil { + panic(err) + } + + e.setDirty(false) +} diff --git a/empty/empty.go b/empty/empty.go deleted file mode 100644 index 8a73050..0000000 --- a/empty/empty.go +++ /dev/null @@ -1,13 +0,0 @@ -package empty - -import ( - "github.com/gdamore/tcell/v2" - "github.com/rivo/tview" -) - -func New(app *tview.Application) *tview.Box { - view := tview.NewBox() - view.SetBackgroundColor(tcell.ColorDefault) - view.SetBorderPadding(1, 1, 1, 1) - return view -} diff --git a/main.go b/main.go index 07934c6..829f3ef 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,12 @@ package main import ( + "os" + "path/filepath" + "git.akyoto.dev/cli/dash/clock" + "git.akyoto.dev/cli/dash/core" "git.akyoto.dev/cli/dash/editor" - //"git.akyoto.dev/cli/dash/empty" "git.akyoto.dev/cli/dash/osinfo" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" @@ -12,37 +15,32 @@ import ( func main() { app := tview.NewApplication() - header := clock.New(app) - main := editor.New(app) - footer := osinfo.New(app) - //left := empty.New(app) - //right := empty.New(app) - grid := tview.NewGrid() grid.SetRows(3, 0, 3) grid.SetColumns(0, 80, 0) grid.SetBorders(false) grid.SetBackgroundColor(tcell.ColorDefault) + dataDir := core.ConfigDir() + err := os.MkdirAll(dataDir, 0700) + + if err != nil { + panic(err) + } + + header := clock.New(app) + main := editor.New(app, filepath.Join(dataDir, "main")) + footer := osinfo.New(app) + grid.AddItem(header, 0, 0, 1, 3, 0, 0, false) - grid.AddItem(main, 1, 1, 1, 1, 0, 0, false) + grid.AddItem(main.Pages, 1, 1, 1, 1, 0, 0, false) grid.AddItem(footer, 2, 0, 1, 3, 0, 0, false) - // Layout for screens narrower than 100 cells (menu and side bar are hidden). - //grid.AddItem(left, 0, 0, 0, 0, 0, 0, false) - //grid.AddItem(main, 1, 0, 1, 3, 0, 0, false) - //grid.AddItem(right, 0, 0, 0, 0, 0, 0, false) - - // Layout for screens wider than 80 cells. - //grid.AddItem(left, 1, 0, 1, 1, 0, 80, false) - //grid.AddItem(main, 1, 1, 1, 1, 0, 80, false) - //grid.AddItem(right, 1, 2, 1, 1, 0, 80, false) - app.EnableMouse(true) app.SetRoot(grid, true) - app.SetFocus(main) + app.SetFocus(main.Pages) - err := app.Run() + err = app.Run() if err != nil { panic(err)