1// Copyright 2016 The Tcell Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use 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 views
16
17import (
18	"strings"
19	"sync"
20
21	"github.com/gdamore/tcell/v2"
22)
23
24// TextArea is a pannable 2 dimensional text widget. It wraps both
25// the view and the model for text in a single, convenient widget.
26// Text is provided as an array of strings, each of which represents
27// a single line to display.  All text in the TextArea has the same
28// style.  An optional soft cursor is available.
29type TextArea struct {
30	model *linesModel
31	once  sync.Once
32	CellView
33}
34
35type linesModel struct {
36	runes  [][]rune
37	width  int
38	height int
39	x      int
40	y      int
41	hide   bool
42	cursor bool
43	style  tcell.Style
44}
45
46func (m *linesModel) GetCell(x, y int) (rune, tcell.Style, []rune, int) {
47	if x < 0 || y < 0 || y >= m.height || x >= len(m.runes[y]) {
48		return 0, m.style, nil, 1
49	}
50	// XXX: extend this to support combining and full width chars
51	return m.runes[y][x], m.style, nil, 1
52}
53
54func (m *linesModel) GetBounds() (int, int) {
55	return m.width, m.height
56}
57
58func (m *linesModel) limitCursor() {
59	if m.x > m.width-1 {
60		m.x = m.width - 1
61	}
62	if m.y > m.height-1 {
63		m.y = m.height - 1
64	}
65	if m.x < 0 {
66		m.x = 0
67	}
68	if m.y < 0 {
69		m.y = 0
70	}
71}
72
73func (m *linesModel) SetCursor(x, y int) {
74	m.x = x
75	m.y = y
76	m.limitCursor()
77}
78
79func (m *linesModel) MoveCursor(x, y int) {
80	m.x += x
81	m.y += y
82	m.limitCursor()
83}
84
85func (m *linesModel) GetCursor() (int, int, bool, bool) {
86	return m.x, m.y, m.cursor, !m.hide
87}
88
89// SetLines sets the content text to display.
90func (ta *TextArea) SetLines(lines []string) {
91	ta.Init()
92	m := ta.model
93	m.width =0
94
95	// extend slice before using m.runes[row] to avoid panic
96	slice := make([][]rune, len(lines))
97	m.runes = slice
98
99	for row, line := range lines {
100		for _, ch := range line {
101			m.runes[row] = append(m.runes[row], ch)
102		}
103		if len(m.runes[row]) > m.width {
104			m.width = len(m.runes[row])
105		}
106	}
107
108	m.height = len(m.runes)
109
110	ta.CellView.SetModel(m)
111}
112
113func (ta *TextArea) SetStyle(style tcell.Style) {
114	ta.model.style = style
115	ta.CellView.SetStyle(style)
116}
117
118// EnableCursor enables a soft cursor in the TextArea.
119func (ta *TextArea) EnableCursor(on bool) {
120	ta.Init()
121	ta.model.cursor = on
122}
123
124// HideCursor hides or shows the cursor in the TextArea.
125// If on is true, the cursor is hidden.  Note that a cursor is only
126// shown if it is enabled.
127func (ta *TextArea) HideCursor(on bool) {
128	ta.Init()
129	ta.model.hide = on
130}
131
132// SetContent is used to set the textual content, passed as a
133// single string.  Lines within the string are delimited by newlines.
134func (ta *TextArea) SetContent(text string) {
135	ta.Init()
136	lines := strings.Split(strings.Trim(text, "\n"), "\n")
137	ta.SetLines(lines)
138}
139
140// Init initializes the TextArea.
141func (ta *TextArea) Init() {
142	ta.once.Do(func() {
143		lm := &linesModel{runes: [][]rune{}, width: 0}
144		ta.model = lm
145		ta.CellView.Init()
146		ta.CellView.SetModel(lm)
147	})
148}
149
150// NewTextArea creates a blank TextArea.
151func NewTextArea() *TextArea {
152	ta := &TextArea{}
153	ta.Init()
154	return ta
155}
156