1// Copyright 2018 Google Inc. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package text 16 17import ( 18 "fmt" 19 20 "github.com/mum4k/termdash/keyboard" 21 "github.com/mum4k/termdash/mouse" 22 "github.com/mum4k/termdash/private/wrap" 23) 24 25// options.go contains configurable options for Text. 26 27// Option is used to provide options to New(). 28type Option interface { 29 // set sets the provided option. 30 set(*options) 31} 32 33// options stores the provided options. 34type options struct { 35 scrollUp rune 36 scrollDown rune 37 wrapMode wrap.Mode 38 rollContent bool 39 maxTextCells int 40 disableScrolling bool 41 mouseUpButton mouse.Button 42 mouseDownButton mouse.Button 43 keyUp keyboard.Key 44 keyDown keyboard.Key 45 keyPgUp keyboard.Key 46 keyPgDown keyboard.Key 47} 48 49// newOptions returns a new options instance. 50func newOptions(opts ...Option) *options { 51 opt := &options{ 52 scrollUp: DefaultScrollUpRune, 53 scrollDown: DefaultScrollDownRune, 54 mouseUpButton: DefaultScrollMouseButtonUp, 55 mouseDownButton: DefaultScrollMouseButtonDown, 56 keyUp: DefaultScrollKeyUp, 57 keyDown: DefaultScrollKeyDown, 58 keyPgUp: DefaultScrollKeyPageUp, 59 keyPgDown: DefaultScrollKeyPageDown, 60 maxTextCells: DefaultMaxTextCells, 61 } 62 for _, o := range opts { 63 o.set(opt) 64 } 65 return opt 66} 67 68// validate validates the provided options. 69func (o *options) validate() error { 70 keys := map[keyboard.Key]bool{ 71 o.keyUp: true, 72 o.keyDown: true, 73 o.keyPgUp: true, 74 o.keyPgDown: true, 75 } 76 if len(keys) != 4 { 77 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) 78 } 79 if o.mouseUpButton == o.mouseDownButton { 80 return fmt.Errorf("invalid ScrollMouseButtons(up:%v, down:%v), the buttons must be unique", o.mouseUpButton, o.mouseDownButton) 81 } 82 if o.maxTextCells < 0 { 83 return fmt.Errorf("invalid MaxTextCells(%d), must be zero or a positive integer", o.maxTextCells) 84 } 85 return nil 86} 87 88// option implements Option. 89type option func(*options) 90 91// set implements Option.set. 92func (o option) set(opts *options) { 93 o(opts) 94} 95 96// ScrollRunes configures the text widgets scroll runes, shown at the top and 97// bottom of a scrollable text widget. If not provided, the default scroll 98// runes will be used. 99func ScrollRunes(up, down rune) Option { 100 return option(func(opts *options) { 101 opts.scrollUp = up 102 opts.scrollDown = down 103 }) 104} 105 106// The default scroll runes for content scrolling 107const ( 108 DefaultScrollUpRune = '⇧' 109 DefaultScrollDownRune = '⇩' 110) 111 112// WrapAtWords configures the text widget so that it automatically wraps lines 113// that are longer than the width of the widget at word boundaries. If not 114// provided, long lines are trimmed instead. 115func WrapAtWords() Option { 116 return option(func(opts *options) { 117 opts.wrapMode = wrap.AtWords 118 }) 119} 120 121// WrapAtRunes configures the text widget so that it automatically wraps lines 122// that are longer than the width of the widget at rune boundaries. If not 123// provided, long lines are trimmed instead. 124func WrapAtRunes() Option { 125 return option(func(opts *options) { 126 opts.wrapMode = wrap.AtRunes 127 }) 128} 129 130// RollContent configures the text widget so that it rolls the text content up 131// if more text than the size of the container is added. If not provided, the 132// content is trimmed instead. 133func RollContent() Option { 134 return option(func(opts *options) { 135 opts.rollContent = true 136 }) 137} 138 139// DisableScrolling disables the scrolling of the content using keyboard and 140// mouse. 141func DisableScrolling() Option { 142 return option(func(opts *options) { 143 opts.disableScrolling = true 144 }) 145} 146 147// The default mouse buttons for content scrolling. 148const ( 149 DefaultScrollMouseButtonUp = mouse.ButtonWheelUp 150 DefaultScrollMouseButtonDown = mouse.ButtonWheelDown 151) 152 153// ScrollMouseButtons configures the mouse buttons that scroll the content. 154// The provided buttons must be unique, e.g. the same button cannot be both up 155// and down. 156func ScrollMouseButtons(up, down mouse.Button) Option { 157 return option(func(opts *options) { 158 opts.mouseUpButton = up 159 opts.mouseDownButton = down 160 }) 161} 162 163// The default keys for content scrolling. 164const ( 165 DefaultScrollKeyUp = keyboard.KeyArrowUp 166 DefaultScrollKeyDown = keyboard.KeyArrowDown 167 DefaultScrollKeyPageUp = keyboard.KeyPgUp 168 DefaultScrollKeyPageDown = keyboard.KeyPgDn 169) 170 171// ScrollKeys configures the keyboard keys that scroll the content. 172// The provided keys must be unique, e.g. the same key cannot be both up and 173// down. 174func ScrollKeys(up, down, pageUp, pageDown keyboard.Key) Option { 175 return option(func(opts *options) { 176 opts.keyUp = up 177 opts.keyDown = down 178 opts.keyPgUp = pageUp 179 opts.keyPgDown = pageDown 180 }) 181} 182 183// The default value for the MaxTextCells option. 184// Use zero as no limit, for logs you may wish to try 10,000 or higher. 185const ( 186 DefaultMaxTextCells = 0 187) 188 189// MaxTextCells limits the text content to this number of terminal cells. 190// This is useful when sending large amounts of text to the Text widget, e.g. 191// when tailing logs as it will limit the memory usage. 192// When the newly added content goes over this number of cells, the Text widget 193// behaves as a circular buffer and drops earlier content to accommodate the 194// new one. 195// Note the count is in cells, not runes, some wide runes can take multiple 196// terminal cells. 197func MaxTextCells(max int) Option { 198 return option(func(opts *options) { 199 opts.maxTextCells = max 200 }) 201} 202