mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-27 13:48:49 +08:00

See issue #293 where memory and performance can degrade with a high number of lines written to the Text widget. This is a very simplistic implementation to limit the possible length the text buffer can grow to with the `maxContent` option. Default value of -1 means there's no limit and therefore behaviour should remain standard. It has been working in our test app and allows the use of the Text widget to monitor logs (ie tail) and therefore doesn't bloat over time, but happy to adjust as required.
202 lines
5.9 KiB
Go
202 lines
5.9 KiB
Go
// Copyright 2018 Google Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package text
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/mum4k/termdash/keyboard"
|
|
"github.com/mum4k/termdash/mouse"
|
|
"github.com/mum4k/termdash/private/wrap"
|
|
)
|
|
|
|
// options.go contains configurable options for Text.
|
|
|
|
// Option is used to provide options to New().
|
|
type Option interface {
|
|
// set sets the provided option.
|
|
set(*options)
|
|
}
|
|
|
|
// options stores the provided options.
|
|
type options struct {
|
|
scrollUp rune
|
|
scrollDown rune
|
|
wrapMode wrap.Mode
|
|
rollContent bool
|
|
maxTextCells int
|
|
disableScrolling bool
|
|
mouseUpButton mouse.Button
|
|
mouseDownButton mouse.Button
|
|
keyUp keyboard.Key
|
|
keyDown keyboard.Key
|
|
keyPgUp keyboard.Key
|
|
keyPgDown keyboard.Key
|
|
}
|
|
|
|
// newOptions returns a new options instance.
|
|
func newOptions(opts ...Option) *options {
|
|
opt := &options{
|
|
scrollUp: DefaultScrollUpRune,
|
|
scrollDown: DefaultScrollDownRune,
|
|
mouseUpButton: DefaultScrollMouseButtonUp,
|
|
mouseDownButton: DefaultScrollMouseButtonDown,
|
|
keyUp: DefaultScrollKeyUp,
|
|
keyDown: DefaultScrollKeyDown,
|
|
keyPgUp: DefaultScrollKeyPageUp,
|
|
keyPgDown: DefaultScrollKeyPageDown,
|
|
maxTextCells: DefaultMaxTextCells,
|
|
}
|
|
for _, o := range opts {
|
|
o.set(opt)
|
|
}
|
|
return opt
|
|
}
|
|
|
|
// validate validates the provided options.
|
|
func (o *options) validate() error {
|
|
keys := map[keyboard.Key]bool{
|
|
o.keyUp: true,
|
|
o.keyDown: true,
|
|
o.keyPgUp: true,
|
|
o.keyPgDown: true,
|
|
}
|
|
if len(keys) != 4 {
|
|
return fmt.Errorf("invalid ScrollKeys(up:%v, down:%v, pageUp:%v, pageDown:%v), the keys must be unique", o.keyUp, o.keyDown, o.keyPgUp, o.keyPgDown)
|
|
}
|
|
if o.mouseUpButton == o.mouseDownButton {
|
|
return fmt.Errorf("invalid ScrollMouseButtons(up:%v, down:%v), the buttons must be unique", o.mouseUpButton, o.mouseDownButton)
|
|
}
|
|
if o.maxTextCells < 0 {
|
|
return fmt.Errorf("invalid MaxTextCells(%d), must be zero or a positive integer", o.maxTextCells)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// option implements Option.
|
|
type option func(*options)
|
|
|
|
// set implements Option.set.
|
|
func (o option) set(opts *options) {
|
|
o(opts)
|
|
}
|
|
|
|
// ScrollRunes configures the text widgets scroll runes, shown at the top and
|
|
// bottom of a scrollable text widget. If not provided, the default scroll
|
|
// runes will be used.
|
|
func ScrollRunes(up, down rune) Option {
|
|
return option(func(opts *options) {
|
|
opts.scrollUp = up
|
|
opts.scrollDown = down
|
|
})
|
|
}
|
|
|
|
// The default scroll runes for content scrolling
|
|
const (
|
|
DefaultScrollUpRune = '⇧'
|
|
DefaultScrollDownRune = '⇩'
|
|
)
|
|
|
|
// WrapAtWords configures the text widget so that it automatically wraps lines
|
|
// that are longer than the width of the widget at word boundaries. If not
|
|
// provided, long lines are trimmed instead.
|
|
func WrapAtWords() Option {
|
|
return option(func(opts *options) {
|
|
opts.wrapMode = wrap.AtWords
|
|
})
|
|
}
|
|
|
|
// WrapAtRunes configures the text widget so that it automatically wraps lines
|
|
// that are longer than the width of the widget at rune boundaries. If not
|
|
// provided, long lines are trimmed instead.
|
|
func WrapAtRunes() Option {
|
|
return option(func(opts *options) {
|
|
opts.wrapMode = wrap.AtRunes
|
|
})
|
|
}
|
|
|
|
// RollContent configures the text widget so that it rolls the text content up
|
|
// if more text than the size of the container is added. If not provided, the
|
|
// content is trimmed instead.
|
|
func RollContent() Option {
|
|
return option(func(opts *options) {
|
|
opts.rollContent = true
|
|
})
|
|
}
|
|
|
|
// DisableScrolling disables the scrolling of the content using keyboard and
|
|
// mouse.
|
|
func DisableScrolling() Option {
|
|
return option(func(opts *options) {
|
|
opts.disableScrolling = true
|
|
})
|
|
}
|
|
|
|
// The default mouse buttons for content scrolling.
|
|
const (
|
|
DefaultScrollMouseButtonUp = mouse.ButtonWheelUp
|
|
DefaultScrollMouseButtonDown = mouse.ButtonWheelDown
|
|
)
|
|
|
|
// ScrollMouseButtons configures the mouse buttons that scroll the content.
|
|
// The provided buttons must be unique, e.g. the same button cannot be both up
|
|
// and down.
|
|
func ScrollMouseButtons(up, down mouse.Button) Option {
|
|
return option(func(opts *options) {
|
|
opts.mouseUpButton = up
|
|
opts.mouseDownButton = down
|
|
})
|
|
}
|
|
|
|
// The default keys for content scrolling.
|
|
const (
|
|
DefaultScrollKeyUp = keyboard.KeyArrowUp
|
|
DefaultScrollKeyDown = keyboard.KeyArrowDown
|
|
DefaultScrollKeyPageUp = keyboard.KeyPgUp
|
|
DefaultScrollKeyPageDown = keyboard.KeyPgDn
|
|
)
|
|
|
|
// ScrollKeys configures the keyboard keys that scroll the content.
|
|
// The provided keys must be unique, e.g. the same key cannot be both up and
|
|
// down.
|
|
func ScrollKeys(up, down, pageUp, pageDown keyboard.Key) Option {
|
|
return option(func(opts *options) {
|
|
opts.keyUp = up
|
|
opts.keyDown = down
|
|
opts.keyPgUp = pageUp
|
|
opts.keyPgDown = pageDown
|
|
})
|
|
}
|
|
|
|
// The default value for the MaxTextCells option.
|
|
// Use zero as no limit, for logs you may wish to try 10,000 or higher.
|
|
const (
|
|
DefaultMaxTextCells = 0
|
|
)
|
|
|
|
// MaxTextCells limits the text content to this number of terminal cells.
|
|
// This is useful when sending large amounts of text to the Text widget, e.g.
|
|
// when tailing logs as it will limit the memory usage.
|
|
// When the newly added content goes over this number of cells, the Text widget
|
|
// behaves as a circular buffer and drops earlier content to accommodate the
|
|
// new one.
|
|
// Note the count is in cells, not runes, some wide runes can take multiple
|
|
// terminal cells.
|
|
func MaxTextCells(max int) Option {
|
|
return option(func(opts *options) {
|
|
opts.maxTextCells = max
|
|
})
|
|
}
|