[GH-ISSUE #325] Keep Indentation when wrapping text #247

Open
opened 2026-03-04 01:03:20 +03:00 by kerem · 8 comments
Owner

Originally created by @Bios-Marcel on GitHub (Jul 20, 2019).
Original GitHub issue: https://github.com/rivo/tview/issues/325

Sometimes it would be very useful to keep the indentation when wrapping text, I have noticed this when trying to indent complete text blocks in order to make them more readable.

For example I got this:

[::b]DESCRPTION
	This command allows you to manipulate and retrieve your user information.

	This command is split into multiple subcommands. The default subcommand
	is [::b]user-get[::-] and will be used if no other command was supplied.

if it linebreaks, it will look like this:

[::b]DESCRPTION
	This command allows you to manipulate and retrieve your user information.

	This command is split into multiple subcommands. The default
subcommand is [::b]user-get[::-] and will be used if no other command was supplied.

So the rule should be, that when the current line has indentation in the front, the wrapped content will as well.

This should be optional and disabled by default

Originally created by @Bios-Marcel on GitHub (Jul 20, 2019). Original GitHub issue: https://github.com/rivo/tview/issues/325 Sometimes it would be very useful to keep the indentation when wrapping text, I have noticed this when trying to indent complete text blocks in order to make them more readable. For example I got this: ``` [::b]DESCRPTION This command allows you to manipulate and retrieve your user information. This command is split into multiple subcommands. The default subcommand is [::b]user-get[::-] and will be used if no other command was supplied. ``` if it linebreaks, it will look like this: ``` [::b]DESCRPTION This command allows you to manipulate and retrieve your user information. This command is split into multiple subcommands. The default subcommand is [::b]user-get[::-] and will be used if no other command was supplied. ``` So the rule should be, that when the current line has indentation in the front, the wrapped content will as well. This should be optional and disabled by default
Author
Owner

@rivo commented on GitHub (Jul 21, 2019):

I'm not sure I will add this. This is also not the wrapping behaviour of common word processors. Also check out man where you'll commonly see indented text. But it also doesn't break the lines like you suggest.

I just spent many hours rewriting the WordWrap() function. This is a lot more complicated than one would expect. You suggestion would add a considerable amount of complexity on top, which is what I'm trying to avoid. And I'm not sure it would benefit a lot of people.

You may want to think about turning off line breaking and manually formatting for a fixed width.

<!-- gh-comment-id:513555940 --> @rivo commented on GitHub (Jul 21, 2019): I'm not sure I will add this. This is also not the wrapping behaviour of common word processors. Also check out `man` where you'll commonly see indented text. But it also doesn't break the lines like you suggest. I just spent many hours rewriting the `WordWrap()` function. This is a lot more complicated than one would expect. You suggestion would add a considerable amount of complexity on top, which is what I'm trying to avoid. And I'm not sure it would benefit a lot of people. You may want to think about turning off line breaking and manually formatting for a fixed width.
Author
Owner

@Bios-Marcel commented on GitHub (Jul 21, 2019):

Yeah, that's what I have done so far, but it kinda wastes vertical space, so I thought this might be very handy. I am not sure whether many people would use this or not. However, I understand if you don't want to add this because it's not worth the trade-offs.

<!-- gh-comment-id:513556085 --> @Bios-Marcel commented on GitHub (Jul 21, 2019): Yeah, that's what I have done so far, but it kinda wastes vertical space, so I thought this might be very handy. I am not sure whether many people would use this or not. However, I understand if you don't want to add this because it's not worth the trade-offs.
Author
Owner

@rivo commented on GitHub (Jul 21, 2019):

I will keep this in mind. If other people need this, too, we can revisit this topic.

<!-- gh-comment-id:513556701 --> @rivo commented on GitHub (Jul 21, 2019): I will keep this in mind. If other people need this, too, we can revisit this topic.
Author
Owner

@calzoneman commented on GitHub (Apr 12, 2021):

I ran into a similar, but not quite identical, desire while building a chat application using tview (when prefixing messages with a timestamp/username, I prefer when the message does not wrap around underneath the username/timestamp, but is rather indented to align with the start of the message, the way weechat does it).

Regarding your suggestion to format for fixed-width, is there a convenient way to hook into component size changes to reflow the text? Or perhaps would it be better to just write a custom component instead of using TextView for this?

<!-- gh-comment-id:817458251 --> @calzoneman commented on GitHub (Apr 12, 2021): I ran into a similar, but not quite identical, desire while building a chat application using tview (when prefixing messages with a timestamp/username, I prefer when the message does not wrap around underneath the username/timestamp, but is rather indented to align with the start of the message, the way weechat does it). Regarding your suggestion to format for fixed-width, is there a convenient way to hook into component size changes to reflow the text? Or perhaps would it be better to just write a custom component instead of using TextView for this?
Author
Owner

@Bios-Marcel commented on GitHub (Apr 12, 2021):

I recommend a custom component. I had a lot of problems that I had to hack around. Performance was bad too.

EDIT

I am not talking about reindentation, but general use as a chat view.
I am assuming you'll have a multitude of features :D

<!-- gh-comment-id:817535042 --> @Bios-Marcel commented on GitHub (Apr 12, 2021): I recommend a custom component. I had a lot of problems that I had to hack around. Performance was bad too. **EDIT** I am not talking about reindentation, but general use as a chat view. I am assuming you'll have a multitude of features :D
Author
Owner

@rivo commented on GitHub (Apr 12, 2021):

I guess I could add a hook for custom reflows. I would prefer that to implementing some automatic indentation detection that may lead to undesirable side effects in other contexts. Basically, I would add the option to supply your own version of the reindexBuffer(width int) function. Let me tell you, though, that word wrapping is not simple, especially when you have to deal with Unicode.

Let me know what if that would help you.

(Reopening for now.)

<!-- gh-comment-id:817738398 --> @rivo commented on GitHub (Apr 12, 2021): I guess I could add a hook for custom reflows. I would prefer that to implementing some automatic indentation detection that may lead to undesirable side effects in other contexts. Basically, I would add the option to supply your own version of the [`reindexBuffer(width int)`](https://github.com/rivo/tview/blob/ae9464cc3598d81fff6813af3e28f292a9de6c15/textview.go#L695) function. Let me tell you, though, that word wrapping is not simple, especially when you have to deal with Unicode. Let me know what if that would help you. (Reopening for now.)
Author
Owner

@r0-zero commented on GitHub (Aug 29, 2022):

If anyone who is still interested this, here is a small fix to keep indentation. I'm not sending a pull request, because I have changed lots of source code to fit my app. If you are okay with it, I will fork it again and make a clean commit for it. Anyways, here is the small fix:
Preserve indent on textview

Edit: Doesnt work with dynamic colors, trying to solve it

dynamic color fix
I think this solves it, but there is probably a lot better optimized way to handle this problem

<!-- gh-comment-id:1230835057 --> @r0-zero commented on GitHub (Aug 29, 2022): If anyone who is still interested this, here is a small fix to keep indentation. I'm not sending a pull request, because I have changed lots of source code to fit my app. If you are okay with it, I will fork it again and make a clean commit for it. Anyways, here is the small fix: [Preserve indent on textview](https://github.com/FT-Labs/tview/commit/c58e0803b61082c64be25e0a2d924c2502c5c3cd) Edit: Doesnt work with dynamic colors, trying to solve it [dynamic color fix](https://github.com/FT-Labs/tview/commit/cbcfb4a0ea9a1ea696efe5258b5bcba8daf563a4) I think this solves it, but there is probably a lot better optimized way to handle this problem
Author
Owner

@rivo commented on GitHub (Dec 12, 2022):

Coming back to this again and thinking about it some more, I don't think this will be added, at least not any time soon. TextView will see another rewrite in the near future (using the new segmenting algorithms of rivo/uniseg) and I believe the additional complexity of offering a custom reflow option is just not worth it. I actually believe it would be quite difficult for anyone who wanted to use it, too. (Consider all the Unicode quirks, color tags, and region tags, which would need to be handled.)

But I didn't want to simply close this thread so here's a simple implementation which formats text in a different way. It keeps the original text outside the component (thus, the built-in io.Writer interface cannot be used) but I suppose this could also be wrapped into its own class if needed.

package main

import (
	"strings"

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

const lorem = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam. "

func main() {
	t, str := newTextView()
	*str = strings.Repeat(lorem, 40)
	if err := tview.NewApplication().SetRoot(t, true).Run(); err != nil {
		panic(err)
	}
}

func newTextView() (*tview.TextView, *string) {
	var str string
	textView := tview.NewTextView().SetWrap(false)
	textView.SetBorder(true)
	textView.SetDrawFunc(func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) {
		// Let's apply our own formatting.
		x, y, width, height = textView.GetInnerRect()
		words := strings.Split(str, " ")
		var (
			b           strings.Builder
			row, column int
		)
		for _, word := range words {
			w := uniseg.StringWidth(word)
			if column+w >= width {
				row++
				if row%4 == 0 {
					b.WriteString("\n")
					column = 0
				} else {
					b.WriteString("\n    ")
					column = 4
				}
			}
			b.WriteString(word)
			b.WriteRune(' ')
			column += w + 1
		}
		textView.SetText(b.String())

		return x, y, width, height
	})
	return textView, &str
}
<!-- gh-comment-id:1345977748 --> @rivo commented on GitHub (Dec 12, 2022): Coming back to this again and thinking about it some more, I don't think this will be added, at least not any time soon. `TextView` will see another rewrite in the near future (using the new segmenting algorithms of [`rivo/uniseg`](https://github.com/rivo/uniseg)) and I believe the additional complexity of offering a custom reflow option is just not worth it. I actually believe it would be quite difficult for anyone who wanted to use it, too. (Consider all the Unicode quirks, color tags, and region tags, which would need to be handled.) But I didn't want to simply close this thread so here's a simple implementation which formats text in a different way. It keeps the original text outside the component (thus, the built-in `io.Writer` interface cannot be used) but I suppose this could also be wrapped into its own class if needed. ```go package main import ( "strings" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "github.com/rivo/uniseg" ) const lorem = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam. " func main() { t, str := newTextView() *str = strings.Repeat(lorem, 40) if err := tview.NewApplication().SetRoot(t, true).Run(); err != nil { panic(err) } } func newTextView() (*tview.TextView, *string) { var str string textView := tview.NewTextView().SetWrap(false) textView.SetBorder(true) textView.SetDrawFunc(func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) { // Let's apply our own formatting. x, y, width, height = textView.GetInnerRect() words := strings.Split(str, " ") var ( b strings.Builder row, column int ) for _, word := range words { w := uniseg.StringWidth(word) if column+w >= width { row++ if row%4 == 0 { b.WriteString("\n") column = 0 } else { b.WriteString("\n ") column = 4 } } b.WriteString(word) b.WriteRune(' ') column += w + 1 } textView.SetText(b.String()) return x, y, width, height }) return textView, &str } ```
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#247
No description provided.