#35 editor wip
[nikiroo-utils.git] / src / jexer / teditor / Line.java
CommitLineData
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 */
29package jexer.teditor;
30
31import java.util.ArrayList;
32import java.util.List;
33
e8a11f98 34import jexer.bits.CellAttributes;
2d3f60d8 35import 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 41public 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}