Commit | Line | Data |
---|---|---|
12b55d76 KL |
1 | /* |
2 | * Jexer - Java Text User Interface | |
3 | * | |
4 | * The MIT License (MIT) | |
5 | * | |
a69ed767 | 6 | * Copyright (C) 2019 Kevin Lamonte |
12b55d76 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 | ||
71a389c9 KL |
31 | import java.io.FileOutputStream; |
32 | import java.io.IOException; | |
33 | import java.io.OutputStreamWriter; | |
12b55d76 KL |
34 | import java.util.ArrayList; |
35 | import java.util.List; | |
36 | ||
e8a11f98 KL |
37 | import jexer.bits.CellAttributes; |
38 | ||
12b55d76 KL |
39 | /** |
40 | * A Document represents a text file, as a collection of lines. | |
41 | */ | |
42 | public class Document { | |
43 | ||
615a0d99 KL |
44 | // ------------------------------------------------------------------------ |
45 | // Variables -------------------------------------------------------------- | |
46 | // ------------------------------------------------------------------------ | |
47 | ||
12b55d76 KL |
48 | /** |
49 | * The list of lines. | |
50 | */ | |
51 | private ArrayList<Line> lines = new ArrayList<Line>(); | |
52 | ||
53 | /** | |
54 | * The current line number being edited. Note that this is 0-based, the | |
55 | * first line is line number 0. | |
56 | */ | |
57 | private int lineNumber = 0; | |
58 | ||
59 | /** | |
60 | * The overwrite flag. When true, characters overwrite data. | |
61 | */ | |
62 | private boolean overwrite = false; | |
63 | ||
71a389c9 KL |
64 | /** |
65 | * If true, the document has been edited. | |
66 | */ | |
67 | private boolean dirty = false; | |
68 | ||
e8a11f98 KL |
69 | /** |
70 | * The default color for the TEditor class. | |
71 | */ | |
72 | private CellAttributes defaultColor = null; | |
73 | ||
74 | /** | |
75 | * The text highlighter to use. | |
76 | */ | |
77 | private Highlighter highlighter = new Highlighter(); | |
78 | ||
21460f44 KL |
79 | /** |
80 | * The tab stop size. | |
81 | */ | |
82 | private int tabSize = 8; | |
83 | ||
84 | /** | |
85 | * If true, backspace at an indent level goes back a full indent level. | |
86 | * If false, backspace always goes back one column. | |
87 | */ | |
88 | private boolean backspaceUnindents = false; | |
89 | ||
90 | /** | |
91 | * If true, save files with tab characters. If false, convert tabs to | |
92 | * spaces when saving files. | |
93 | */ | |
94 | private boolean saveWithTabs = false; | |
95 | ||
615a0d99 KL |
96 | // ------------------------------------------------------------------------ |
97 | // Constructors ----------------------------------------------------------- | |
98 | // ------------------------------------------------------------------------ | |
99 | ||
100 | /** | |
101 | * Construct a new Document from an existing text string. | |
102 | * | |
103 | * @param str the text string | |
104 | * @param defaultColor the color for unhighlighted text | |
105 | */ | |
106 | public Document(final String str, final CellAttributes defaultColor) { | |
107 | this.defaultColor = defaultColor; | |
108 | ||
edbcdccc KL |
109 | // Set colors to resemble the Borland IDE colors, but for Java |
110 | // language keywords. | |
615a0d99 KL |
111 | highlighter.setJavaColors(); |
112 | ||
113 | String [] rawLines = str.split("\n"); | |
114 | for (int i = 0; i < rawLines.length; i++) { | |
115 | lines.add(new Line(rawLines[i], this.defaultColor, highlighter)); | |
116 | } | |
117 | } | |
118 | ||
21460f44 KL |
119 | /** |
120 | * Private constructor used by dup(). | |
121 | */ | |
122 | private Document() { | |
123 | // NOP | |
124 | } | |
125 | ||
615a0d99 KL |
126 | // ------------------------------------------------------------------------ |
127 | // Document --------------------------------------------------------------- | |
128 | // ------------------------------------------------------------------------ | |
129 | ||
21460f44 KL |
130 | /** |
131 | * Create a duplicate instance. | |
132 | * | |
133 | * @return duplicate intance | |
134 | */ | |
135 | public Document dup() { | |
136 | Document other = new Document(); | |
137 | for (Line line: lines) { | |
138 | other.lines.add(line.dup()); | |
139 | } | |
140 | other.lineNumber = lineNumber; | |
141 | other.overwrite = overwrite; | |
142 | other.dirty = dirty; | |
143 | other.defaultColor = defaultColor; | |
144 | other.highlighter.setTo(highlighter); | |
145 | return other; | |
146 | } | |
147 | ||
12b55d76 KL |
148 | /** |
149 | * Get the overwrite flag. | |
150 | * | |
151 | * @return true if addChar() overwrites data, false if it inserts | |
152 | */ | |
21460f44 | 153 | public boolean isOverwrite() { |
12b55d76 KL |
154 | return overwrite; |
155 | } | |
156 | ||
71a389c9 KL |
157 | /** |
158 | * Get the dirty value. | |
159 | * | |
160 | * @return true if the buffer is dirty | |
161 | */ | |
162 | public boolean isDirty() { | |
163 | return dirty; | |
164 | } | |
165 | ||
0580bf2c KL |
166 | /** |
167 | * Unset the dirty flag. | |
168 | */ | |
169 | public void setNotDirty() { | |
170 | dirty = false; | |
171 | } | |
172 | ||
71a389c9 KL |
173 | /** |
174 | * Save contents to file. | |
175 | * | |
176 | * @param filename file to save to | |
177 | * @throws IOException if a java.io operation throws | |
178 | */ | |
179 | public void saveToFilename(final String filename) throws IOException { | |
180 | OutputStreamWriter output = null; | |
181 | try { | |
182 | output = new OutputStreamWriter(new FileOutputStream(filename), | |
183 | "UTF-8"); | |
184 | ||
185 | for (Line line: lines) { | |
21460f44 KL |
186 | if (saveWithTabs) { |
187 | output.write(convertSpacesToTabs(line.getRawString())); | |
188 | } else { | |
189 | output.write(line.getRawString()); | |
190 | } | |
71a389c9 KL |
191 | output.write("\n"); |
192 | } | |
193 | ||
194 | dirty = false; | |
195 | } | |
196 | finally { | |
197 | if (output != null) { | |
198 | output.close(); | |
199 | } | |
200 | } | |
201 | } | |
202 | ||
12b55d76 KL |
203 | /** |
204 | * Set the overwrite flag. | |
205 | * | |
206 | * @param overwrite true if addChar() should overwrite data, false if it | |
207 | * should insert | |
208 | */ | |
209 | public void setOverwrite(final boolean overwrite) { | |
210 | this.overwrite = overwrite; | |
211 | } | |
212 | ||
213 | /** | |
214 | * Get the current line number being edited. | |
215 | * | |
216 | * @return the line number. Note that this is 0-based: 0 is the first | |
217 | * line. | |
218 | */ | |
219 | public int getLineNumber() { | |
220 | return lineNumber; | |
221 | } | |
222 | ||
e8a11f98 KL |
223 | /** |
224 | * Get the current editing line. | |
225 | * | |
226 | * @return the line | |
227 | */ | |
228 | public Line getCurrentLine() { | |
229 | return lines.get(lineNumber); | |
230 | } | |
231 | ||
12b55d76 KL |
232 | /** |
233 | * Get a specific line by number. | |
234 | * | |
235 | * @param lineNumber the line number. Note that this is 0-based: 0 is | |
236 | * the first line. | |
237 | * @return the line | |
238 | */ | |
239 | public Line getLine(final int lineNumber) { | |
240 | return lines.get(lineNumber); | |
241 | } | |
242 | ||
243 | /** | |
244 | * Set the current line number being edited. | |
245 | * | |
246 | * @param n the line number. Note that this is 0-based: 0 is the first | |
247 | * line. | |
248 | */ | |
249 | public void setLineNumber(final int n) { | |
250 | if ((n < 0) || (n > lines.size())) { | |
e8a11f98 KL |
251 | throw new IndexOutOfBoundsException("Lines array size is " + |
252 | lines.size() + ", requested index " + n); | |
12b55d76 KL |
253 | } |
254 | lineNumber = n; | |
255 | } | |
256 | ||
e8a11f98 KL |
257 | /** |
258 | * Get the current cursor position of the editing line. | |
259 | * | |
260 | * @return the cursor position | |
261 | */ | |
262 | public int getCursor() { | |
263 | return lines.get(lineNumber).getCursor(); | |
264 | } | |
265 | ||
e23989a4 KL |
266 | /** |
267 | * Get the character at the current cursor position in the text. | |
268 | * | |
269 | * @return the character, or -1 if the cursor is at the end of the line | |
270 | */ | |
271 | public int getChar() { | |
272 | return lines.get(lineNumber).getChar(); | |
273 | } | |
274 | ||
71a389c9 KL |
275 | /** |
276 | * Set the current cursor position of the editing line. 0-based. | |
277 | * | |
278 | * @param cursor the new cursor position | |
279 | */ | |
280 | public void setCursor(final int cursor) { | |
4297b49b KL |
281 | if (cursor >= lines.get(lineNumber).getDisplayLength()) { |
282 | lines.get(lineNumber).end(); | |
283 | } else { | |
284 | lines.get(lineNumber).setCursor(cursor); | |
285 | } | |
71a389c9 KL |
286 | } |
287 | ||
12b55d76 KL |
288 | /** |
289 | * Increment the line number by one. If at the last line, do nothing. | |
e8a11f98 KL |
290 | * |
291 | * @return true if the editing line changed | |
12b55d76 | 292 | */ |
e8a11f98 | 293 | public boolean down() { |
12b55d76 | 294 | if (lineNumber < lines.size() - 1) { |
e8a11f98 | 295 | int x = lines.get(lineNumber).getCursor(); |
12b55d76 | 296 | lineNumber++; |
4297b49b | 297 | if (x >= lines.get(lineNumber).getDisplayLength()) { |
e8a11f98 KL |
298 | lines.get(lineNumber).end(); |
299 | } else { | |
300 | lines.get(lineNumber).setCursor(x); | |
301 | } | |
302 | return true; | |
12b55d76 | 303 | } |
e8a11f98 | 304 | return false; |
12b55d76 KL |
305 | } |
306 | ||
307 | /** | |
308 | * Increment the line number by n. If n would go past the last line, | |
309 | * increment only to the last line. | |
310 | * | |
311 | * @param n the number of lines to increment by | |
e8a11f98 | 312 | * @return true if the editing line changed |
12b55d76 | 313 | */ |
e8a11f98 KL |
314 | public boolean down(final int n) { |
315 | if (lineNumber < lines.size() - 1) { | |
316 | int x = lines.get(lineNumber).getCursor(); | |
317 | lineNumber += n; | |
318 | if (lineNumber > lines.size() - 1) { | |
319 | lineNumber = lines.size() - 1; | |
320 | } | |
4297b49b | 321 | if (x >= lines.get(lineNumber).getDisplayLength()) { |
e8a11f98 KL |
322 | lines.get(lineNumber).end(); |
323 | } else { | |
324 | lines.get(lineNumber).setCursor(x); | |
325 | } | |
326 | return true; | |
12b55d76 | 327 | } |
e8a11f98 | 328 | return false; |
12b55d76 KL |
329 | } |
330 | ||
331 | /** | |
332 | * Decrement the line number by one. If at the first line, do nothing. | |
e8a11f98 KL |
333 | * |
334 | * @return true if the editing line changed | |
12b55d76 | 335 | */ |
e8a11f98 | 336 | public boolean up() { |
12b55d76 | 337 | if (lineNumber > 0) { |
e8a11f98 | 338 | int x = lines.get(lineNumber).getCursor(); |
12b55d76 | 339 | lineNumber--; |
4297b49b | 340 | if (x >= lines.get(lineNumber).getDisplayLength()) { |
e8a11f98 KL |
341 | lines.get(lineNumber).end(); |
342 | } else { | |
343 | lines.get(lineNumber).setCursor(x); | |
344 | } | |
345 | return true; | |
12b55d76 | 346 | } |
e8a11f98 | 347 | return false; |
12b55d76 KL |
348 | } |
349 | ||
350 | /** | |
351 | * Decrement the line number by n. If n would go past the first line, | |
352 | * decrement only to the first line. | |
353 | * | |
354 | * @param n the number of lines to decrement by | |
e8a11f98 | 355 | * @return true if the editing line changed |
12b55d76 | 356 | */ |
e8a11f98 KL |
357 | public boolean up(final int n) { |
358 | if (lineNumber > 0) { | |
359 | int x = lines.get(lineNumber).getCursor(); | |
360 | lineNumber -= n; | |
361 | if (lineNumber < 0) { | |
362 | lineNumber = 0; | |
363 | } | |
4297b49b | 364 | if (x >= lines.get(lineNumber).getDisplayLength()) { |
e8a11f98 KL |
365 | lines.get(lineNumber).end(); |
366 | } else { | |
367 | lines.get(lineNumber).setCursor(x); | |
368 | } | |
369 | return true; | |
12b55d76 | 370 | } |
e8a11f98 | 371 | return false; |
12b55d76 KL |
372 | } |
373 | ||
374 | /** | |
e23989a4 KL |
375 | * Decrement the cursor by one. If at the first column on the first |
376 | * line, do nothing. | |
e8a11f98 KL |
377 | * |
378 | * @return true if the cursor position changed | |
12b55d76 | 379 | */ |
e8a11f98 | 380 | public boolean left() { |
df602ccf KL |
381 | if (!lines.get(lineNumber).left()) { |
382 | // We are on the leftmost column, wrap | |
383 | if (up()) { | |
384 | end(); | |
385 | } else { | |
386 | return false; | |
387 | } | |
388 | } | |
389 | return true; | |
12b55d76 KL |
390 | } |
391 | ||
392 | /** | |
e23989a4 KL |
393 | * Increment the cursor by one. If at the last column on the last line, |
394 | * do nothing. | |
e8a11f98 KL |
395 | * |
396 | * @return true if the cursor position changed | |
12b55d76 | 397 | */ |
e8a11f98 | 398 | public boolean right() { |
df602ccf KL |
399 | if (!lines.get(lineNumber).right()) { |
400 | // We are on the rightmost column, wrap | |
401 | if (down()) { | |
402 | home(); | |
403 | } else { | |
404 | return false; | |
405 | } | |
406 | } | |
407 | return true; | |
12b55d76 KL |
408 | } |
409 | ||
e23989a4 KL |
410 | /** |
411 | * Go back to the beginning of this word if in the middle, or the | |
412 | * beginning of the previous word. | |
413 | */ | |
414 | public void backwardsWord() { | |
415 | ||
416 | // If at the beginning of a word already, push past it. | |
417 | if ((getChar() != -1) | |
418 | && (getRawLine().length() > 0) | |
b2efac6e | 419 | && !Character.isWhitespace((char) getChar()) |
e23989a4 KL |
420 | ) { |
421 | left(); | |
422 | } | |
423 | ||
424 | // int line = lineNumber; | |
425 | while ((getChar() == -1) | |
426 | || (getRawLine().length() == 0) | |
b2efac6e | 427 | || Character.isWhitespace((char) getChar()) |
e23989a4 KL |
428 | ) { |
429 | if (left() == false) { | |
430 | return; | |
431 | } | |
432 | } | |
433 | ||
434 | ||
435 | assert (getChar() != -1); | |
436 | ||
b2efac6e | 437 | if (!Character.isWhitespace((char) getChar()) |
e23989a4 KL |
438 | && (getRawLine().length() > 0) |
439 | ) { | |
440 | // Advance until at the beginning of the document or a whitespace | |
441 | // is encountered. | |
b2efac6e | 442 | while (!Character.isWhitespace((char) getChar())) { |
e23989a4 KL |
443 | int line = lineNumber; |
444 | if (left() == false) { | |
445 | // End of document, bail out. | |
446 | return; | |
447 | } | |
448 | if (lineNumber != line) { | |
449 | // We wrapped a line. Here that counts as whitespace. | |
450 | right(); | |
451 | return; | |
452 | } | |
453 | } | |
454 | } | |
455 | ||
456 | // We went one past the word, push back to the first character of | |
457 | // that word. | |
458 | right(); | |
459 | return; | |
460 | } | |
461 | ||
462 | /** | |
463 | * Go to the beginning of the next word. | |
464 | */ | |
465 | public void forwardsWord() { | |
466 | int line = lineNumber; | |
467 | while ((getChar() == -1) | |
468 | || (getRawLine().length() == 0) | |
469 | ) { | |
470 | if (right() == false) { | |
471 | return; | |
472 | } | |
473 | if (lineNumber != line) { | |
474 | // We wrapped a line. Here that counts as whitespace. | |
b2efac6e | 475 | if (!Character.isWhitespace((char) getChar())) { |
e23989a4 KL |
476 | // We found a character immediately after the line. |
477 | // Done! | |
478 | return; | |
479 | } | |
480 | // Still looking... | |
481 | line = lineNumber; | |
482 | } | |
483 | } | |
484 | assert (getChar() != -1); | |
485 | ||
b2efac6e | 486 | if (!Character.isWhitespace((char) getChar()) |
e23989a4 KL |
487 | && (getRawLine().length() > 0) |
488 | ) { | |
489 | // Advance until at the end of the document or a whitespace is | |
490 | // encountered. | |
b2efac6e | 491 | while (!Character.isWhitespace((char) getChar())) { |
e23989a4 KL |
492 | line = lineNumber; |
493 | if (right() == false) { | |
494 | // End of document, bail out. | |
495 | return; | |
496 | } | |
497 | if (lineNumber != line) { | |
498 | // We wrapped a line. Here that counts as whitespace. | |
b2efac6e | 499 | if (!Character.isWhitespace((char) getChar()) |
e23989a4 KL |
500 | && (getRawLine().length() > 0) |
501 | ) { | |
502 | // We found a character immediately after the line. | |
503 | // Done! | |
504 | return; | |
505 | } | |
506 | break; | |
507 | } | |
508 | } | |
509 | } | |
510 | ||
511 | while ((getChar() == -1) | |
512 | || (getRawLine().length() == 0) | |
513 | ) { | |
514 | if (right() == false) { | |
515 | return; | |
516 | } | |
517 | if (lineNumber != line) { | |
518 | // We wrapped a line. Here that counts as whitespace. | |
b2efac6e | 519 | if (!Character.isWhitespace((char) getChar())) { |
e23989a4 KL |
520 | // We found a character immediately after the line. |
521 | // Done! | |
522 | return; | |
523 | } | |
524 | // Still looking... | |
525 | line = lineNumber; | |
526 | } | |
527 | } | |
528 | assert (getChar() != -1); | |
529 | ||
b2efac6e | 530 | if (Character.isWhitespace((char) getChar())) { |
e23989a4 KL |
531 | // Advance until at the end of the document or a non-whitespace |
532 | // is encountered. | |
b2efac6e | 533 | while (Character.isWhitespace((char) getChar())) { |
e23989a4 KL |
534 | if (right() == false) { |
535 | // End of document, bail out. | |
536 | return; | |
537 | } | |
538 | } | |
539 | return; | |
540 | } | |
541 | ||
542 | // We wrapped the line to get here. | |
543 | return; | |
544 | } | |
545 | ||
546 | /** | |
547 | * Get the raw string that matches this line. | |
548 | * | |
549 | * @return the string | |
550 | */ | |
551 | public String getRawLine() { | |
552 | return lines.get(lineNumber).getRawString(); | |
553 | } | |
554 | ||
12b55d76 KL |
555 | /** |
556 | * Go to the first column of this line. | |
e8a11f98 KL |
557 | * |
558 | * @return true if the cursor position changed | |
12b55d76 | 559 | */ |
e8a11f98 KL |
560 | public boolean home() { |
561 | return lines.get(lineNumber).home(); | |
12b55d76 KL |
562 | } |
563 | ||
564 | /** | |
565 | * Go to the last column of this line. | |
e8a11f98 KL |
566 | * |
567 | * @return true if the cursor position changed | |
12b55d76 | 568 | */ |
e8a11f98 KL |
569 | public boolean end() { |
570 | return lines.get(lineNumber).end(); | |
12b55d76 KL |
571 | } |
572 | ||
573 | /** | |
574 | * Delete the character under the cursor. | |
575 | */ | |
576 | public void del() { | |
71a389c9 | 577 | dirty = true; |
df602ccf KL |
578 | int cursor = lines.get(lineNumber).getCursor(); |
579 | if (cursor < lines.get(lineNumber).getDisplayLength() - 1) { | |
580 | lines.get(lineNumber).del(); | |
581 | } else if (lineNumber < lines.size() - 2) { | |
582 | // Join two lines | |
583 | StringBuilder newLine = new StringBuilder(lines. | |
584 | get(lineNumber).getRawString()); | |
585 | newLine.append(lines.get(lineNumber + 1).getRawString()); | |
586 | lines.set(lineNumber, new Line(newLine.toString(), | |
587 | defaultColor, highlighter)); | |
588 | lines.get(lineNumber).setCursor(cursor); | |
589 | lines.remove(lineNumber + 1); | |
590 | } | |
12b55d76 KL |
591 | } |
592 | ||
593 | /** | |
594 | * Delete the character immediately preceeding the cursor. | |
595 | */ | |
596 | public void backspace() { | |
71a389c9 | 597 | dirty = true; |
df602ccf KL |
598 | int cursor = lines.get(lineNumber).getCursor(); |
599 | if (cursor > 0) { | |
21460f44 | 600 | lines.get(lineNumber).backspace(tabSize, backspaceUnindents); |
df602ccf KL |
601 | } else if (lineNumber > 0) { |
602 | // Join two lines | |
603 | lineNumber--; | |
604 | String firstLine = lines.get(lineNumber).getRawString(); | |
605 | if (firstLine.length() > 0) { | |
606 | // Backspacing combining two lines | |
607 | StringBuilder newLine = new StringBuilder(firstLine); | |
608 | newLine.append(lines.get(lineNumber + 1).getRawString()); | |
609 | lines.set(lineNumber, new Line(newLine.toString(), | |
610 | defaultColor, highlighter)); | |
611 | lines.get(lineNumber).setCursor(firstLine.length()); | |
612 | lines.remove(lineNumber + 1); | |
613 | } else { | |
614 | // Backspacing an empty line | |
615 | lines.remove(lineNumber); | |
616 | lines.get(lineNumber).setCursor(0); | |
617 | } | |
618 | } | |
619 | } | |
620 | ||
621 | /** | |
622 | * Split the current line into two, like pressing the enter key. | |
623 | */ | |
624 | public void enter() { | |
625 | dirty = true; | |
39e86397 | 626 | int cursor = lines.get(lineNumber).getRawCursor(); |
df602ccf KL |
627 | String original = lines.get(lineNumber).getRawString(); |
628 | String firstLine = original.substring(0, cursor); | |
629 | String secondLine = original.substring(cursor); | |
630 | lines.add(lineNumber + 1, new Line(secondLine, defaultColor, | |
631 | highlighter)); | |
632 | lines.set(lineNumber, new Line(firstLine, defaultColor, highlighter)); | |
633 | lineNumber++; | |
634 | lines.get(lineNumber).home(); | |
12b55d76 KL |
635 | } |
636 | ||
637 | /** | |
638 | * Replace or insert a character at the cursor, depending on overwrite | |
639 | * flag. | |
640 | * | |
641 | * @param ch the character to replace or insert | |
642 | */ | |
2d3f60d8 | 643 | public void addChar(final int ch) { |
71a389c9 | 644 | dirty = true; |
e8a11f98 KL |
645 | if (overwrite) { |
646 | lines.get(lineNumber).replaceChar(ch); | |
647 | } else { | |
648 | lines.get(lineNumber).addChar(ch); | |
649 | } | |
12b55d76 KL |
650 | } |
651 | ||
21460f44 KL |
652 | /** |
653 | * Get the tab stop size. | |
654 | * | |
655 | * @return the tab stop size | |
656 | */ | |
657 | public int getTabSize() { | |
658 | return tabSize; | |
659 | } | |
660 | ||
661 | /** | |
662 | * Set the tab stop size. | |
663 | * | |
664 | * @param tabSize the new tab stop size | |
665 | */ | |
666 | public void setTabSize(final int tabSize) { | |
667 | this.tabSize = tabSize; | |
668 | } | |
669 | ||
670 | /** | |
671 | * Set the backspace unindent option. | |
672 | * | |
673 | * @param backspaceUnindents If true, backspace at an indent level goes | |
674 | * back a full indent level. If false, backspace always goes back one | |
675 | * column. | |
676 | */ | |
677 | public void setBackspaceUnindents(final boolean backspaceUnindents) { | |
678 | this.backspaceUnindents = backspaceUnindents; | |
679 | } | |
680 | ||
681 | /** | |
682 | * Set the save with tabs option. | |
683 | * | |
684 | * @param saveWithTabs If true, save files with tab characters. If | |
685 | * false, convert tabs to spaces when saving files. | |
686 | */ | |
687 | public void setSaveWithTabs(final boolean saveWithTabs) { | |
688 | this.saveWithTabs = saveWithTabs; | |
689 | } | |
690 | ||
691 | /** | |
692 | * Handle the tab character. | |
693 | */ | |
694 | public void tab() { | |
695 | if (overwrite) { | |
696 | del(); | |
697 | } | |
698 | lines.get(lineNumber).tab(tabSize); | |
699 | } | |
700 | ||
701 | /** | |
702 | * Handle the backtab (shift-tab) character. | |
703 | */ | |
704 | public void backTab() { | |
705 | lines.get(lineNumber).backTab(tabSize); | |
706 | } | |
707 | ||
12b55d76 KL |
708 | /** |
709 | * Get a (shallow) copy of the list of lines. | |
710 | * | |
711 | * @return the list of lines | |
712 | */ | |
713 | public List<Line> getLines() { | |
714 | return new ArrayList<Line>(lines); | |
715 | } | |
716 | ||
717 | /** | |
718 | * Get the number of lines. | |
719 | * | |
720 | * @return the number of lines | |
721 | */ | |
722 | public int getLineCount() { | |
723 | return lines.size(); | |
724 | } | |
725 | ||
726 | /** | |
727 | * Compute the maximum line length for this document. | |
728 | * | |
729 | * @return the number of cells needed to display the longest line | |
730 | */ | |
731 | public int getLineLengthMax() { | |
732 | int n = 0; | |
733 | for (Line line : lines) { | |
734 | if (line.getDisplayLength() > n) { | |
735 | n = line.getDisplayLength(); | |
736 | } | |
737 | } | |
738 | return n; | |
739 | } | |
740 | ||
fe0770f9 KL |
741 | /** |
742 | * Get the current line length. | |
743 | * | |
744 | * @return the number of cells needed to display the current line | |
745 | */ | |
746 | public int getLineLength() { | |
747 | return lines.get(lineNumber).getDisplayLength(); | |
748 | } | |
749 | ||
0580bf2c KL |
750 | /** |
751 | * Get the entire contents of the document as one string. | |
752 | * | |
753 | * @return the document contents | |
754 | */ | |
755 | public String getText() { | |
756 | StringBuilder sb = new StringBuilder(); | |
757 | for (Line line: getLines()) { | |
758 | sb.append(line.getRawString()); | |
759 | sb.append("\n"); | |
760 | } | |
761 | return sb.toString(); | |
762 | } | |
763 | ||
21460f44 KL |
764 | /** |
765 | * Trim trailing whitespace from lines and trailing empty | |
766 | * lines from the document. | |
767 | */ | |
768 | public void cleanWhitespace() { | |
769 | for (Line line: getLines()) { | |
770 | line.trimRight(); | |
771 | } | |
772 | if (lines.size() == 0) { | |
773 | return; | |
774 | } | |
775 | while (lines.get(lines.size() - 1).length() == 0) { | |
776 | lines.remove(lines.size() - 1); | |
777 | } | |
778 | if (lineNumber > lines.size() - 1) { | |
779 | lineNumber = lines.size() - 1; | |
780 | } | |
781 | } | |
782 | ||
783 | /** | |
784 | * Set keyword highlighting. | |
785 | * | |
786 | * @param enabled if true, enable keyword highlighting | |
787 | */ | |
788 | public void setHighlighting(final boolean enabled) { | |
789 | highlighter.setEnabled(enabled); | |
790 | for (Line line: getLines()) { | |
791 | line.scanLine(); | |
792 | } | |
793 | } | |
794 | ||
795 | /** | |
796 | * Convert a string with leading spaces to a mix of tabs and spaces. | |
797 | * | |
798 | * @param string the string to convert | |
799 | */ | |
800 | private String convertSpacesToTabs(final String string) { | |
801 | if (string.length() == 0) { | |
802 | return string; | |
803 | } | |
804 | ||
805 | int start = 0; | |
806 | while (string.charAt(start) == ' ') { | |
807 | start++; | |
808 | } | |
809 | int tabCount = start / 8; | |
810 | if (tabCount == 0) { | |
811 | return string; | |
812 | } | |
813 | ||
814 | StringBuilder sb = new StringBuilder(string.length()); | |
815 | ||
816 | for (int i = 0; i < tabCount; i++) { | |
817 | sb.append('\t'); | |
818 | } | |
819 | sb.append(string.substring(tabCount * 8)); | |
820 | return sb.toString(); | |
821 | } | |
822 | ||
12b55d76 | 823 | } |