Commit | Line | Data |
---|---|---|
cd92b0ba KL |
1 | /* |
2 | * Jexer - Java Text User Interface | |
3 | * | |
4 | * The MIT License (MIT) | |
5 | * | |
a69ed767 | 6 | * Copyright (C) 2019 Kevin Lamonte |
cd92b0ba KL |
7 | * |
8 | * Permission is hereby granted, free of charge, to any person obtaining a | |
9 | * copy of this software and associated documentation files (the "Software"), | |
10 | * to deal in the Software without restriction, including without limitation | |
11 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
12 | * and/or sell copies of the Software, and to permit persons to whom the | |
13 | * Software is furnished to do so, subject to the following conditions: | |
14 | * | |
15 | * The above copyright notice and this permission notice shall be included in | |
16 | * all copies or substantial portions of the Software. | |
17 | * | |
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
21 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
24 | * DEALINGS IN THE SOFTWARE. | |
25 | * | |
26 | * @author Kevin Lamonte [kevin.lamonte@gmail.com] | |
27 | * @version 1 | |
28 | */ | |
29 | package jexer.teditor; | |
30 | ||
31 | import java.util.ArrayList; | |
32 | import java.util.List; | |
33 | ||
e8a11f98 | 34 | import jexer.bits.CellAttributes; |
2d3f60d8 | 35 | import jexer.bits.StringUtils; |
e8a11f98 | 36 | |
cd92b0ba | 37 | /** |
12b55d76 KL |
38 | * A Line represents a single line of text on the screen, as a collection of |
39 | * words. | |
cd92b0ba | 40 | */ |
12b55d76 | 41 | public class Line { |
cd92b0ba | 42 | |
615a0d99 KL |
43 | // ------------------------------------------------------------------------ |
44 | // Variables -------------------------------------------------------------- | |
45 | // ------------------------------------------------------------------------ | |
46 | ||
cd92b0ba | 47 | /** |
12b55d76 | 48 | * The list of words. |
cd92b0ba | 49 | */ |
12b55d76 | 50 | private ArrayList<Word> words = new ArrayList<Word>(); |
cd92b0ba | 51 | |
e8a11f98 KL |
52 | /** |
53 | * The default color for the TEditor class. | |
54 | */ | |
55 | private CellAttributes defaultColor = null; | |
56 | ||
57 | /** | |
58 | * The text highlighter to use. | |
59 | */ | |
60 | private Highlighter highlighter = null; | |
61 | ||
cd92b0ba | 62 | /** |
12b55d76 | 63 | * The current cursor position on this line. |
cd92b0ba | 64 | */ |
e8a11f98 | 65 | private int cursor = 0; |
cd92b0ba KL |
66 | |
67 | /** | |
71a389c9 KL |
68 | * The raw text of this line, what is passed to Word to determine |
69 | * highlighting behavior. | |
cd92b0ba | 70 | */ |
71a389c9 | 71 | private StringBuilder rawText; |
cd92b0ba | 72 | |
615a0d99 KL |
73 | // ------------------------------------------------------------------------ |
74 | // Constructors ----------------------------------------------------------- | |
75 | // ------------------------------------------------------------------------ | |
76 | ||
77 | /** | |
78 | * Construct a new Line from an existing text string, and highlight | |
79 | * certain strings. | |
80 | * | |
81 | * @param str the text string | |
82 | * @param defaultColor the color for unhighlighted text | |
83 | * @param highlighter the highlighter to use | |
84 | */ | |
85 | public Line(final String str, final CellAttributes defaultColor, | |
86 | final Highlighter highlighter) { | |
87 | ||
88 | this.defaultColor = defaultColor; | |
89 | this.highlighter = highlighter; | |
90 | this.rawText = new StringBuilder(str); | |
91 | ||
92 | scanLine(); | |
93 | } | |
94 | ||
95 | /** | |
96 | * Construct a new Line from an existing text string. | |
97 | * | |
98 | * @param str the text string | |
99 | * @param defaultColor the color for unhighlighted text | |
100 | */ | |
101 | public Line(final String str, final CellAttributes defaultColor) { | |
102 | this(str, defaultColor, null); | |
103 | } | |
104 | ||
105 | // ------------------------------------------------------------------------ | |
106 | // Line ------------------------------------------------------------------- | |
107 | // ------------------------------------------------------------------------ | |
108 | ||
cd92b0ba | 109 | /** |
71a389c9 KL |
110 | * Get a (shallow) copy of the words in this line. |
111 | * | |
112 | * @return a copy of the word list | |
cd92b0ba | 113 | */ |
71a389c9 KL |
114 | public List<Word> getWords() { |
115 | return new ArrayList<Word>(words); | |
116 | } | |
cd92b0ba | 117 | |
e8a11f98 KL |
118 | /** |
119 | * Get the current cursor position. | |
120 | * | |
121 | * @return the cursor position | |
122 | */ | |
123 | public int getCursor() { | |
124 | return cursor; | |
125 | } | |
126 | ||
127 | /** | |
128 | * Set the current cursor position. | |
129 | * | |
130 | * @param cursor the new cursor position | |
131 | */ | |
132 | public void setCursor(final int cursor) { | |
133 | if ((cursor < 0) | |
134 | || ((cursor >= getDisplayLength()) | |
135 | && (getDisplayLength() > 0)) | |
136 | ) { | |
137 | throw new IndexOutOfBoundsException("Max length is " + | |
138 | getDisplayLength() + ", requested position " + cursor); | |
139 | } | |
140 | this.cursor = cursor; | |
e8a11f98 KL |
141 | } |
142 | ||
cd92b0ba | 143 | /** |
71a389c9 | 144 | * Get the on-screen display length. |
cd92b0ba | 145 | * |
71a389c9 | 146 | * @return the number of cells needed to display this line |
cd92b0ba | 147 | */ |
71a389c9 | 148 | public int getDisplayLength() { |
2d3f60d8 | 149 | int n = StringUtils.width(rawText.toString()); |
71a389c9 KL |
150 | |
151 | // For now just return the raw text length. | |
152 | if (n > 0) { | |
153 | // If we have any visible characters, add one to the display so | |
154 | // that the cursor is immediately after the data. | |
155 | return n + 1; | |
156 | } | |
157 | return n; | |
cd92b0ba KL |
158 | } |
159 | ||
160 | /** | |
71a389c9 | 161 | * Get the raw string that matches this line. |
cd92b0ba | 162 | * |
71a389c9 | 163 | * @return the string |
cd92b0ba | 164 | */ |
71a389c9 KL |
165 | public String getRawString() { |
166 | return rawText.toString(); | |
167 | } | |
e8a11f98 | 168 | |
71a389c9 KL |
169 | /** |
170 | * Scan rawText and make words out of it. | |
171 | */ | |
172 | private void scanLine() { | |
173 | words.clear(); | |
174 | Word word = new Word(this.defaultColor, this.highlighter); | |
175 | words.add(word); | |
2d3f60d8 KL |
176 | for (int i = 0; i < rawText.length();) { |
177 | int ch = rawText.codePointAt(i); | |
178 | i += Character.charCount(ch); | |
71a389c9 KL |
179 | Word newWord = word.addChar(ch); |
180 | if (newWord != word) { | |
181 | words.add(newWord); | |
182 | word = newWord; | |
183 | } | |
184 | } | |
185 | for (Word w: words) { | |
186 | w.applyHighlight(); | |
e8a11f98 | 187 | } |
cd92b0ba KL |
188 | } |
189 | ||
cd92b0ba | 190 | /** |
12b55d76 | 191 | * Decrement the cursor by one. If at the first column, do nothing. |
e8a11f98 KL |
192 | * |
193 | * @return true if the cursor position changed | |
cd92b0ba | 194 | */ |
e8a11f98 KL |
195 | public boolean left() { |
196 | if (cursor == 0) { | |
197 | return false; | |
cd92b0ba | 198 | } |
2d3f60d8 | 199 | cursor -= StringUtils.width(rawText.codePointAt(cursor - 1)); |
e8a11f98 | 200 | return true; |
cd92b0ba KL |
201 | } |
202 | ||
203 | /** | |
12b55d76 | 204 | * Increment the cursor by one. If at the last column, do nothing. |
e8a11f98 KL |
205 | * |
206 | * @return true if the cursor position changed | |
cd92b0ba | 207 | */ |
e8a11f98 KL |
208 | public boolean right() { |
209 | if (getDisplayLength() == 0) { | |
210 | return false; | |
cd92b0ba | 211 | } |
e8a11f98 KL |
212 | if (cursor == getDisplayLength() - 1) { |
213 | return false; | |
214 | } | |
2d3f60d8 KL |
215 | if (cursor < getDisplayLength()) { |
216 | cursor += StringUtils.width(rawText.codePointAt(cursor)); | |
217 | } else { | |
218 | cursor++; | |
219 | } | |
e8a11f98 | 220 | return true; |
cd92b0ba KL |
221 | } |
222 | ||
223 | /** | |
12b55d76 | 224 | * Go to the first column of this line. |
e8a11f98 KL |
225 | * |
226 | * @return true if the cursor position changed | |
cd92b0ba | 227 | */ |
e8a11f98 KL |
228 | public boolean home() { |
229 | if (cursor > 0) { | |
230 | cursor = 0; | |
e8a11f98 KL |
231 | return true; |
232 | } | |
233 | return false; | |
cd92b0ba KL |
234 | } |
235 | ||
236 | /** | |
12b55d76 | 237 | * Go to the last column of this line. |
e8a11f98 KL |
238 | * |
239 | * @return true if the cursor position changed | |
cd92b0ba | 240 | */ |
e8a11f98 KL |
241 | public boolean end() { |
242 | if (cursor != getDisplayLength() - 1) { | |
243 | cursor = getDisplayLength() - 1; | |
244 | if (cursor < 0) { | |
245 | cursor = 0; | |
246 | } | |
e8a11f98 KL |
247 | return true; |
248 | } | |
249 | return false; | |
cd92b0ba KL |
250 | } |
251 | ||
252 | /** | |
12b55d76 | 253 | * Delete the character under the cursor. |
cd92b0ba | 254 | */ |
12b55d76 | 255 | public void del() { |
71a389c9 KL |
256 | assert (words.size() > 0); |
257 | ||
258 | if (cursor < getDisplayLength()) { | |
2d3f60d8 KL |
259 | for (int i = 0; i < Character.charCount(rawText.charAt(cursor)); i++) { |
260 | rawText.deleteCharAt(cursor); | |
261 | } | |
71a389c9 KL |
262 | } |
263 | ||
264 | // Re-scan the line to determine the new word boundaries. | |
265 | scanLine(); | |
cd92b0ba KL |
266 | } |
267 | ||
268 | /** | |
12b55d76 | 269 | * Delete the character immediately preceeding the cursor. |
cd92b0ba | 270 | */ |
12b55d76 | 271 | public void backspace() { |
71a389c9 KL |
272 | if (left()) { |
273 | del(); | |
274 | } | |
cd92b0ba KL |
275 | } |
276 | ||
277 | /** | |
e8a11f98 | 278 | * Insert a character at the cursor. |
cd92b0ba | 279 | * |
e8a11f98 | 280 | * @param ch the character to insert |
cd92b0ba | 281 | */ |
2d3f60d8 | 282 | public void addChar(final int ch) { |
71a389c9 | 283 | if (cursor < getDisplayLength() - 1) { |
2d3f60d8 | 284 | rawText.insert(cursor, Character.toChars(ch)); |
71a389c9 | 285 | } else { |
2d3f60d8 | 286 | rawText.append(Character.toChars(ch)); |
71a389c9 KL |
287 | } |
288 | scanLine(); | |
289 | cursor++; | |
cd92b0ba KL |
290 | } |
291 | ||
e8a11f98 KL |
292 | /** |
293 | * Replace a character at the cursor. | |
294 | * | |
295 | * @param ch the character to replace | |
296 | */ | |
2d3f60d8 | 297 | public void replaceChar(final int ch) { |
71a389c9 | 298 | if (cursor < getDisplayLength() - 1) { |
2d3f60d8 KL |
299 | for (int i = 0; i < Character.charCount(rawText.charAt(cursor)); i++) { |
300 | rawText.deleteCharAt(cursor); | |
301 | } | |
302 | rawText.insert(cursor, Character.toChars(ch)); | |
71a389c9 | 303 | } else { |
2d3f60d8 | 304 | rawText.append(Character.toChars(ch)); |
71a389c9 KL |
305 | } |
306 | scanLine(); | |
307 | cursor++; | |
e8a11f98 KL |
308 | } |
309 | ||
cd92b0ba | 310 | } |