aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2019-05-11 13:12:44 -0400
committerDrew DeVault <sir@cmpwn.com>2019-05-11 13:12:44 -0400
commit8fa458323058c8998408f88b80d07c687fccd521 (patch)
treeaf6c44861f021018f4e5a04a2a83edadf2cc6857
parentde122b16ee3e4c3c12576f311938a63ee6eeedbe (diff)
downloadaerc-8fa458323058c8998408f88b80d07c687fccd521.tar.gz
Split ex line text handling into dedicated widget
-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 00000000..aff520bd
--- /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 374d1428..faf73a14 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 8b187361..c8418024 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 bb7dbe8d..86158e6d 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 {