[GH-ISSUE #838] How can I show an InputField at the position of the current list item? #607

Open
opened 2026-03-04 01:06:25 +03:00 by kerem · 2 comments
Owner

Originally created by @quantonganh on GitHub (Apr 8, 2023).
Original GitHub issue: https://github.com/rivo/tview/issues/838

Hi,

I'm building something like this:

package main

import (
	"strconv"

	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
)

func main() {
	app := tview.NewApplication()
	button := tview.NewButton("+ New chat")
	button.SetBorder(true)

	list := tview.NewList()
	list.SetTitle("History").SetBorder(true)
	for i := 0; i < 50; i++ {
		list.AddItem("item "+strconv.FormatInt(int64(i), 10), "", rune(0), nil)
	}
	list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Rune() {
		case 'j':
			if list.GetCurrentItem() < list.GetItemCount() {
				list.SetCurrentItem(list.GetCurrentItem() + 1)
			}
		case 'k':
			if list.GetCurrentItem() > 0 {
				list.SetCurrentItem(list.GetCurrentItem() - 1)
			}
		}
		return event
	})

	textView := tview.NewTextView()
	textView.SetTitle("Conversation").SetBorder(true)

	textArea := tview.NewTextArea()
	textArea.SetTitle("Question").SetBorder(true)

	mainFlex := tview.NewFlex().SetDirection(tview.FlexRow).
		AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
			AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
				AddItem(button, 3, 1, false).
				AddItem(list, 0, 1, false), 0, 1, false).
			AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
				AddItem(textView, 0, 1, false).
				AddItem(textArea, 5, 1, false), 0, 3, false), 0, 1, false)
	if err := app.SetRoot(mainFlex, true).SetFocus(list).Run(); err != nil {
		panic(err)
	}
}

I want to allow to edit the list item: for e.g, when user press e, I want to show an InputField at the position of the list item. To do that, I can create a Modal on top of this layout, will all items nil except for the one at the list item's position:

	modal := func(p tview.Primitive, currentIndex int) tview.Primitive {
		return tview.NewFlex().SetDirection(tview.FlexRow).
			AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
				AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
					AddItem(nil, 4+(currentIndex*2), 1, false).
					AddItem(p, 1, 1, true).
					AddItem(nil, 0, 1, false), 0, 1, true).
				AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
					AddItem(nil, 0, 1, false).
					AddItem(nil, 5, 1, false), 0, 3, false), 0, 1, true)
	}

and then I can call it in the SetInputCapture func:

	list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		case 'e':
			pages.AddPage(pageEditTitle, modal(editTitleInputField, list.GetCurrentItem()), true, false)

https://gist.github.com/quantonganh/88d14214cffcf226cfcd7bea5a17ecf3

If the list is short, it will be ok. But if the current index is greater than the height of the screen / 2 (there is a blank line between each list item), it shows nothing as it is out of the screen.

Let me give you an example:

Screen Shot 2023-04-08 at 9 21 07 PM

Please look at the above screenshot: if I scroll down, then up to hide some top items, then I stop at the item that has index 5 and press e, the InputField will be shown at the position of "item 8".

So, how can I show an InputField at the position of the current list item?

If I can get the position of the "item 5" on the screen (2 in this case, if we start from 0 - "item 3", 1 - "item 4"), then I can show the InputField at line: 4 + 2*2 = 8 (and it will be matched, since the "New chat" button has height of 3, the "item 5" is at line 5).

Originally created by @quantonganh on GitHub (Apr 8, 2023). Original GitHub issue: https://github.com/rivo/tview/issues/838 Hi, I'm building something like this: ```go package main import ( "strconv" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) func main() { app := tview.NewApplication() button := tview.NewButton("+ New chat") button.SetBorder(true) list := tview.NewList() list.SetTitle("History").SetBorder(true) for i := 0; i < 50; i++ { list.AddItem("item "+strconv.FormatInt(int64(i), 10), "", rune(0), nil) } list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { switch event.Rune() { case 'j': if list.GetCurrentItem() < list.GetItemCount() { list.SetCurrentItem(list.GetCurrentItem() + 1) } case 'k': if list.GetCurrentItem() > 0 { list.SetCurrentItem(list.GetCurrentItem() - 1) } } return event }) textView := tview.NewTextView() textView.SetTitle("Conversation").SetBorder(true) textArea := tview.NewTextArea() textArea.SetTitle("Question").SetBorder(true) mainFlex := tview.NewFlex().SetDirection(tview.FlexRow). AddItem(tview.NewFlex().SetDirection(tview.FlexColumn). AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(button, 3, 1, false). AddItem(list, 0, 1, false), 0, 1, false). AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(textView, 0, 1, false). AddItem(textArea, 5, 1, false), 0, 3, false), 0, 1, false) if err := app.SetRoot(mainFlex, true).SetFocus(list).Run(); err != nil { panic(err) } } ``` I want to allow to edit the list item: for e.g, when user press `e`, I want to show an [InputField](https://github.com/rivo/tview/wiki/InputField) at the position of the list item. To do that, I can create a [Modal](https://github.com/rivo/tview/wiki/Modal) on top of this layout, will all items nil except for the one at the list item's position: ```go modal := func(p tview.Primitive, currentIndex int) tview.Primitive { return tview.NewFlex().SetDirection(tview.FlexRow). AddItem(tview.NewFlex().SetDirection(tview.FlexColumn). AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(nil, 4+(currentIndex*2), 1, false). AddItem(p, 1, 1, true). AddItem(nil, 0, 1, false), 0, 1, true). AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(nil, 0, 1, false). AddItem(nil, 5, 1, false), 0, 3, false), 0, 1, true) } ``` and then I can call it in the `SetInputCapture` func: ```go list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { case 'e': pages.AddPage(pageEditTitle, modal(editTitleInputField, list.GetCurrentItem()), true, false) ``` https://gist.github.com/quantonganh/88d14214cffcf226cfcd7bea5a17ecf3 If the list is short, it will be ok. But if the current index is greater than the height of the screen / 2 (there is a blank line between each list item), it shows nothing as it is out of the screen. Let me give you an example: <img width="468" alt="Screen Shot 2023-04-08 at 9 21 07 PM" src="https://user-images.githubusercontent.com/1568504/230726267-bcc20a71-2ca7-4467-96b1-1d52a8dc4aff.png"> Please look at the above screenshot: if I scroll down, then up to hide some top items, then I stop at the item that has index 5 and press `e`, the InputField will be shown at the position of "item 8". So, how can I show an InputField at the position of the current list item? If I can get the position of the "item 5" on the screen (2 in this case, if we start from 0 - "item 3", 1 - "item 4"), then I can show the InputField at line: 4 + 2*2 = 8 (and it will be matched, since the "New chat" button has height of 3, the "item 5" is at line 5).
Author
Owner

@quantonganh commented on GitHub (Apr 11, 2023):

I solved it by counting how many items are hidden base on the height of list:

	var hiddenItemCount int
	list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		_, _, _, height := list.GetInnerRect()

		switch event.Rune() {
		case 'j':
			if list.GetCurrentItem() < list.GetItemCount() {
				list.SetCurrentItem(list.GetCurrentItem() + 1)
			}

			if list.GetCurrentItem() >= height/2 {
				hiddenItemCount = list.GetCurrentItem() + 1 - (height / 2)
			}
		case 'k':
			if list.GetCurrentItem() > 0 {
				list.SetCurrentItem(list.GetCurrentItem() - 1)
			}

			if list.GetCurrentItem()+1 == hiddenItemCount {
				hiddenItemCount--
			}
		case 'e':
			pages.AddPage(pageEditTitle, modal(editTitleInputField, list.GetCurrentItem()-hiddenItemCount), true, false)
<!-- gh-comment-id:1502539683 --> @quantonganh commented on GitHub (Apr 11, 2023): I solved it by counting how many items are hidden base on the height of list: ```go var hiddenItemCount int list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { _, _, _, height := list.GetInnerRect() switch event.Rune() { case 'j': if list.GetCurrentItem() < list.GetItemCount() { list.SetCurrentItem(list.GetCurrentItem() + 1) } if list.GetCurrentItem() >= height/2 { hiddenItemCount = list.GetCurrentItem() + 1 - (height / 2) } case 'k': if list.GetCurrentItem() > 0 { list.SetCurrentItem(list.GetCurrentItem() - 1) } if list.GetCurrentItem()+1 == hiddenItemCount { hiddenItemCount-- } case 'e': pages.AddPage(pageEditTitle, modal(editTitleInputField, list.GetCurrentItem()-hiddenItemCount), true, false) ```
Author
Owner

@rivo commented on GitHub (Jun 18, 2023):

This is probably more complicated when the list is longer than the screen height and there is scrolling involved.

There is currently no solution to this. Eventually, I might add a function to return the current selection's screen position. It will be needed in Table, TreeView, and others as well.

But for now, there is nothing like this in tview.

<!-- gh-comment-id:1596116080 --> @rivo commented on GitHub (Jun 18, 2023): This is probably more complicated when the list is longer than the screen height and there is scrolling involved. There is currently no solution to this. Eventually, I might add a function to return the current selection's screen position. It will be needed in `Table`, `TreeView`, and others as well. But for now, there is nothing like this in `tview`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/tview#607
No description provided.