2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 package jexer
.teditor
;
31 import java
.util
.ArrayList
;
32 import java
.util
.List
;
34 import jexer
.bits
.CellAttributes
;
35 import jexer
.bits
.StringUtils
;
38 * A Line represents a single line of text on the screen, as a collection of
43 // ------------------------------------------------------------------------
44 // Variables --------------------------------------------------------------
45 // ------------------------------------------------------------------------
50 private ArrayList
<Word
> words
= new ArrayList
<Word
>();
53 * The default color for the TEditor class.
55 private CellAttributes defaultColor
= null;
58 * The text highlighter to use.
60 private Highlighter highlighter
= null;
63 * The current edition position on this line.
65 private int position
= 0;
68 * The current editing position screen column number.
70 private int screenPosition
= 0;
73 * The raw text of this line, what is passed to Word to determine
74 * highlighting behavior.
76 private StringBuilder rawText
;
78 // ------------------------------------------------------------------------
79 // Constructors -----------------------------------------------------------
80 // ------------------------------------------------------------------------
83 * Construct a new Line from an existing text string, and highlight
86 * @param str the text string
87 * @param defaultColor the color for unhighlighted text
88 * @param highlighter the highlighter to use
90 public Line(final String str
, final CellAttributes defaultColor
,
91 final Highlighter highlighter
) {
93 this.defaultColor
= defaultColor
;
94 this.highlighter
= highlighter
;
95 this.rawText
= new StringBuilder(str
);
101 * Construct a new Line from an existing text string.
103 * @param str the text string
104 * @param defaultColor the color for unhighlighted text
106 public Line(final String str
, final CellAttributes defaultColor
) {
107 this(str
, defaultColor
, null);
110 // ------------------------------------------------------------------------
111 // Line -------------------------------------------------------------------
112 // ------------------------------------------------------------------------
115 * Get a (shallow) copy of the words in this line.
117 * @return a copy of the word list
119 public List
<Word
> getWords() {
120 return new ArrayList
<Word
>(words
);
124 * Get the current cursor position in the text.
126 * @return the cursor position
128 public int getRawCursor() {
133 * Get the current cursor position on screen.
135 * @return the cursor position
137 public int getCursor() {
138 return screenPosition
;
142 * Set the current cursor position.
144 * @param cursor the new cursor position
146 public void setCursor(final int cursor
) {
148 || ((cursor
>= getDisplayLength())
149 && (getDisplayLength() > 0))
151 throw new IndexOutOfBoundsException("Max length is " +
152 getDisplayLength() + ", requested position " + cursor
);
154 screenPosition
= cursor
;
155 position
= screenToTextPosition(screenPosition
);
159 * Get the on-screen display length.
161 * @return the number of cells needed to display this line
163 public int getDisplayLength() {
164 int n
= StringUtils
.width(rawText
.toString());
167 // If we have any visible characters, add one to the display so
168 // that the position is immediately after the data.
175 * Get the raw string that matches this line.
179 public String
getRawString() {
180 return rawText
.toString();
184 * Scan rawText and make words out of it.
186 private void scanLine() {
188 Word word
= new Word(this.defaultColor
, this.highlighter
);
190 for (int i
= 0; i
< rawText
.length();) {
191 int ch
= rawText
.codePointAt(i
);
192 i
+= Character
.charCount(ch
);
193 Word newWord
= word
.addChar(ch
);
194 if (newWord
!= word
) {
199 for (Word w
: words
) {
205 * Decrement the cursor by one. If at the first column, do nothing.
207 * @return true if the cursor position changed
209 public boolean left() {
213 screenPosition
-= StringUtils
.width(rawText
.codePointBefore(position
));
214 position
-= Character
.charCount(rawText
.codePointBefore(position
));
219 * Increment the cursor by one. If at the last column, do nothing.
221 * @return true if the cursor position changed
223 public boolean right() {
224 if (getDisplayLength() == 0) {
227 if (position
== getDisplayLength() - 1) {
230 if (position
< rawText
.length()) {
231 screenPosition
+= StringUtils
.width(rawText
.codePointAt(position
));
232 position
+= Character
.charCount(rawText
.codePointAt(position
));
234 assert (position
<= rawText
.length());
239 * Go to the first column of this line.
241 * @return true if the cursor position changed
243 public boolean home() {
253 * Go to the last column of this line.
255 * @return true if the cursor position changed
257 public boolean end() {
258 if (position
!= getDisplayLength() - 1) {
259 position
= rawText
.length();
260 screenPosition
= StringUtils
.width(rawText
.toString());
267 * Delete the character under the cursor.
270 assert (words
.size() > 0);
272 if (position
< getDisplayLength()) {
273 int n
= Character
.charCount(rawText
.codePointAt(position
));
274 for (int i
= 0; i
< n
; i
++) {
275 rawText
.deleteCharAt(position
);
279 // Re-scan the line to determine the new word boundaries.
284 * Delete the character immediately preceeding the cursor.
286 public void backspace() {
293 * Insert a character at the cursor.
295 * @param ch the character to insert
297 public void addChar(final int ch
) {
298 if (position
< getDisplayLength() - 1) {
299 rawText
.insert(position
, Character
.toChars(ch
));
301 rawText
.append(Character
.toChars(ch
));
303 position
+= Character
.charCount(ch
);
304 screenPosition
+= StringUtils
.width(ch
);
309 * Replace a character at the cursor.
311 * @param ch the character to replace
313 public void replaceChar(final int ch
) {
314 if (position
< getDisplayLength() - 1) {
316 String oldText
= rawText
.toString();
317 rawText
= new StringBuilder(oldText
.substring(0, position
));
318 rawText
.append(Character
.toChars(ch
));
319 rawText
.append(oldText
.substring(position
+ 1));
320 screenPosition
+= StringUtils
.width(rawText
.codePointAt(position
));
321 position
+= Character
.charCount(ch
);
323 rawText
.append(Character
.toChars(ch
));
324 position
+= Character
.charCount(ch
);
325 screenPosition
+= StringUtils
.width(ch
);
331 * Determine string position from screen position.
333 * @param screenPosition the position on screen
334 * @return the equivalent position in text
336 protected int screenToTextPosition(final int screenPosition
) {
337 if (screenPosition
== 0) {
342 for (int i
= 0; i
< rawText
.length(); i
++) {
343 n
+= StringUtils
.width(rawText
.codePointAt(i
));
344 if (n
>= screenPosition
) {
348 // screenPosition exceeds the available text length.
349 throw new IndexOutOfBoundsException("screenPosition " + screenPosition
+
350 " exceeds available text length " + rawText
.length());