2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2017 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
.io
.FileOutputStream
;
32 import java
.io
.IOException
;
33 import java
.io
.OutputStreamWriter
;
34 import java
.util
.ArrayList
;
35 import java
.util
.List
;
37 import jexer
.bits
.CellAttributes
;
40 * A Document represents a text file, as a collection of lines.
42 public class Document
{
47 private ArrayList
<Line
> lines
= new ArrayList
<Line
>();
50 * The current line number being edited. Note that this is 0-based, the
51 * first line is line number 0.
53 private int lineNumber
= 0;
56 * The overwrite flag. When true, characters overwrite data.
58 private boolean overwrite
= false;
61 * If true, the document has been edited.
63 private boolean dirty
= false;
66 * The default color for the TEditor class.
68 private CellAttributes defaultColor
= null;
71 * The text highlighter to use.
73 private Highlighter highlighter
= new Highlighter();
76 * Get the overwrite flag.
78 * @return true if addChar() overwrites data, false if it inserts
80 public boolean getOverwrite() {
85 * Get the dirty value.
87 * @return true if the buffer is dirty
89 public boolean isDirty() {
94 * Save contents to file.
96 * @param filename file to save to
97 * @throws IOException if a java.io operation throws
99 public void saveToFilename(final String filename
) throws IOException
{
100 OutputStreamWriter output
= null;
102 output
= new OutputStreamWriter(new FileOutputStream(filename
),
105 for (Line line
: lines
) {
106 output
.write(line
.getRawString());
113 if (output
!= null) {
120 * Set the overwrite flag.
122 * @param overwrite true if addChar() should overwrite data, false if it
125 public void setOverwrite(final boolean overwrite
) {
126 this.overwrite
= overwrite
;
130 * Get the current line number being edited.
132 * @return the line number. Note that this is 0-based: 0 is the first
135 public int getLineNumber() {
140 * Get the current editing line.
144 public Line
getCurrentLine() {
145 return lines
.get(lineNumber
);
149 * Get a specific line by number.
151 * @param lineNumber the line number. Note that this is 0-based: 0 is
155 public Line
getLine(final int lineNumber
) {
156 return lines
.get(lineNumber
);
160 * Set the current line number being edited.
162 * @param n the line number. Note that this is 0-based: 0 is the first
165 public void setLineNumber(final int n
) {
166 if ((n
< 0) || (n
> lines
.size())) {
167 throw new IndexOutOfBoundsException("Lines array size is " +
168 lines
.size() + ", requested index " + n
);
174 * Get the current cursor position of the editing line.
176 * @return the cursor position
178 public int getCursor() {
179 return lines
.get(lineNumber
).getCursor();
183 * Set the current cursor position of the editing line. 0-based.
185 * @param cursor the new cursor position
187 public void setCursor(final int cursor
) {
188 if (cursor
>= lines
.get(lineNumber
).getDisplayLength()) {
189 lines
.get(lineNumber
).end();
191 lines
.get(lineNumber
).setCursor(cursor
);
196 * Construct a new Document from an existing text string.
198 * @param str the text string
199 * @param defaultColor the color for unhighlighted text
201 public Document(final String str
, final CellAttributes defaultColor
) {
202 this.defaultColor
= defaultColor
;
204 // TODO: set different colors based on file extension
205 highlighter
.setJavaColors();
207 String
[] rawLines
= str
.split("\n");
208 for (int i
= 0; i
< rawLines
.length
; i
++) {
209 lines
.add(new Line(rawLines
[i
], this.defaultColor
, highlighter
));
214 * Increment the line number by one. If at the last line, do nothing.
216 * @return true if the editing line changed
218 public boolean down() {
219 if (lineNumber
< lines
.size() - 1) {
220 int x
= lines
.get(lineNumber
).getCursor();
222 if (x
>= lines
.get(lineNumber
).getDisplayLength()) {
223 lines
.get(lineNumber
).end();
225 lines
.get(lineNumber
).setCursor(x
);
233 * Increment the line number by n. If n would go past the last line,
234 * increment only to the last line.
236 * @param n the number of lines to increment by
237 * @return true if the editing line changed
239 public boolean down(final int n
) {
240 if (lineNumber
< lines
.size() - 1) {
241 int x
= lines
.get(lineNumber
).getCursor();
243 if (lineNumber
> lines
.size() - 1) {
244 lineNumber
= lines
.size() - 1;
246 if (x
>= lines
.get(lineNumber
).getDisplayLength()) {
247 lines
.get(lineNumber
).end();
249 lines
.get(lineNumber
).setCursor(x
);
257 * Decrement the line number by one. If at the first line, do nothing.
259 * @return true if the editing line changed
261 public boolean up() {
262 if (lineNumber
> 0) {
263 int x
= lines
.get(lineNumber
).getCursor();
265 if (x
>= lines
.get(lineNumber
).getDisplayLength()) {
266 lines
.get(lineNumber
).end();
268 lines
.get(lineNumber
).setCursor(x
);
276 * Decrement the line number by n. If n would go past the first line,
277 * decrement only to the first line.
279 * @param n the number of lines to decrement by
280 * @return true if the editing line changed
282 public boolean up(final int n
) {
283 if (lineNumber
> 0) {
284 int x
= lines
.get(lineNumber
).getCursor();
286 if (lineNumber
< 0) {
289 if (x
>= lines
.get(lineNumber
).getDisplayLength()) {
290 lines
.get(lineNumber
).end();
292 lines
.get(lineNumber
).setCursor(x
);
300 * Decrement the cursor by one. If at the first column, do nothing.
302 * @return true if the cursor position changed
304 public boolean left() {
305 if (!lines
.get(lineNumber
).left()) {
306 // We are on the leftmost column, wrap
317 * Increment the cursor by one. If at the last column, do nothing.
319 * @return true if the cursor position changed
321 public boolean right() {
322 if (!lines
.get(lineNumber
).right()) {
323 // We are on the rightmost column, wrap
334 * Go to the first column of this line.
336 * @return true if the cursor position changed
338 public boolean home() {
339 return lines
.get(lineNumber
).home();
343 * Go to the last column of this line.
345 * @return true if the cursor position changed
347 public boolean end() {
348 return lines
.get(lineNumber
).end();
352 * Delete the character under the cursor.
356 int cursor
= lines
.get(lineNumber
).getCursor();
357 if (cursor
< lines
.get(lineNumber
).getDisplayLength() - 1) {
358 lines
.get(lineNumber
).del();
359 } else if (lineNumber
< lines
.size() - 2) {
361 StringBuilder newLine
= new StringBuilder(lines
.
362 get(lineNumber
).getRawString());
363 newLine
.append(lines
.get(lineNumber
+ 1).getRawString());
364 lines
.set(lineNumber
, new Line(newLine
.toString(),
365 defaultColor
, highlighter
));
366 lines
.get(lineNumber
).setCursor(cursor
);
367 lines
.remove(lineNumber
+ 1);
372 * Delete the character immediately preceeding the cursor.
374 public void backspace() {
376 int cursor
= lines
.get(lineNumber
).getCursor();
378 lines
.get(lineNumber
).backspace();
379 } else if (lineNumber
> 0) {
382 String firstLine
= lines
.get(lineNumber
).getRawString();
383 if (firstLine
.length() > 0) {
384 // Backspacing combining two lines
385 StringBuilder newLine
= new StringBuilder(firstLine
);
386 newLine
.append(lines
.get(lineNumber
+ 1).getRawString());
387 lines
.set(lineNumber
, new Line(newLine
.toString(),
388 defaultColor
, highlighter
));
389 lines
.get(lineNumber
).setCursor(firstLine
.length());
390 lines
.remove(lineNumber
+ 1);
392 // Backspacing an empty line
393 lines
.remove(lineNumber
);
394 lines
.get(lineNumber
).setCursor(0);
400 * Split the current line into two, like pressing the enter key.
402 public void enter() {
404 int cursor
= lines
.get(lineNumber
).getCursor();
405 String original
= lines
.get(lineNumber
).getRawString();
406 String firstLine
= original
.substring(0, cursor
);
407 String secondLine
= original
.substring(cursor
);
408 lines
.add(lineNumber
+ 1, new Line(secondLine
, defaultColor
,
410 lines
.set(lineNumber
, new Line(firstLine
, defaultColor
, highlighter
));
412 lines
.get(lineNumber
).home();
416 * Replace or insert a character at the cursor, depending on overwrite
419 * @param ch the character to replace or insert
421 public void addChar(final char ch
) {
424 lines
.get(lineNumber
).replaceChar(ch
);
426 lines
.get(lineNumber
).addChar(ch
);
431 * Get a (shallow) copy of the list of lines.
433 * @return the list of lines
435 public List
<Line
> getLines() {
436 return new ArrayList
<Line
>(lines
);
440 * Get the number of lines.
442 * @return the number of lines
444 public int getLineCount() {
449 * Compute the maximum line length for this document.
451 * @return the number of cells needed to display the longest line
453 public int getLineLengthMax() {
455 for (Line line
: lines
) {
456 if (line
.getDisplayLength() > n
) {
457 n
= line
.getDisplayLength();
464 * Get the current line length.
466 * @return the number of cells needed to display the current line
468 public int getLineLength() {
469 return lines
.get(lineNumber
).getDisplayLength();