TEditor 80% complete
[fanfix.git] / src / jexer / teditor / Document.java
CommitLineData
12b55d76
KL
1/*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2017 Kevin Lamonte
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
71a389c9
KL
31import java.io.FileOutputStream;
32import java.io.IOException;
33import java.io.OutputStreamWriter;
12b55d76
KL
34import java.util.ArrayList;
35import java.util.List;
36
e8a11f98
KL
37import jexer.bits.CellAttributes;
38
12b55d76
KL
39/**
40 * A Document represents a text file, as a collection of lines.
41 */
42public class Document {
43
44 /**
45 * The list of lines.
46 */
47 private ArrayList<Line> lines = new ArrayList<Line>();
48
49 /**
50 * The current line number being edited. Note that this is 0-based, the
51 * first line is line number 0.
52 */
53 private int lineNumber = 0;
54
55 /**
56 * The overwrite flag. When true, characters overwrite data.
57 */
58 private boolean overwrite = false;
59
71a389c9
KL
60 /**
61 * If true, the document has been edited.
62 */
63 private boolean dirty = false;
64
e8a11f98
KL
65 /**
66 * The default color for the TEditor class.
67 */
68 private CellAttributes defaultColor = null;
69
70 /**
71 * The text highlighter to use.
72 */
73 private Highlighter highlighter = new Highlighter();
74
12b55d76
KL
75 /**
76 * Get the overwrite flag.
77 *
78 * @return true if addChar() overwrites data, false if it inserts
79 */
80 public boolean getOverwrite() {
81 return overwrite;
82 }
83
71a389c9
KL
84 /**
85 * Get the dirty value.
86 *
87 * @return true if the buffer is dirty
88 */
89 public boolean isDirty() {
90 return dirty;
91 }
92
93 /**
94 * Save contents to file.
95 *
96 * @param filename file to save to
97 * @throws IOException if a java.io operation throws
98 */
99 public void saveToFilename(final String filename) throws IOException {
100 OutputStreamWriter output = null;
101 try {
102 output = new OutputStreamWriter(new FileOutputStream(filename),
103 "UTF-8");
104
105 for (Line line: lines) {
106 output.write(line.getRawString());
107 output.write("\n");
108 }
109
110 dirty = false;
111 }
112 finally {
113 if (output != null) {
114 output.close();
115 }
116 }
117 }
118
12b55d76
KL
119 /**
120 * Set the overwrite flag.
121 *
122 * @param overwrite true if addChar() should overwrite data, false if it
123 * should insert
124 */
125 public void setOverwrite(final boolean overwrite) {
126 this.overwrite = overwrite;
127 }
128
129 /**
130 * Get the current line number being edited.
131 *
132 * @return the line number. Note that this is 0-based: 0 is the first
133 * line.
134 */
135 public int getLineNumber() {
136 return lineNumber;
137 }
138
e8a11f98
KL
139 /**
140 * Get the current editing line.
141 *
142 * @return the line
143 */
144 public Line getCurrentLine() {
145 return lines.get(lineNumber);
146 }
147
12b55d76
KL
148 /**
149 * Get a specific line by number.
150 *
151 * @param lineNumber the line number. Note that this is 0-based: 0 is
152 * the first line.
153 * @return the line
154 */
155 public Line getLine(final int lineNumber) {
156 return lines.get(lineNumber);
157 }
158
159 /**
160 * Set the current line number being edited.
161 *
162 * @param n the line number. Note that this is 0-based: 0 is the first
163 * line.
164 */
165 public void setLineNumber(final int n) {
166 if ((n < 0) || (n > lines.size())) {
e8a11f98
KL
167 throw new IndexOutOfBoundsException("Lines array size is " +
168 lines.size() + ", requested index " + n);
12b55d76
KL
169 }
170 lineNumber = n;
171 }
172
e8a11f98
KL
173 /**
174 * Get the current cursor position of the editing line.
175 *
176 * @return the cursor position
177 */
178 public int getCursor() {
179 return lines.get(lineNumber).getCursor();
180 }
181
71a389c9
KL
182 /**
183 * Set the current cursor position of the editing line. 0-based.
184 *
185 * @param cursor the new cursor position
186 */
187 public void setCursor(final int cursor) {
188 lines.get(lineNumber).setCursor(cursor);
189 }
190
e8a11f98
KL
191 /**
192 * Construct a new Document from an existing text string.
193 *
194 * @param str the text string
195 * @param defaultColor the color for unhighlighted text
196 */
197 public Document(final String str, final CellAttributes defaultColor) {
198 this.defaultColor = defaultColor;
199
200 // TODO: set different colors based on file extension
201 highlighter.setJavaColors();
202
203 String [] rawLines = str.split("\n");
204 for (int i = 0; i < rawLines.length; i++) {
205 lines.add(new Line(rawLines[i], this.defaultColor, highlighter));
206 }
207 }
208
12b55d76
KL
209 /**
210 * Increment the line number by one. If at the last line, do nothing.
e8a11f98
KL
211 *
212 * @return true if the editing line changed
12b55d76 213 */
e8a11f98 214 public boolean down() {
12b55d76 215 if (lineNumber < lines.size() - 1) {
e8a11f98 216 int x = lines.get(lineNumber).getCursor();
12b55d76 217 lineNumber++;
e8a11f98
KL
218 if (x > lines.get(lineNumber).getDisplayLength()) {
219 lines.get(lineNumber).end();
220 } else {
221 lines.get(lineNumber).setCursor(x);
222 }
223 return true;
12b55d76 224 }
e8a11f98 225 return false;
12b55d76
KL
226 }
227
228 /**
229 * Increment the line number by n. If n would go past the last line,
230 * increment only to the last line.
231 *
232 * @param n the number of lines to increment by
e8a11f98 233 * @return true if the editing line changed
12b55d76 234 */
e8a11f98
KL
235 public boolean down(final int n) {
236 if (lineNumber < lines.size() - 1) {
237 int x = lines.get(lineNumber).getCursor();
238 lineNumber += n;
239 if (lineNumber > lines.size() - 1) {
240 lineNumber = lines.size() - 1;
241 }
242 if (x > lines.get(lineNumber).getDisplayLength()) {
243 lines.get(lineNumber).end();
244 } else {
245 lines.get(lineNumber).setCursor(x);
246 }
247 return true;
12b55d76 248 }
e8a11f98 249 return false;
12b55d76
KL
250 }
251
252 /**
253 * Decrement the line number by one. If at the first line, do nothing.
e8a11f98
KL
254 *
255 * @return true if the editing line changed
12b55d76 256 */
e8a11f98 257 public boolean up() {
12b55d76 258 if (lineNumber > 0) {
e8a11f98 259 int x = lines.get(lineNumber).getCursor();
12b55d76 260 lineNumber--;
e8a11f98
KL
261 if (x > lines.get(lineNumber).getDisplayLength()) {
262 lines.get(lineNumber).end();
263 } else {
264 lines.get(lineNumber).setCursor(x);
265 }
266 return true;
12b55d76 267 }
e8a11f98 268 return false;
12b55d76
KL
269 }
270
271 /**
272 * Decrement the line number by n. If n would go past the first line,
273 * decrement only to the first line.
274 *
275 * @param n the number of lines to decrement by
e8a11f98 276 * @return true if the editing line changed
12b55d76 277 */
e8a11f98
KL
278 public boolean up(final int n) {
279 if (lineNumber > 0) {
280 int x = lines.get(lineNumber).getCursor();
281 lineNumber -= n;
282 if (lineNumber < 0) {
283 lineNumber = 0;
284 }
285 if (x > lines.get(lineNumber).getDisplayLength()) {
286 lines.get(lineNumber).end();
287 } else {
288 lines.get(lineNumber).setCursor(x);
289 }
290 return true;
12b55d76 291 }
e8a11f98 292 return false;
12b55d76
KL
293 }
294
295 /**
296 * Decrement the cursor by one. If at the first column, do nothing.
e8a11f98
KL
297 *
298 * @return true if the cursor position changed
12b55d76 299 */
e8a11f98
KL
300 public boolean left() {
301 return lines.get(lineNumber).left();
12b55d76
KL
302 }
303
304 /**
305 * Increment the cursor by one. If at the last column, do nothing.
e8a11f98
KL
306 *
307 * @return true if the cursor position changed
12b55d76 308 */
e8a11f98
KL
309 public boolean right() {
310 return lines.get(lineNumber).right();
12b55d76
KL
311 }
312
313 /**
314 * Go to the first column of this line.
e8a11f98
KL
315 *
316 * @return true if the cursor position changed
12b55d76 317 */
e8a11f98
KL
318 public boolean home() {
319 return lines.get(lineNumber).home();
12b55d76
KL
320 }
321
322 /**
323 * Go to the last column of this line.
e8a11f98
KL
324 *
325 * @return true if the cursor position changed
12b55d76 326 */
e8a11f98
KL
327 public boolean end() {
328 return lines.get(lineNumber).end();
12b55d76
KL
329 }
330
331 /**
332 * Delete the character under the cursor.
333 */
334 public void del() {
71a389c9 335 dirty = true;
12b55d76
KL
336 lines.get(lineNumber).del();
337 }
338
339 /**
340 * Delete the character immediately preceeding the cursor.
341 */
342 public void backspace() {
71a389c9 343 dirty = true;
12b55d76
KL
344 lines.get(lineNumber).backspace();
345 }
346
347 /**
348 * Replace or insert a character at the cursor, depending on overwrite
349 * flag.
350 *
351 * @param ch the character to replace or insert
352 */
353 public void addChar(final char ch) {
71a389c9 354 dirty = true;
e8a11f98
KL
355 if (overwrite) {
356 lines.get(lineNumber).replaceChar(ch);
357 } else {
358 lines.get(lineNumber).addChar(ch);
359 }
12b55d76
KL
360 }
361
362 /**
363 * Get a (shallow) copy of the list of lines.
364 *
365 * @return the list of lines
366 */
367 public List<Line> getLines() {
368 return new ArrayList<Line>(lines);
369 }
370
371 /**
372 * Get the number of lines.
373 *
374 * @return the number of lines
375 */
376 public int getLineCount() {
377 return lines.size();
378 }
379
380 /**
381 * Compute the maximum line length for this document.
382 *
383 * @return the number of cells needed to display the longest line
384 */
385 public int getLineLengthMax() {
386 int n = 0;
387 for (Line line : lines) {
388 if (line.getDisplayLength() > n) {
389 n = line.getDisplayLength();
390 }
391 }
392 return n;
393 }
394
12b55d76 395}