Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maxlines option on views #30

Open
A-UNDERSCORE-D opened this issue Jul 18, 2019 · 4 comments
Open

Maxlines option on views #30

A-UNDERSCORE-D opened this issue Jul 18, 2019 · 4 comments
Labels
enhancement New feature or request

Comments

@A-UNDERSCORE-D
Copy link

A-UNDERSCORE-D commented Jul 18, 2019

Describe the feature you'd like
The ability to clear out the internal buffers either up to the point that the data is being displayed again, or to some arbitrary point provided in the constructor.

There was an issue for something like this on the original repo: jroimartin#103 and an associated PR: jroimartin#104

Describe alternatives you've considered
Ive tried doing this myself with Clear() and an Fprintf:

		_, y := dataView.Size()
		if dataView.LinesHeight() > y*2 {
			lines := getLast(dataView.BufferLines(), y*1)
			dataView.Clear()
			dataView.Title = fmt.Sprintf("cleanup done at %s", time.Now())
			fmt.Fprint(dataView, strings.Join(lines, "\n"))
		}
// ----------------------
func getLast(ls []string, last int) []string {
	out := make([]string, 0, last)
	if len(ls) < last {
		// fast path, we're not trimming anything
		copy(out, ls)
		return out
	}
	// we need to chomp
	copy(out, ls[len(ls)-last:])
	return out
}

But Clear() seems far to slow to make this work seamlessly. Not to mention that this requires significant extra processing and memory usage to convert the lines out of their internal representation and back.

Reflection magic:

			dv := reflect.Indirect(reflect.ValueOf(dataView))

			buffer := dv.FieldByName("lines")
			buffer = reflect.NewAt(buffer.Type(), unsafe.Pointer(buffer.UnsafeAddr())).Elem()
			l := buffer.Len()
			buffer.Set(buffer.Slice(l-y, l))

			tainted := dv.FieldByName("tainted")
			tainted = reflect.NewAt(tainted.Type(), unsafe.Pointer(tainted.UnsafeAddr())).Elem()
			tainted.Set(reflect.ValueOf(true))

			dataView.Title = fmt.Sprintf("cleanup done at %s", time.Now())
		}
	}

I dont think I really need to point out the issues with this approach. It does work, but reflection to this degree is in general a dangerous idea, and is brittle at best.

Additional context
The use case here is a long running TUI that is constantly written to, for me specifically that is for my game management IRC bot's TUI. As that has near constant writes from game server logs.

@A-UNDERSCORE-D A-UNDERSCORE-D added the enhancement New feature or request label Jul 18, 2019
@glvr182
Copy link
Member

glvr182 commented Jul 23, 2019

I agree that using reflect this much would be unwise, I am not really sure how to go about this after checking the linked issue and PR, linking @jesseduffield @skanehira @mjarkk for feedback

@mjarkk
Copy link
Member

mjarkk commented Jul 26, 2019

Agree!
Your solutions seems way to complected (but necessary to get it working).
From the original issue this seems like something more users have so i think it's a good idea to add support for this.

@gethiox
Copy link

gethiox commented Jul 3, 2022

This would be really useful, for now it's unusable for logging purpose, especially when application is supposed to run for a longer time.
My temporary solution is to use fixed-sized buffer outside of the view, and overwriting the view with my data which effectively prevents internal buffer from growing.

Simplified example:

type logBuffer struct {
	buffer         [][]byte
	size, position int
}

func newLogBuffer(size int) logBuffer {
	return logBuffer{
		buffer:   make([][]byte, size),
		size:     size,
		position: 0,
	}
}

func (b *logBuffer) WriteMessage(message []byte) {
	b.buffer[b.position] = message
	if b.position+1 == b.size {
		b.position = 0
	} else {
		b.position++
	}
}

func (b *logBuffer) ReadLastMessages(n int) [][]byte {
	if n > b.size {
		n = b.size
	}
	var data = make([][]byte, 0)
	for i := n; i > 0; i-- {
		data = append(data, b.buffer[((b.position-i)%b.size+b.size)%b.size])
	}
	return data
}

Note: it requires additional communication when new messages are available in the buffer and filling up all unused horizontal space of view when writing to view to prevent displaying not overwritten cells.
I'm using fixed-sized output messages according to horizontal view size, wrapping capability might require additional care.

_, y := view.Size()
for _, msg := range buf.ReadLastMessages(y) {
	view.Write(msg)
	view.Write([]byte{'\n'})
}

However, my solution is relatively more inefficient on CPU than internal view buffer as whole data needs to be processed and converted into [][]cell format every time the view is being updated that way, and for the platform that I'm currently working with it's pretty significant, but good enough.

I wonder how much work it would take to implement something like this for internal buffer.

@dankox
Copy link

dankox commented Jul 3, 2022

This is an old issue which I guess got forgotten, so I just want to clarify what is requested.

We would want something like View.MaxLines field, which would specify what is the maximum amount of lines available for a View and if new lines would be added at the end, lines from the beginning would be removed.

However, what should happen when lines would be added to the beginning or middle and the amount would cause them to be removed because of hitting MaxLines?
Should we just ignore this and maybe document it? Or should it behave differently?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants