summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/ui/textinput.go136
-rw-r--r--widgets/dirlist.go14
-rw-r--r--widgets/exline.go116
-rw-r--r--widgets/spinner.go4
4 files changed, 163 insertions, 107 deletions
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
new file mode 100644
index 0000000..aff520b
--- /dev/null
+++ b/lib/ui/textinput.go
@@ -0,0 +1,136 @@
+package ui
+
+import (
+	"github.com/gdamore/tcell"
+	"github.com/mattn/go-runewidth"
+)
+
+// TODO: Attach history and tab completion providers
+// TODO: scrolling
+
+type TextInput struct {
+	Invalidatable
+	cells  int
+	ctx    *Context
+	focus  bool
+	index  int
+	prompt string
+	scroll int
+	text   []rune
+}
+
+// Creates a new TextInput. TextInputs will render a "textbox" in the entire
+// context they're given, and process keypresses to build a string from user
+// input.
+func NewTextInput() *TextInput {
+	return &TextInput{
+		cells: -1,
+		text:  []rune{},
+	}
+}
+
+func (ti *TextInput) Prompt(prompt string) *TextInput {
+	ti.prompt = prompt
+	return ti
+}
+
+func (ti *TextInput) String() string {
+	return string(ti.text)
+}
+
+func (ti *TextInput) Invalidate() {
+	ti.DoInvalidate(ti)
+}
+
+func (ti *TextInput) Draw(ctx *Context) {
+	ti.ctx = ctx // gross
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
+	ctx.Printf(0, 0, tcell.StyleDefault, "%s%s", ti.prompt, string(ti.text))
+	cells := runewidth.StringWidth(string(ti.text[:ti.index]))
+	if cells != ti.cells {
+		ctx.SetCursor(cells+1, 0)
+	}
+}
+
+func (ti *TextInput) Focus(focus bool) {
+	ti.focus = focus
+	if focus && ti.ctx != nil {
+		cells := runewidth.StringWidth(string(ti.text[:ti.index]))
+		ti.ctx.SetCursor(cells+1, 0)
+	}
+}
+
+func (ti *TextInput) insert(ch rune) {
+	left := ti.text[:ti.index]
+	right := ti.text[ti.index:]
+	ti.text = append(left, append([]rune{ch}, right...)...)
+	ti.index++
+	ti.Invalidate()
+}
+
+func (ti *TextInput) deleteWord() {
+	// TODO: Break on any of / " '
+	if len(ti.text) == 0 {
+		return
+	}
+	i := ti.index - 1
+	if ti.text[i] == ' ' {
+		i--
+	}
+	for ; i >= 0; i-- {
+		if ti.text[i] == ' ' {
+			break
+		}
+	}
+	ti.text = append(ti.text[:i+1], ti.text[ti.index:]...)
+	ti.index = i + 1
+	ti.Invalidate()
+}
+
+func (ti *TextInput) deleteChar() {
+	if len(ti.text) > 0 && ti.index != len(ti.text) {
+		ti.text = append(ti.text[:ti.index], ti.text[ti.index+1:]...)
+		ti.Invalidate()
+	}
+}
+
+func (ti *TextInput) backspace() {
+	if len(ti.text) > 0 && ti.index != 0 {
+		ti.text = append(ti.text[:ti.index-1], ti.text[ti.index:]...)
+		ti.index--
+		ti.Invalidate()
+	}
+}
+
+func (ti *TextInput) Event(event tcell.Event) bool {
+	switch event := event.(type) {
+	case *tcell.EventKey:
+		switch event.Key() {
+		case tcell.KeyBackspace, tcell.KeyBackspace2:
+			ti.backspace()
+		case tcell.KeyCtrlD, tcell.KeyDelete:
+			ti.deleteChar()
+		case tcell.KeyCtrlB, tcell.KeyLeft:
+			if ti.index > 0 {
+				ti.index--
+				ti.Invalidate()
+			}
+		case tcell.KeyCtrlF, tcell.KeyRight:
+			if ti.index < len(ti.text) {
+				ti.index++
+				ti.Invalidate()
+			}
+		case tcell.KeyCtrlA, tcell.KeyHome:
+			ti.index = 0
+			ti.Invalidate()
+		case tcell.KeyCtrlE, tcell.KeyEnd:
+			ti.index = len(ti.text)
+			ti.Invalidate()
+		case tcell.KeyCtrlW:
+			ti.deleteWord()
+		case tcell.KeyRune:
+			ti.insert(event.Rune())
+		}
+	}
+	return true
+}
diff --git a/widgets/dirlist.go b/widgets/dirlist.go
index 374d142..faf73a1 100644
--- a/widgets/dirlist.go
+++ b/widgets/dirlist.go
@@ -13,13 +13,13 @@ import (
 
 type DirectoryList struct {
 	ui.Invalidatable
-	conf         *config.AccountConfig
-	dirs         []string
-	logger       *log.Logger
-	selecting    string
-	selected     string
-	spinner      *Spinner
-	worker       *types.Worker
+	conf      *config.AccountConfig
+	dirs      []string
+	logger    *log.Logger
+	selecting string
+	selected  string
+	spinner   *Spinner
+	worker    *types.Worker
 }
 
 func NewDirectoryList(conf *config.AccountConfig,
diff --git a/widgets/exline.go b/widgets/exline.go
index 8b18736..c841802 100644
--- a/widgets/exline.go
+++ b/widgets/exline.go
@@ -2,36 +2,29 @@ package widgets
 
 import (
 	"github.com/gdamore/tcell"
-	"github.com/mattn/go-runewidth"
 
 	"git.sr.ht/~sircmpwn/aerc2/lib/ui"
 )
 
-// TODO: history
-// TODO: tab completion
-// TODO: scrolling
-
 type ExLine struct {
 	ui.Invalidatable
-	command []rune
-	commit  func(cmd string)
-	ctx     *ui.Context
-	cancel  func()
-	cells   int
-	focus   bool
-	index   int
-	scroll  int
-
-	onInvalidate func(d ui.Drawable)
+	cancel func()
+	commit func(cmd string)
+	ctx    *ui.Context
+	input  *ui.TextInput
 }
 
 func NewExLine(commit func(cmd string), cancel func()) *ExLine {
-	return &ExLine{
-		cancel:  cancel,
-		cells:   -1,
-		commit:  commit,
-		command: []rune{},
+	input := ui.NewTextInput().Prompt(":")
+	exline := &ExLine{
+		cancel: cancel,
+		commit: commit,
+		input:  input,
 	}
+	input.OnInvalidate(func(d ui.Drawable) {
+		exline.Invalidate()
+	})
+	return exline
 }
 
 func (ex *ExLine) Invalidate() {
@@ -40,102 +33,29 @@ func (ex *ExLine) Invalidate() {
 
 func (ex *ExLine) Draw(ctx *ui.Context) {
 	ex.ctx = ctx // gross
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
-	ctx.Printf(0, 0, tcell.StyleDefault, ":%s", string(ex.command))
-	cells := runewidth.StringWidth(string(ex.command[:ex.index]))
-	if cells != ex.cells {
-		ctx.SetCursor(cells+1, 0)
-	}
+	ex.input.Draw(ctx)
 }
 
 func (ex *ExLine) Focus(focus bool) {
-	ex.focus = focus
-	if focus && ex.ctx != nil {
-		cells := runewidth.StringWidth(string(ex.command[:ex.index]))
-		ex.ctx.SetCursor(cells+1, 0)
-	}
-}
-
-func (ex *ExLine) insert(ch rune) {
-	left := ex.command[:ex.index]
-	right := ex.command[ex.index:]
-	ex.command = append(left, append([]rune{ch}, right...)...)
-	ex.index++
-	ex.Invalidate()
-}
-
-func (ex *ExLine) deleteWord() {
-	// TODO: Break on any of / " '
-	if len(ex.command) == 0 {
-		return
-	}
-	i := ex.index - 1
-	if ex.command[i] == ' ' {
-		i--
-	}
-	for ; i >= 0; i-- {
-		if ex.command[i] == ' ' {
-			break
-		}
-	}
-	ex.command = append(ex.command[:i+1], ex.command[ex.index:]...)
-	ex.index = i + 1
-	ex.Invalidate()
-}
-
-func (ex *ExLine) deleteChar() {
-	if len(ex.command) > 0 && ex.index != len(ex.command) {
-		ex.command = append(ex.command[:ex.index], ex.command[ex.index+1:]...)
-		ex.Invalidate()
-	}
-}
-
-func (ex *ExLine) backspace() {
-	if len(ex.command) > 0 && ex.index != 0 {
-		ex.command = append(ex.command[:ex.index-1], ex.command[ex.index:]...)
-		ex.index--
-		ex.Invalidate()
-	}
+	ex.input.Focus(focus)
 }
 
 func (ex *ExLine) Event(event tcell.Event) bool {
 	switch event := event.(type) {
 	case *tcell.EventKey:
 		switch event.Key() {
-		case tcell.KeyBackspace, tcell.KeyBackspace2:
-			ex.backspace()
-		case tcell.KeyCtrlD, tcell.KeyDelete:
-			ex.deleteChar()
-		case tcell.KeyCtrlB, tcell.KeyLeft:
-			if ex.index > 0 {
-				ex.index--
-				ex.Invalidate()
-			}
-		case tcell.KeyCtrlF, tcell.KeyRight:
-			if ex.index < len(ex.command) {
-				ex.index++
-				ex.Invalidate()
-			}
-		case tcell.KeyCtrlA, tcell.KeyHome:
-			ex.index = 0
-			ex.Invalidate()
-		case tcell.KeyCtrlE, tcell.KeyEnd:
-			ex.index = len(ex.command)
-			ex.Invalidate()
-		case tcell.KeyCtrlW:
-			ex.deleteWord()
 		case tcell.KeyEnter:
 			if ex.ctx != nil {
 				ex.ctx.HideCursor()
 			}
-			ex.commit(string(ex.command))
+			ex.commit(ex.input.String())
 		case tcell.KeyEsc, tcell.KeyCtrlC:
 			if ex.ctx != nil {
 				ex.ctx.HideCursor()
 			}
 			ex.cancel()
-		case tcell.KeyRune:
-			ex.insert(event.Rune())
+		default:
+			return ex.input.Event(event)
 		}
 	}
 	return true
diff --git a/widgets/spinner.go b/widgets/spinner.go
index bb7dbe8..86158e6 100644
--- a/widgets/spinner.go
+++ b/widgets/spinner.go
@@ -24,8 +24,8 @@ var (
 
 type Spinner struct {
 	ui.Invalidatable
-	frame        int64 // access via atomic
-	stop         chan struct{}
+	frame int64 // access via atomic
+	stop  chan struct{}
 }
 
 func NewSpinner() *Spinner {