tview icon indicating copy to clipboard operation
tview copied to clipboard

Keep Indentation when wrapping text

Open Bios-Marcel opened this issue 5 years ago • 8 comments

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

Bios-Marcel avatar Jul 20 '19 19:07 Bios-Marcel

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.

rivo avatar Jul 21 '19 13:07 rivo

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.

Bios-Marcel avatar Jul 21 '19 13:07 Bios-Marcel

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

rivo avatar Jul 21 '19 13:07 rivo

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?

calzoneman avatar Apr 12 '21 03:04 calzoneman

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

Bios-Marcel avatar Apr 12 '21 06:04 Bios-Marcel

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.)

rivo avatar Apr 12 '21 11:04 rivo

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

FT-Labs avatar Aug 29 '22 20:08 FT-Labs

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
}

rivo avatar Dec 12 '22 06:12 rivo