summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--config/aerc.conf63
-rw-r--r--config/bindings.go284
-rw-r--r--config/bindings_test.go61
-rw-r--r--config/config.go16
-rw-r--r--go.mod3
-rw-r--r--go.sum9
-rw-r--r--widgets/exline.go6
7 files changed, 404 insertions, 38 deletions
diff --git a/config/aerc.conf b/config/aerc.conf
index 76b0310..3b29a77 100644
--- a/config/aerc.conf
+++ b/config/aerc.conf
@@ -91,43 +91,44 @@ alternatives=text/plain,text/html
 
 [lbinds]
 #
-# Binds are of the form <key sequence> = <command to run>
-# To use '=' in a key sequence, substitute it with "Eq": "<Ctrl+Eq>"
-# If you wish to bind #, you can wrap the key sequence in quotes: "#" = quit
-#
-# lbinds are bindings that take effect in the list view
-# mbinds are bindings that take effect in the message view
-q=:quit<Enter>
-<Ctrl+c>=:quit<Enter>
-
-j=:next-message<Enter>
-<Down>=:next-message<Enter>
-<Ctrl+d>=:next-message --scroll 50%<Enter>
-<Ctrl+f>=:next-message --scroll 100%<Enter>
-<PageDown>=:next-message --scroll 100%<Enter>
-<WheelDown>=:next-message --scroll 1<Enter>
-
-k=:previous-message<Enter>
-<Up>=:previous-message<Enter>
-<Ctrl+u>=:previous-message --scroll 50%<Enter>
-<Ctrl+b>=:previous-message --scroll 100%<Enter>
-<PageUp>=:previous-message --scroll 100%<Enter>
-<WheelUp>=:previous-message --scroll 1<Enter>
-g=:select-message 0<Enter>
-G=:select-message -1<Enter>
-
-J=:next-folder<Enter>
-K=:previous-folder<Enter>
+# Binds are of the form <input keys> = <output keys>
+# Pressing <input keys> in sequence will then simulate pressing <output keys>
+#
+# Use <C-*> to refer to control+something.
+#
+# lbinds are effective in the list view
+# mbinds are effective in the message view
+q = :quit<Enter>
+<C-c> = :quit<Enter>
+
+j = :next-message<Enter>
+<Down> = :next-message<Enter>
+<C-d> = :next-message --scroll 50%<Enter>
+<C-f> = :next-message --scroll 100%<Enter>
+<PageDown> = :next-message --scroll 100%<Enter>
+<WheelDown> = :next-message --scroll 1<Enter>
+
+k = :previous-message<Enter>
+<Up> = :previous-message<Enter>
+<C-u> = :previous-message --scroll 50%<Enter>
+<C-b> = :previous-message --scroll 100%<Enter>
+<PageUp> = :previous-message --scroll 100%<Enter>
+<WheelUp> = :previous-message --scroll 1<Enter>
+g = :select-message 0<Enter>
+G = :select-message -1<Enter>
+
+J = :next-folder<Enter>
+K = :previous-folder<Enter>
 l = :next-account<Enter>
 <Right> = :next-account<Enter>
 h = :previous-account<Enter>
 <Left> = :previous-account<Enter>
 
-<Enter>=:view-message<Enter>
-d=:confirm 'Really delete this message?' ':delete-message<Enter>'<Enter>
+<Enter> = :view-message<Enter>
+d = :confirm 'Really delete this message?' ':delete-message<Enter>'<Enter>
 
-c=:cd 
-$=:term-exec 
+c = :cd 
+$ = :term-exec 
 
 [mbinds]
 #
diff --git a/config/bindings.go b/config/bindings.go
new file mode 100644
index 0000000..c10b68f
--- /dev/null
+++ b/config/bindings.go
@@ -0,0 +1,284 @@
+package config
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"strings"
+
+	"github.com/gdamore/tcell"
+)
+
+type KeyStroke struct {
+	Key  tcell.Key
+	Rune rune
+}
+
+type Binding struct {
+	Output []KeyStroke
+	Input  []KeyStroke
+}
+
+type KeyBindings []*Binding
+
+const (
+	BINDING_FOUND = iota
+	BINDING_INCOMPLETE
+	BINDING_NOT_FOUND
+)
+
+type BindingSearchResult int
+
+func NewKeyBindings() *KeyBindings {
+	return &KeyBindings{}
+}
+
+func (bindings *KeyBindings) Add(binding *Binding) {
+	// TODO: Search for conflicts?
+	*bindings = append(*bindings, binding)
+}
+
+func (bindings *KeyBindings) GetBinding(
+	input []KeyStroke) (BindingSearchResult, []KeyStroke) {
+
+	incomplete := false
+	// TODO: This could probably be a sorted list to speed things up
+	// TODO: Deal with bindings that share a prefix
+	for _, binding := range *bindings {
+		if len(binding.Input) < len(input) {
+			continue
+		}
+		for i, stroke := range input {
+			if stroke != binding.Input[i] {
+				goto next
+			}
+		}
+		if len(binding.Input) != len(input) {
+			incomplete = true
+		} else {
+			return BINDING_FOUND, binding.Output
+		}
+	next:
+	}
+	if incomplete {
+		return BINDING_INCOMPLETE, nil
+	}
+	return BINDING_NOT_FOUND, nil
+}
+
+var (
+	keyNames map[string]tcell.Key
+)
+
+func ParseKeyStrokes(keystrokes string) ([]KeyStroke, error) {
+	var strokes []KeyStroke
+	buf := bytes.NewBufferString(keystrokes)
+	for {
+		tok, _, err := buf.ReadRune()
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			return nil, err
+		}
+		// TODO: make it possible to bind to < or > themselves (and default to
+		// switching accounts)
+		switch tok {
+		case '<':
+			name, err := buf.ReadString(byte('>'))
+			if err == io.EOF {
+				return nil, errors.New("Expecting '>'")
+			} else if err != nil {
+				return nil, err
+			} else if name == ">" {
+				return nil, errors.New("Expected a key name")
+			}
+			name = name[:len(name)-1]
+			if key, ok := keyNames[strings.ToLower(name)]; ok {
+				strokes = append(strokes, KeyStroke{
+					Key: key,
+				})
+			} else {
+				return nil, errors.New(fmt.Sprintf("Unknown key '%s'", name))
+			}
+		case '>':
+			return nil, errors.New("Found '>' without '<'")
+		default:
+			strokes = append(strokes, KeyStroke{
+				Key:  tcell.KeyRune,
+				Rune: tok,
+			})
+		}
+	}
+	return strokes, nil
+}
+
+func ParseBinding(input, output string) (*Binding, error) {
+	in, err := ParseKeyStrokes(input)
+	if err != nil {
+		return nil, err
+	}
+	out, err := ParseKeyStrokes(output)
+	if err != nil {
+		return nil, err
+	}
+	return &Binding{
+		Input:  in,
+		Output: out,
+	}, nil
+}
+
+func init() {
+	keyNames = make(map[string]tcell.Key)
+	keyNames["up"] = tcell.KeyUp
+	keyNames["down"] = tcell.KeyDown
+	keyNames["right"] = tcell.KeyRight
+	keyNames["left"] = tcell.KeyLeft
+	keyNames["upleft"] = tcell.KeyUpLeft
+	keyNames["upright"] = tcell.KeyUpRight
+	keyNames["downleft"] = tcell.KeyDownLeft
+	keyNames["downright"] = tcell.KeyDownRight
+	keyNames["center"] = tcell.KeyCenter
+	keyNames["pgup"] = tcell.KeyPgUp
+	keyNames["pgdn"] = tcell.KeyPgDn
+	keyNames["home"] = tcell.KeyHome
+	keyNames["end"] = tcell.KeyEnd
+	keyNames["insert"] = tcell.KeyInsert
+	keyNames["delete"] = tcell.KeyDelete
+	keyNames["help"] = tcell.KeyHelp
+	keyNames["exit"] = tcell.KeyExit
+	keyNames["clear"] = tcell.KeyClear
+	keyNames["cancel"] = tcell.KeyCancel
+	keyNames["print"] = tcell.KeyPrint
+	keyNames["pause"] = tcell.KeyPause
+	keyNames["backtab"] = tcell.KeyBacktab
+	keyNames["f1"] = tcell.KeyF1
+	keyNames["f2"] = tcell.KeyF2
+	keyNames["f3"] = tcell.KeyF3
+	keyNames["f4"] = tcell.KeyF4
+	keyNames["f5"] = tcell.KeyF5
+	keyNames["f6"] = tcell.KeyF6
+	keyNames["f7"] = tcell.KeyF7
+	keyNames["f8"] = tcell.KeyF8
+	keyNames["f9"] = tcell.KeyF9
+	keyNames["f10"] = tcell.KeyF10
+	keyNames["f11"] = tcell.KeyF11
+	keyNames["f12"] = tcell.KeyF12
+	keyNames["f13"] = tcell.KeyF13
+	keyNames["f14"] = tcell.KeyF14
+	keyNames["f15"] = tcell.KeyF15
+	keyNames["f16"] = tcell.KeyF16
+	keyNames["f17"] = tcell.KeyF17
+	keyNames["f18"] = tcell.KeyF18
+	keyNames["f19"] = tcell.KeyF19
+	keyNames["f20"] = tcell.KeyF20
+	keyNames["f21"] = tcell.KeyF21
+	keyNames["f22"] = tcell.KeyF22
+	keyNames["f23"] = tcell.KeyF23
+	keyNames["f24"] = tcell.KeyF24
+	keyNames["f25"] = tcell.KeyF25
+	keyNames["f26"] = tcell.KeyF26
+	keyNames["f27"] = tcell.KeyF27
+	keyNames["f28"] = tcell.KeyF28
+	keyNames["f29"] = tcell.KeyF29
+	keyNames["f30"] = tcell.KeyF30
+	keyNames["f31"] = tcell.KeyF31
+	keyNames["f32"] = tcell.KeyF32
+	keyNames["f33"] = tcell.KeyF33
+	keyNames["f34"] = tcell.KeyF34
+	keyNames["f35"] = tcell.KeyF35
+	keyNames["f36"] = tcell.KeyF36
+	keyNames["f37"] = tcell.KeyF37
+	keyNames["f38"] = tcell.KeyF38
+	keyNames["f39"] = tcell.KeyF39
+	keyNames["f40"] = tcell.KeyF40
+	keyNames["f41"] = tcell.KeyF41
+	keyNames["f42"] = tcell.KeyF42
+	keyNames["f43"] = tcell.KeyF43
+	keyNames["f44"] = tcell.KeyF44
+	keyNames["f45"] = tcell.KeyF45
+	keyNames["f46"] = tcell.KeyF46
+	keyNames["f47"] = tcell.KeyF47
+	keyNames["f48"] = tcell.KeyF48
+	keyNames["f49"] = tcell.KeyF49
+	keyNames["f50"] = tcell.KeyF50
+	keyNames["f51"] = tcell.KeyF51
+	keyNames["f52"] = tcell.KeyF52
+	keyNames["f53"] = tcell.KeyF53
+	keyNames["f54"] = tcell.KeyF54
+	keyNames["f55"] = tcell.KeyF55
+	keyNames["f56"] = tcell.KeyF56
+	keyNames["f57"] = tcell.KeyF57
+	keyNames["f58"] = tcell.KeyF58
+	keyNames["f59"] = tcell.KeyF59
+	keyNames["f60"] = tcell.KeyF60
+	keyNames["f61"] = tcell.KeyF61
+	keyNames["f62"] = tcell.KeyF62
+	keyNames["f63"] = tcell.KeyF63
+	keyNames["f64"] = tcell.KeyF64
+	keyNames["c-space"] = tcell.KeyCtrlSpace
+	keyNames["c-a"] = tcell.KeyCtrlA
+	keyNames["c-b"] = tcell.KeyCtrlB
+	keyNames["c-c"] = tcell.KeyCtrlC
+	keyNames["c-d"] = tcell.KeyCtrlD
+	keyNames["c-e"] = tcell.KeyCtrlE
+	keyNames["c-f"] = tcell.KeyCtrlF
+	keyNames["c-g"] = tcell.KeyCtrlG
+	keyNames["c-h"] = tcell.KeyCtrlH
+	keyNames["c-i"] = tcell.KeyCtrlI
+	keyNames["c-j"] = tcell.KeyCtrlJ
+	keyNames["c-k"] = tcell.KeyCtrlK
+	keyNames["c-l"] = tcell.KeyCtrlL
+	keyNames["c-m"] = tcell.KeyCtrlM
+	keyNames["c-n"] = tcell.KeyCtrlN
+	keyNames["c-o"] = tcell.KeyCtrlO
+	keyNames["c-p"] = tcell.KeyCtrlP
+	keyNames["c-q"] = tcell.KeyCtrlQ
+	keyNames["c-r"] = tcell.KeyCtrlR
+	keyNames["c-s"] = tcell.KeyCtrlS
+	keyNames["c-t"] = tcell.KeyCtrlT
+	keyNames["c-u"] = tcell.KeyCtrlU
+	keyNames["c-v"] = tcell.KeyCtrlV
+	keyNames["c-w"] = tcell.KeyCtrlW
+	keyNames["c-x"] = tcell.KeyCtrlX
+	keyNames["c-y"] = tcell.KeyCtrlY
+	keyNames["c-z"] = tcell.KeyCtrlZ
+	keyNames["c-]"] = tcell.KeyCtrlLeftSq
+	keyNames["c-\\"] = tcell.KeyCtrlBackslash
+	keyNames["c-["] = tcell.KeyCtrlRightSq
+	keyNames["c-^"] = tcell.KeyCtrlCarat
+	keyNames["c-_"] = tcell.KeyCtrlUnderscore
+	keyNames["NUL"] = tcell.KeyNUL
+	keyNames["SOH"] = tcell.KeySOH
+	keyNames["STX"] = tcell.KeySTX
+	keyNames["ETX"] = tcell.KeyETX
+	keyNames["EOT"] = tcell.KeyEOT
+	keyNames["ENQ"] = tcell.KeyENQ
+	keyNames["ACK"] = tcell.KeyACK
+	keyNames["BEL"] = tcell.KeyBEL
+	keyNames["BS"] = tcell.KeyBS
+	keyNames["TAB"] = tcell.KeyTAB
+	keyNames["LF"] = tcell.KeyLF
+	keyNames["VT"] = tcell.KeyVT
+	keyNames["FF"] = tcell.KeyFF
+	keyNames["CR"] = tcell.KeyCR
+	keyNames["SO"] = tcell.KeySO
+	keyNames["SI"] = tcell.KeySI
+	keyNames["DLE"] = tcell.KeyDLE
+	keyNames["DC1"] = tcell.KeyDC1
+	keyNames["DC2"] = tcell.KeyDC2
+	keyNames["DC3"] = tcell.KeyDC3
+	keyNames["DC4"] = tcell.KeyDC4
+	keyNames["NAK"] = tcell.KeyNAK
+	keyNames["SYN"] = tcell.KeySYN
+	keyNames["ETB"] = tcell.KeyETB
+	keyNames["CAN"] = tcell.KeyCAN
+	keyNames["EM"] = tcell.KeyEM
+	keyNames["SUB"] = tcell.KeySUB
+	keyNames["ESC"] = tcell.KeyESC
+	keyNames["FS"] = tcell.KeyFS
+	keyNames["GS"] = tcell.KeyGS
+	keyNames["RS"] = tcell.KeyRS
+	keyNames["US"] = tcell.KeyUS
+	keyNames["DEL"] = tcell.KeyDEL
+}
diff --git a/config/bindings_test.go b/config/bindings_test.go
new file mode 100644
index 0000000..1d1cbfe
--- /dev/null
+++ b/config/bindings_test.go
@@ -0,0 +1,61 @@
+package config
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/gdamore/tcell"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetBinding(t *testing.T) {
+	assert := assert.New(t)
+
+	bindings := NewKeyBindings()
+	add := func(binding, cmd string) {
+		b, _ := ParseBinding(binding, cmd)
+		bindings.Add(b)
+	}
+
+	add("abc", ":abc")
+	add("cba", ":cba")
+	add("foo", ":foo")
+	add("bar", ":bar")
+
+	test := func(input []KeyStroke, result int, output string) {
+		_output, _ := ParseKeyStrokes(output)
+		r, out := bindings.GetBinding(input)
+		assert.Equal(result, int(r), fmt.Sprintf(
+			"%s: Expected result %d, got %d", output, result, r))
+		assert.Equal(_output, out, fmt.Sprintf(
+			"%s: Expected output %v, got %v", output, _output, out))
+	}
+
+	test([]KeyStroke{
+		{tcell.KeyRune, 'a'},
+	}, BINDING_INCOMPLETE, "")
+	test([]KeyStroke{
+		{tcell.KeyRune, 'a'},
+		{tcell.KeyRune, 'b'},
+		{tcell.KeyRune, 'c'},
+	}, BINDING_FOUND, ":abc")
+	test([]KeyStroke{
+		{tcell.KeyRune, 'c'},
+		{tcell.KeyRune, 'b'},
+		{tcell.KeyRune, 'a'},
+	}, BINDING_FOUND, ":cba")
+	test([]KeyStroke{
+		{tcell.KeyRune, 'f'},
+		{tcell.KeyRune, 'o'},
+	}, BINDING_INCOMPLETE, "")
+	test([]KeyStroke{
+		{tcell.KeyRune, '4'},
+		{tcell.KeyRune, '0'},
+		{tcell.KeyRune, '4'},
+	}, BINDING_NOT_FOUND, "")
+
+	add("<C-a>", "c-a")
+	test([]KeyStroke{
+		{tcell.KeyCtrlA, 0},
+	}, BINDING_FOUND, "c-a")
+}
diff --git a/config/config.go b/config/config.go
index 142a1e0..ff0e094 100644
--- a/config/config.go
+++ b/config/config.go
@@ -29,6 +29,7 @@ type AccountConfig struct {
 }
 
 type AercConfig struct {
+	Lbinds   *KeyBindings
 	Ini      *ini.File       `ini:"-"`
 	Accounts []AccountConfig `ini:"-"`
 	Ui       UIConfig
@@ -94,7 +95,9 @@ func LoadConfig(root *string) (*AercConfig, error) {
 	}
 	file.NameMapper = mapName
 	config := &AercConfig{
-		Ini: file,
+		Lbinds: NewKeyBindings(),
+		Ini:    file,
+
 		Ui: UIConfig{
 			IndexFormat:     "%4C %Z %D %-17.17n %s",
 			TimestampFormat: "%F %l:%M %p",
@@ -110,9 +113,18 @@ func LoadConfig(root *string) (*AercConfig, error) {
 			EmptyMessage:      "(no messages)",
 		},
 	}
-	if ui, err := file.GetSection("ui"); err != nil {
+	if ui, err := file.GetSection("ui"); err == nil {
 		ui.MapTo(config.Ui)
 	}
+	if lbinds, err := file.GetSection("lbinds"); err == nil {
+		for key, value := range lbinds.KeysHash() {
+			binding, err := ParseBinding(key, value)
+			if err != nil {
+				return nil, err
+			}
+			config.Lbinds.Add(binding)
+		}
+	}
 	accountsPath := path.Join(*root, "accounts.conf")
 	if accounts, err := loadAccountConfig(accountsPath); err != nil {
 		return nil, err
diff --git a/go.mod b/go.mod
index 05f0880..5eae665 100644
--- a/go.mod
+++ b/go.mod
@@ -6,12 +6,13 @@ require (
 	github.com/emersion/go-sasl v0.0.0-20161116183048-7e096a0a6197 // indirect
 	github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635
 	github.com/gdamore/tcell v1.0.0
-	github.com/go-ini/ini v1.32.0
+	github.com/go-ini/ini v1.42.0
 	github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
 	github.com/kyoh86/xdg v0.0.0-20171127140545-8db68a8ea76a
 	github.com/lucasb-eyer/go-colorful v0.0.0-20180531031333-d9cec903b20c
 	github.com/mattn/go-isatty v0.0.3
 	github.com/mattn/go-runewidth v0.0.2
 	github.com/nsf/termbox-go v0.0.0-20180129072728-88b7b944be8b
+	github.com/stretchr/testify v1.3.0
 	golang.org/x/text v0.3.0
 )
diff --git a/go.sum b/go.sum
index be1bff1..1278e7a 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/emersion/go-imap v1.0.0-beta.1 h1:bTCaVlUnb5mKoW9lEukusxguSYYZPer+q0g5t+vw5X0=
 github.com/emersion/go-imap v1.0.0-beta.1/go.mod h1:oydmHwiyv92ZOiNfQY9BDax5heePWN8P2+W1B2T6qjc=
 github.com/emersion/go-imap-idle v0.0.0-20180114101550-2af93776db6b h1:q4qkNe/W10qFGD3RWd4meQTkD0+Zrz0L4ekMvlptg60=
@@ -10,6 +12,8 @@ github.com/gdamore/tcell v1.0.0 h1:oaly4AkxvDT5ffKHV/n4L8iy6FxG2QkAVl0M6cjryuE=
 github.com/gdamore/tcell v1.0.0/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
 github.com/go-ini/ini v1.32.0 h1:/MArBHSS0TFR28yPPDK1vPIjt4wUnPBfb81i6iiyKvA=
 github.com/go-ini/ini v1.32.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
+github.com/go-ini/ini v1.42.0 h1:TWr1wGj35+UiWHlBA8er89seFXxzwFn11spilrrj+38=
+github.com/go-ini/ini v1.42.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
 github.com/kyoh86/xdg v0.0.0-20171127140545-8db68a8ea76a h1:vLFQnHOnCnmlySdpHAKF+mH7MhsthJgpBbfexVhHwxY=
@@ -21,5 +25,10 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
 github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/nsf/termbox-go v0.0.0-20180129072728-88b7b944be8b/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/widgets/exline.go b/widgets/exline.go
index e0954d7..77f1414 100644
--- a/widgets/exline.go
+++ b/widgets/exline.go
@@ -126,10 +126,8 @@ func (ex *ExLine) Event(event tcell.Event) bool {
 		case tcell.KeyEsc, tcell.KeyCtrlC:
 			ex.ctx.HideCursor()
 			ex.cancel()
-		default:
-			if event.Rune() != 0 {
-				ex.insert(event.Rune())
-			}
+		case tcell.KeyRune:
+			ex.insert(event.Rune())
 		}
 	}
 	return true