Skip to content

Commit

Permalink
Initial editor implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
bahner committed Feb 24, 2024
2 parents 52e05d5 + 0ad8de6 commit ddffa7a
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 45 deletions.
15 changes: 15 additions & 0 deletions config/editor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package config

import "os"

const defaultEditor string = "vim"

func GetEditor() string {
editor := os.Getenv("EDITOR")

if editor == "" {
return defaultEditor
}
return editor

}
34 changes: 28 additions & 6 deletions ui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,42 @@ func (ui *ChatUI) setupApp() {

// chatPanel is a horizontal box with messages on the left and peers on the right
// the peers list takes 20 columns, and the messages take the remaining space
chatPanel := tview.NewFlex().
ui.chatPanel = tview.NewFlex().
AddItem(msgBox, 0, 1, false).
AddItem(peersList, config.GetUIPeerslistWidth(), 1, false)

// The ordering here is a little kludgy, but acceptable for now.
// the input fiield setup became rather verbose, so it was moved to its own file.
ui.inputField = ui.setupInputField()

// flex is a vertical box with the chatPanel on top and the input field at the bottom.
flex := tview.NewFlex().
ui.updateRoot()

}

func (ui *ChatUI) updateRoot() {
ui.screen = tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(chatPanel, 0, 1, false).
AddItem(ui.inputField, 1, 1, true)
AddItem(ui.chatPanel, 0, 1, false).
AddItem(ui.inputField, 1, 1, true) // True meaans it has focus.
ui.app.SetRoot(ui.screen, true)
}

func setupMsgbox(app *tview.Application) *tview.TextView {

// make a text view to contain our chat messages
msgBox := tview.NewTextView()
msgBox.SetDynamicColors(true)
msgBox.SetBorder(true)
msgBox.SetScrollable(true)
msgBox.SetTitle(defaultLimbo)

ui.app.SetRoot(flex, true)
// text views are io.Writers, but they don't automatically refresh.
// this sets a change handler to force the app to redraw when we get
// new messages to display.
msgBox.SetChangedFunc(func() {
app.Draw()
msgBox.ScrollToEnd()
})

return msgBox
}
2 changes: 2 additions & 0 deletions ui/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ func (ui *ChatUI) handleCommands(input string) {
ui.handleBroadcastCommand(args)
case "/set":
ui.handleSetCommand(args)
case "/edit":
ui.handleEditCommand(args)
case "/resolve":
go ui.handleResolveCommand(args) // This make take some time. No need to block the UI
case "/discover":
Expand Down
81 changes: 81 additions & 0 deletions ui/edit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ui

import (
"bytes"
"os"
"os/exec"

"github.com/bahner/go-ma-actor/config"
log "github.com/sirupsen/logrus"
)

func (ui *ChatUI) invokeEditor() ([]byte, error) {
tmpfile, err := os.CreateTemp("", "edit")
if err != nil {
return nil, err
}
defer os.Remove(tmpfile.Name()) // clean up

var editorErr error
var contents []byte

// Use Suspend to stop the TUI and run the external editor
ui.app.Suspend(func() {
// Launch external editor
cmd := exec.Command(config.GetEditor(), tmpfile.Name())
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
editorErr = cmd.Run() // This will wait until the editor is closed

if editorErr == nil {
// Read the file only if there was no error with the editor
contents, editorErr = os.ReadFile(tmpfile.Name())
}
})

// Check if there was an error with the editor or reading the file
if editorErr != nil {
return nil, editorErr
}

// Remove newlies, We want this to be a single line
// Trim right to remove trailing newlines
contents = bytes.TrimRight(contents, "\n")
// Replace all newlines with spaces (or another character if preferred)
contents = bytes.ReplaceAll(contents, []byte("\n"), []byte(" "))
// Append a single newline at the end if desired
contents = append(contents, '\n')

return contents, nil
}

func (ui *ChatUI) handleEditCommand(args []string) {

if log.GetLevel() < log.DebugLevel {
ui.displaySystemMessage("Debug mode is off. Cannot edit messages")
return
}

m, err := ui.invokeEditor()
if err != nil {
ui.displaySystemMessage("Error invoking editor: " + err.Error())
return
}

if len(m) == 0 {
ui.displaySystemMessage("No changes made")
return
}

log.Debugf("Editor returned: %s", m)

ui.app.QueueUpdateDraw(func() {
ui.inputField = ui.setupInputField()
ui.updateRoot()
ui.inputField.SetText(string(m))
ui.app.SetFocus(ui.inputField)
})

log.Debug("Editor command handled")

}
3 changes: 3 additions & 0 deletions ui/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/bahner/go-ma"
log "github.com/sirupsen/logrus"
)

// // displaySelfMessage writes a message from ourself to the message window,
Expand Down Expand Up @@ -37,8 +38,10 @@ func (ui *ChatUI) handleEvents() {
select {
case input := <-ui.chInput:
if strings.HasPrefix(input, "/") {
log.Debug("hadleEvents got command: ", input)
ui.handleCommands(input)
} else {
log.Debug("hadleEvents got message: ", input)
ui.handleChatMessage(input)
}

Expand Down
19 changes: 7 additions & 12 deletions ui/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package ui

import (
"github.com/gdamore/tcell/v2"

"github.com/rivo/tview"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)

Expand All @@ -15,6 +17,10 @@ func (ui *ChatUI) setupInputField() *tview.InputField {

inputField.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEscape:
ui.app.QueueUpdateDraw(func() {
ui.app.SetFocus(ui.inputField)
})
case tcell.KeyUp:
if ui.currentHistoryIndex < len(ui.inputHistory)-1 {
ui.currentHistoryIndex++
Expand All @@ -34,21 +40,10 @@ func (ui *ChatUI) setupInputField() *tview.InputField {
return nil // event handled
}
}
log.Debugf("inputCapture: %v", event)
return event // let other keys pass through
})

inputField.SetDoneFunc(func(key tcell.Key) {
if key == tcell.KeyEnter {
input := inputField.GetText()
if input != "" {
ui.inputHistory = append(ui.inputHistory, input) // Add to history
ui.currentHistoryIndex = -1 // Reset index
ui.chInput <- input // Send input to be handled
inputField.SetText("") // Clear the input field
}
}
})

// the done func is called when the user hits enter, or tabs out of the field
inputField.SetDoneFunc(func(key tcell.Key) {
if key != tcell.KeyEnter {
Expand Down
23 changes: 0 additions & 23 deletions ui/msgbox.go

This file was deleted.

22 changes: 22 additions & 0 deletions ui/nick.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ui

import "github.com/spf13/viper"

func (ui *ChatUI) handleNickCommand(args []string) {

if len(args) == 2 {

nick := args[1]

viper.Set("actor.nick", nick)
ui.inputField.SetLabel(nick + ": ")

} else {
ui.handleHelpNickCommand()
}
}

func (ui *ChatUI) handleHelpNickCommand() {
ui.displaySystemMessage("Usage: /nick NAME")
ui.displaySystemMessage("Sets your nick.")
}
10 changes: 6 additions & 4 deletions ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,18 @@ type ChatUI struct {
broadcastCancel context.CancelFunc

// History of entries
inputField *tview.InputField
inputHistory []string
currentHistoryIndex int

// The Topic is used for publication of messages after encryption and signing.
// The names are obviously, from the corresponding DIDDocument.

app *tview.Application
peersList *tview.TextView
msgBox *tview.TextView
app *tview.Application
peersList *tview.TextView
msgBox *tview.TextView
inputField *tview.InputField
chatPanel *tview.Flex
screen *tview.Flex

msgW io.Writer
chInput chan string
Expand Down

0 comments on commit ddffa7a

Please sign in to comment.