package main import ( "fmt" "io" "log" "os" "os/exec" "strings" "github.com/gdamore/tcell/v2" _ "github.com/gdamore/tcell/v2/encoding" "github.com/mattn/go-runewidth" ) const lineHeight = 2 const editor = "vim" const ( uiMain = 0 uiDelete = 1 ) var uiState = uiMain var selectedNote = 0 var notes []note func edit(s tcell.Screen, text string, ext string) (string, error) { cmd := exec.Command("vipe", "--suffix", ext) cmd.Env = append(cmd.Env, "EDITOR="+editor) pipe, _ := cmd.StdinPipe() io.WriteString(pipe, text) pipe.Close() err := s.Suspend() if err != nil { return "", err } output, err := cmd.Output() if err != nil { return "", err } text = string(output) err = s.Resume() if err != nil { return "", err } return text, nil } func editNote(s tcell.Screen, n note) (note, error) { newContent, err := edit(s, n.Content, "md") if err != nil { return note{}, err } n.Content = newContent err = putNote(n) if err != nil { return note{}, nil } showNotes(s) s.Sync() return n, nil } func editNoteTitle(s tcell.Screen, n note) (note, error) { newTitle, err := edit(s, n.Title, "txt") if err != nil { return note{}, err } idx := strings.Index(newTitle, "\n") if idx != -1 { newTitle = newTitle[:idx] } if len(newTitle) == 0 { return n, nil } n.Title = newTitle err = putNote(n) if err != nil { return note{}, nil } showNotes(s) s.Sync() return n, nil } func emitStr(s tcell.Screen, x int, y int, style tcell.Style, str string) { for _, c := range str { var comb []rune w := runewidth.RuneWidth(c) if w == 0 { comb = []rune{c} c = ' ' w = 1 } s.SetContent(x, y, c, comb, style) x += w } } func showNotes(s tcell.Screen) { uiState = uiMain s.Fill(' ', tcell.StyleDefault) for idx, note := range notes { style := tcell.StyleDefault if idx == selectedNote { style = style.Foreground(tcell.ColorBlack).Background(tcell.ColorWhite) } text := fmt.Sprint(idx) + ". " + note.Title emitStr(s, 1, 1+idx*lineHeight, style, text) } _, h := s.Size() emitStr(s, 1, h-1, tcell.StyleDefault.Foreground(tcell.ColorBlue), "n - new, r - rename, d - delete, Enter - edit, Esc - exit") } func showDelete(s tcell.Screen) { uiState = uiDelete s.Fill(' ', tcell.StyleDefault) emitStr(s, 1, 1, tcell.StyleDefault, "Do you really want to delete the note '"+notes[selectedNote].Title+"'?") emitStr(s, 1, 3, tcell.StyleDefault.Foreground(tcell.ColorBlue), "y - yes, n - no") } func showUI(s tcell.Screen) { switch uiState { case uiMain: showNotes(s) case uiDelete: showDelete(s) } } func main() { var err error notes, err = loadNotes() if err != nil { log.Fatalln(err) } s, err := tcell.NewScreen() if err != nil { log.Fatalln(err) } if err := s.Init(); err != nil { log.Fatalln(err) } defStyle := tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite) s.SetStyle(defStyle) showNotes(s) for { switch ev := s.PollEvent().(type) { case *tcell.EventResize: showUI(s) s.Sync() case *tcell.EventKey: switch uiState { case uiMain: switch ev.Key() { case tcell.KeyUp: if selectedNote > 0 { selectedNote-- } showNotes(s) s.Show() case tcell.KeyDown: if selectedNote < len(notes)-1 { selectedNote++ } showNotes(s) s.Show() case tcell.KeyEnter: n, err := editNote(s, notes[selectedNote]) if err != nil { s.Fini() log.Fatalln(err) } notes[selectedNote] = n case tcell.KeyEscape: s.Fini() os.Exit(1) case tcell.KeyRune: switch ev.Rune() { case 'r': n, err := editNoteTitle(s, notes[selectedNote]) if err != nil { s.Fini() log.Fatalln(err) } notes[selectedNote] = n showNotes(s) s.Sync() case 'n': title, err := edit(s, "", "txt") if err != nil { s.Fini() log.Fatalln(err) } if len(title) == 0 { showNotes(s) s.Sync() break } n2, err := postNote(note{Title: title}) if err != nil { s.Fini() log.Fatalln(err) } notes = append(notes, n2) selectedNote = len(notes) - 1 showNotes(s) s.Sync() case 'd': showDelete(s) s.Show() } } case uiDelete: switch ev.Key() { case tcell.KeyRune: switch ev.Rune() { case 'y': err := deleteNote(notes[selectedNote]) if err != nil { s.Fini() log.Fatalln(err) } notes = append(notes[:selectedNote], notes[selectedNote+1:]...) if selectedNote >= len(notes) { selectedNote = len(notes) - 1 } showNotes(s) s.Show() case 'n': showNotes(s) s.Show() } } } } } }