Fix wide characters in input field and prepare for tab completion
This commit is contained in:
parent
4196931e1b
commit
dacc7fd6a3
@ -75,9 +75,11 @@ type AdvancedInputField struct {
|
|||||||
changed func(text string)
|
changed func(text string)
|
||||||
|
|
||||||
// An optional function which is called when the user indicated that they
|
// An optional function which is called when the user indicated that they
|
||||||
// are done entering text. The key which was pressed is provided (tab,
|
// are done entering text. The key which was pressed is provided (enter or escape).
|
||||||
// shift-tab, enter, or escape).
|
|
||||||
done func(tcell.Key)
|
done func(tcell.Key)
|
||||||
|
|
||||||
|
// An optional function which is called when the user presses tab.
|
||||||
|
tabComplete func(text string, cursorOffset int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdvancedInputField returns a new input field.
|
// NewAdvancedInputField returns a new input field.
|
||||||
@ -198,13 +200,16 @@ func (field *AdvancedInputField) SetChangedFunc(handler func(text string)) *Adva
|
|||||||
//
|
//
|
||||||
// - KeyEnter: Done entering text.
|
// - KeyEnter: Done entering text.
|
||||||
// - KeyEscape: Abort text input.
|
// - KeyEscape: Abort text input.
|
||||||
// - KeyTab: Move to the next field.
|
|
||||||
// - KeyBacktab: Move to the previous field.
|
|
||||||
func (field *AdvancedInputField) SetDoneFunc(handler func(key tcell.Key)) *AdvancedInputField {
|
func (field *AdvancedInputField) SetDoneFunc(handler func(key tcell.Key)) *AdvancedInputField {
|
||||||
field.done = handler
|
field.done = handler
|
||||||
return field
|
return field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (field *AdvancedInputField) SetTabCompleteFunc(handler func(text string, cursorOffset int)) *AdvancedInputField {
|
||||||
|
field.tabComplete = handler
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
// SetFinishedFunc calls SetDoneFunc().
|
// SetFinishedFunc calls SetDoneFunc().
|
||||||
func (field *AdvancedInputField) SetFinishedFunc(handler func(key tcell.Key)) tview.FormItem {
|
func (field *AdvancedInputField) SetFinishedFunc(handler func(key tcell.Key)) tview.FormItem {
|
||||||
return field.SetDoneFunc(handler)
|
return field.SetDoneFunc(handler)
|
||||||
@ -255,9 +260,9 @@ func (field *AdvancedInputField) Draw(screen tcell.Screen) {
|
|||||||
// Recalculate view offset
|
// Recalculate view offset
|
||||||
if field.cursorOffset < field.viewOffset {
|
if field.cursorOffset < field.viewOffset {
|
||||||
field.viewOffset = field.cursorOffset
|
field.viewOffset = field.cursorOffset
|
||||||
} else if field.cursorOffset > field.viewOffset + fieldWidth {
|
} else if field.cursorOffset > field.viewOffset+fieldWidth {
|
||||||
field.viewOffset = field.cursorOffset - fieldWidth
|
field.viewOffset = field.cursorOffset - fieldWidth
|
||||||
} else if textWidth - field.viewOffset < fieldWidth {
|
} else if textWidth-field.viewOffset < fieldWidth {
|
||||||
field.viewOffset = textWidth - fieldWidth
|
field.viewOffset = textWidth - fieldWidth
|
||||||
}
|
}
|
||||||
// Make sure view offset didn't become negative
|
// Make sure view offset didn't become negative
|
||||||
@ -268,7 +273,7 @@ func (field *AdvancedInputField) Draw(screen tcell.Screen) {
|
|||||||
// Draw entered text.
|
// Draw entered text.
|
||||||
runes := []rune(text)
|
runes := []rune(text)
|
||||||
relPos := 0
|
relPos := 0
|
||||||
for pos := field.viewOffset; pos <= fieldWidth + field.viewOffset && pos < len(runes); pos++ {
|
for pos := field.viewOffset; pos <= fieldWidth+field.viewOffset && pos < len(runes); pos++ {
|
||||||
ch := runes[pos]
|
ch := runes[pos]
|
||||||
w := runewidth.RuneWidth(ch)
|
w := runewidth.RuneWidth(ch)
|
||||||
_, _, style, _ := screen.GetContent(x+relPos, y)
|
_, _, style, _ := screen.GetContent(x+relPos, y)
|
||||||
@ -331,6 +336,10 @@ var (
|
|||||||
firstWord = regexp.MustCompile(`^\s*\S+`)
|
firstWord = regexp.MustCompile(`^\s*\S+`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func SubstringBefore(s string, w int) string {
|
||||||
|
return runewidth.Truncate(s, w, "")
|
||||||
|
}
|
||||||
|
|
||||||
// InputHandler returns the handler for this primitive.
|
// InputHandler returns the handler for this primitive.
|
||||||
func (field *AdvancedInputField) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
func (field *AdvancedInputField) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||||
return field.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
return field.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||||
@ -353,57 +362,76 @@ func (field *AdvancedInputField) InputHandler() func(event *tcell.EventKey, setF
|
|||||||
// Process key event.
|
// Process key event.
|
||||||
switch key := event.Key(); key {
|
switch key := event.Key(); key {
|
||||||
case tcell.KeyRune: // Regular character.
|
case tcell.KeyRune: // Regular character.
|
||||||
runes := []rune(field.text)
|
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
||||||
newText := string(runes[0:field.cursorOffset]) + string(event.Rune()) + string(runes[field.cursorOffset:])
|
newText := leftPart + string(event.Rune()) + field.text[len(leftPart):]
|
||||||
if field.accept != nil {
|
if field.accept != nil {
|
||||||
if !field.accept(newText, event.Rune()) {
|
if !field.accept(newText, event.Rune()) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
field.text = newText
|
field.text = newText
|
||||||
field.cursorOffset++
|
field.cursorOffset += runewidth.RuneWidth(event.Rune())
|
||||||
case tcell.KeyCtrlV:
|
case tcell.KeyCtrlV:
|
||||||
clip, _ := clipboard.ReadAll("clipboard")
|
clip, _ := clipboard.ReadAll("clipboard")
|
||||||
runes := []rune(field.text)
|
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
||||||
field.text = string(runes[0:field.cursorOffset]) + clip + string(runes[field.cursorOffset:])
|
field.text = leftPart + clip + field.text[len(leftPart):]
|
||||||
field.cursorOffset += runewidth.StringWidth(clip)
|
field.cursorOffset += runewidth.StringWidth(clip)
|
||||||
case tcell.KeyLeft: // Move cursor left.
|
case tcell.KeyLeft: // Move cursor left.
|
||||||
|
before := SubstringBefore(field.text, field.cursorOffset)
|
||||||
if event.Modifiers() == tcell.ModCtrl {
|
if event.Modifiers() == tcell.ModCtrl {
|
||||||
runes := []rune(field.text)
|
found := lastWord.FindString(before)
|
||||||
found := lastWord.FindString(string(runes[0:field.cursorOffset]))
|
|
||||||
field.cursorOffset -= runewidth.StringWidth(found)
|
field.cursorOffset -= runewidth.StringWidth(found)
|
||||||
} else {
|
} else if len(before) > 0 {
|
||||||
field.cursorOffset--
|
beforeRunes := []rune(before)
|
||||||
|
char := beforeRunes[len(beforeRunes)-1]
|
||||||
|
field.cursorOffset -= runewidth.RuneWidth(char)
|
||||||
}
|
}
|
||||||
case tcell.KeyRight: // Move cursor right.
|
case tcell.KeyRight: // Move cursor right.
|
||||||
|
before := SubstringBefore(field.text, field.cursorOffset)
|
||||||
|
after := field.text[len(before):]
|
||||||
if event.Modifiers() == tcell.ModCtrl {
|
if event.Modifiers() == tcell.ModCtrl {
|
||||||
runes := []rune(field.text)
|
found := firstWord.FindString(after)
|
||||||
found := firstWord.FindString(string(runes[field.cursorOffset:]))
|
|
||||||
field.cursorOffset += runewidth.StringWidth(found)
|
field.cursorOffset += runewidth.StringWidth(found)
|
||||||
} else {
|
} else if len(after) > 0 {
|
||||||
field.cursorOffset++
|
char := []rune(after)[0]
|
||||||
|
field.cursorOffset += runewidth.RuneWidth(char)
|
||||||
}
|
}
|
||||||
case tcell.KeyDelete: // Delete next character.
|
case tcell.KeyDelete: // Delete next character.
|
||||||
if field.cursorOffset >= runewidth.StringWidth(field.text) {
|
if field.cursorOffset >= runewidth.StringWidth(field.text) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
runes := []rune(field.text)
|
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
||||||
field.text = string(runes[0:field.cursorOffset]) + string(runes[field.cursorOffset + 1:])
|
rightPart := field.text[len(leftPart):]
|
||||||
|
rightPartRunes := []rune(rightPart)
|
||||||
|
rightPartRunes = rightPartRunes[1:]
|
||||||
|
rightPart = string(rightPartRunes)
|
||||||
|
field.text = leftPart + rightPart
|
||||||
case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete last character.
|
case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete last character.
|
||||||
if field.cursorOffset == 0 {
|
if field.cursorOffset == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
runes := []rune(field.text)
|
|
||||||
if key == tcell.KeyBackspace { // Ctrl+backspace
|
if key == tcell.KeyBackspace { // Ctrl+backspace
|
||||||
orig := string(runes[0:field.cursorOffset])
|
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
||||||
replacement := lastWord.ReplaceAllString(orig, "")
|
rightPart := field.text[len(leftPart):]
|
||||||
field.text = replacement + string(runes[field.cursorOffset:])
|
replacement := lastWord.ReplaceAllString(leftPart, "")
|
||||||
field.cursorOffset -= runewidth.StringWidth(orig) - runewidth.StringWidth(replacement)
|
field.text = replacement + rightPart
|
||||||
|
|
||||||
|
field.cursorOffset -= runewidth.StringWidth(leftPart) - runewidth.StringWidth(replacement)
|
||||||
} else { // Just backspace
|
} else { // Just backspace
|
||||||
field.text = string(runes[0:field.cursorOffset - 1]) + string(runes[field.cursorOffset:])
|
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
||||||
field.cursorOffset--
|
rightPart := field.text[len(leftPart):]
|
||||||
|
leftPartRunes := []rune(leftPart)
|
||||||
|
leftPartRunes = leftPartRunes[0 : len(leftPartRunes)-1]
|
||||||
|
leftPart = string(leftPartRunes)
|
||||||
|
removedChar := field.text[len(leftPart) : len(field.text)-len(rightPart)]
|
||||||
|
field.text = leftPart + rightPart
|
||||||
|
field.cursorOffset -= runewidth.StringWidth(removedChar)
|
||||||
}
|
}
|
||||||
case tcell.KeyEnter, tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done.
|
case tcell.KeyTab: // Tab-completion
|
||||||
|
if field.tabComplete != nil {
|
||||||
|
field.tabComplete(field.text, field.cursorOffset)
|
||||||
|
}
|
||||||
|
case tcell.KeyEnter, tcell.KeyEscape: // We're done.
|
||||||
if field.done != nil {
|
if field.done != nil {
|
||||||
field.done(key)
|
field.done(key)
|
||||||
}
|
}
|
||||||
|
@ -45,12 +45,15 @@ type gomuks struct {
|
|||||||
config *Config
|
config *Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gdebug DebugPrinter
|
||||||
|
|
||||||
func NewGomuks(debug bool) *gomuks {
|
func NewGomuks(debug bool) *gomuks {
|
||||||
configDir := filepath.Join(os.Getenv("HOME"), ".config/gomuks")
|
configDir := filepath.Join(os.Getenv("HOME"), ".config/gomuks")
|
||||||
gmx := &gomuks{
|
gmx := &gomuks{
|
||||||
app: tview.NewApplication(),
|
app: tview.NewApplication(),
|
||||||
}
|
}
|
||||||
gmx.debug = NewDebugPane(gmx)
|
gmx.debug = NewDebugPane(gmx)
|
||||||
|
gdebug = gmx.debug
|
||||||
gmx.config = NewConfig(gmx, configDir)
|
gmx.config = NewConfig(gmx, configDir)
|
||||||
gmx.ui = NewGomuksUI(gmx)
|
gmx.ui = NewGomuksUI(gmx)
|
||||||
gmx.matrix = NewMatrixContainer(gmx)
|
gmx.matrix = NewMatrixContainer(gmx)
|
||||||
|
12
view-main.go
12
view-main.go
@ -65,11 +65,14 @@ func (ui *GomuksUI) NewMainView() tview.Primitive {
|
|||||||
|
|
||||||
mainView.roomList.
|
mainView.roomList.
|
||||||
ShowSecondaryText(false).
|
ShowSecondaryText(false).
|
||||||
|
SetSelectedBackgroundColor(tcell.ColorDarkGreen).
|
||||||
|
SetSelectedTextColor(tcell.ColorWhite).
|
||||||
SetBorderPadding(0, 0, 1, 0)
|
SetBorderPadding(0, 0, 1, 0)
|
||||||
|
|
||||||
mainView.input.
|
mainView.input.
|
||||||
SetDoneFunc(mainView.InputDone).
|
SetDoneFunc(mainView.InputDone).
|
||||||
SetChangedFunc(mainView.InputChanged).
|
SetChangedFunc(mainView.InputChanged).
|
||||||
|
SetTabCompleteFunc(mainView.InputTabComplete).
|
||||||
SetFieldBackgroundColor(tcell.ColorDefault).
|
SetFieldBackgroundColor(tcell.ColorDefault).
|
||||||
SetPlaceholder("Send a message...").
|
SetPlaceholder("Send a message...").
|
||||||
SetPlaceholderExtColor(tcell.ColorGray).
|
SetPlaceholderExtColor(tcell.ColorGray).
|
||||||
@ -93,6 +96,13 @@ func (view *MainView) InputChanged(text string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (view *MainView) InputTabComplete(text string, cursorOffset int) {
|
||||||
|
roomView, _ := view.rooms[view.CurrentRoomID()]
|
||||||
|
if roomView != nil {
|
||||||
|
// text[0:cursorOffset]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (view *MainView) InputDone(key tcell.Key) {
|
func (view *MainView) InputDone(key tcell.Key) {
|
||||||
if key == tcell.KeyEnter {
|
if key == tcell.KeyEnter {
|
||||||
room, text := view.CurrentRoomID(), view.input.GetText()
|
room, text := view.CurrentRoomID(), view.input.GetText()
|
||||||
@ -207,7 +217,7 @@ func (view *MainView) AddRoom(room string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
view.roomIDs = append(view.roomIDs, room)
|
view.roomIDs = append(view.roomIDs, room)
|
||||||
view.addRoom(len(view.roomIDs) - 1, room)
|
view.addRoom(len(view.roomIDs)-1, room)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) RemoveRoom(room string) {
|
func (view *MainView) RemoveRoom(room string) {
|
||||||
|
Loading…
Reference in New Issue
Block a user