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]
31 import java
.io
.IOException
;
32 import java
.util
.ArrayList
;
33 import java
.util
.List
;
35 import jexer
.bits
.CellAttributes
;
36 import jexer
.bits
.StringUtils
;
37 import jexer
.event
.TCommandEvent
;
38 import jexer
.event
.TKeypressEvent
;
39 import jexer
.event
.TMouseEvent
;
40 import jexer
.event
.TResizeEvent
;
41 import jexer
.teditor
.Document
;
42 import jexer
.teditor
.Line
;
43 import jexer
.teditor
.Word
;
44 import static jexer
.TCommand
.*;
45 import static jexer
.TKeypress
.*;
48 * TEditorWidget displays an editable text document. It is unaware of
49 * scrolling behavior, but can respond to mouse and keyboard events.
51 public class TEditorWidget
extends TWidget
implements EditMenuUser
{
53 // ------------------------------------------------------------------------
54 // Constants --------------------------------------------------------------
55 // ------------------------------------------------------------------------
58 * The number of lines to scroll on mouse wheel up/down.
60 private static final int wheelScrollSize
= 3;
62 // ------------------------------------------------------------------------
63 // Variables --------------------------------------------------------------
64 // ------------------------------------------------------------------------
67 * The document being edited.
69 protected Document document
;
72 * The default color for the editable text.
74 private CellAttributes defaultColor
= null;
77 * The topmost line number in the visible area. 0-based.
79 private int topLine
= 0;
82 * The leftmost column number in the visible area. 0-based.
84 private int leftColumn
= 0;
87 * If true, the mouse is dragging a selection.
89 private boolean inSelection
= false;
92 * Selection starting column.
94 private int selectionColumn0
;
97 * Selection starting line.
99 private int selectionLine0
;
102 * Selection ending column.
104 private int selectionColumn1
;
107 * Selection ending line.
109 private int selectionLine1
;
112 * The list of undo/redo states.
114 private List
<SavedState
> undoList
= new ArrayList
<SavedState
>();
117 * The position in undoList for undo/redo.
119 private int undoListI
= 0;
122 * The maximum size of the undo list.
124 private int undoLevel
= 50;
127 * The saved state for an undo/redo operation.
129 private class SavedState
{
131 * The Document state.
133 public Document document
;
136 * The topmost line number in the visible area. 0-based.
138 public int topLine
= 0;
141 * The leftmost column number in the visible area. 0-based.
143 public int leftColumn
= 0;
147 // ------------------------------------------------------------------------
148 // Constructors -----------------------------------------------------------
149 // ------------------------------------------------------------------------
152 * Public constructor.
154 * @param parent parent widget
155 * @param text text on the screen
156 * @param x column relative to parent
157 * @param y row relative to parent
158 * @param width width of text area
159 * @param height height of text area
161 public TEditorWidget(final TWidget parent
, final String text
, final int x
,
162 final int y
, final int width
, final int height
) {
164 // Set parent and window
165 super(parent
, x
, y
, width
, height
);
167 setCursorVisible(true);
169 defaultColor
= getTheme().getColor("teditor");
170 document
= new Document(text
, defaultColor
);
173 // ------------------------------------------------------------------------
174 // Event handlers ---------------------------------------------------------
175 // ------------------------------------------------------------------------
178 * Handle mouse press events.
180 * @param mouse mouse button press event
183 public void onMouseDown(final TMouseEvent mouse
) {
184 if (mouse
.isMouseWheelUp()) {
185 for (int i
= 0; i
< wheelScrollSize
; i
++) {
188 alignDocument(false);
193 if (mouse
.isMouseWheelDown()) {
194 for (int i
= 0; i
< wheelScrollSize
; i
++) {
195 if (topLine
< document
.getLineCount() - 1) {
203 if (mouse
.isMouse1()) {
205 int newLine
= topLine
+ mouse
.getY();
206 int newX
= leftColumn
+ mouse
.getX();
209 if (newLine
> document
.getLineCount() - 1) {
210 selectionLine0
= document
.getLineCount() - 1;
212 selectionLine0
= topLine
+ mouse
.getY();
214 selectionColumn0
= leftColumn
+ mouse
.getX();
215 selectionColumn0
= Math
.max(0, Math
.min(selectionColumn0
,
216 document
.getLine(selectionLine0
).getDisplayLength() - 1));
217 selectionColumn1
= selectionColumn0
;
218 selectionLine1
= selectionLine0
;
220 // Set the row and column
221 if (newLine
> document
.getLineCount() - 1) {
223 document
.setLineNumber(document
.getLineCount() - 1);
225 if (newLine
> document
.getLineCount() - 1) {
226 setCursorY(document
.getLineCount() - 1 - topLine
);
228 setCursorY(mouse
.getY());
232 selectionColumn1
= document
.getCursor();
233 selectionLine1
= document
.getLineNumber();
238 document
.setLineNumber(newLine
);
239 setCursorY(mouse
.getY());
240 if (newX
>= document
.getCurrentLine().getDisplayLength()) {
244 document
.setCursor(newX
);
245 setCursorX(mouse
.getX());
248 selectionColumn1
= document
.getCursor();
249 selectionLine1
= document
.getLineNumber();
257 super.onMouseDown(mouse
);
261 * Handle mouse motion events.
263 * @param mouse mouse motion event
266 public void onMouseMotion(final TMouseEvent mouse
) {
268 if (mouse
.isMouse1()) {
269 // Set the row and column
270 int newLine
= topLine
+ mouse
.getY();
271 int newX
= leftColumn
+ mouse
.getX();
272 if ((newLine
< 0) || (newX
< 0)) {
278 selectionColumn1
= newX
;
279 selectionLine1
= newLine
;
282 selectionColumn0
= newX
;
283 selectionLine0
= newLine
;
284 selectionColumn1
= selectionColumn0
;
285 selectionLine1
= selectionLine0
;
288 if (newLine
> document
.getLineCount() - 1) {
290 document
.setLineNumber(document
.getLineCount() - 1);
292 if (newLine
> document
.getLineCount() - 1) {
293 setCursorY(document
.getLineCount() - 1 - topLine
);
295 setCursorY(mouse
.getY());
299 selectionColumn1
= document
.getCursor();
300 selectionLine1
= document
.getLineNumber();
304 document
.setLineNumber(newLine
);
305 setCursorY(mouse
.getY());
306 if (newX
>= document
.getCurrentLine().getDisplayLength()) {
310 document
.setCursor(newX
);
311 setCursorX(mouse
.getX());
314 selectionColumn1
= document
.getCursor();
315 selectionLine1
= document
.getLineNumber();
321 super.onMouseDown(mouse
);
327 * @param keypress keystroke event
330 public void onKeypress(final TKeypressEvent keypress
) {
331 if (keypress
.getKey().isShift()) {
332 if (keypress
.equals(kbShiftLeft
)
333 || keypress
.equals(kbShiftRight
)
334 || keypress
.equals(kbShiftUp
)
335 || keypress
.equals(kbShiftDown
)
336 || keypress
.equals(kbShiftPgDn
)
337 || keypress
.equals(kbShiftPgUp
)
338 || keypress
.equals(kbShiftHome
)
339 || keypress
.equals(kbShiftEnd
)
341 // Shifted navigation keys enable selection
344 selectionColumn0
= document
.getCursor();
345 selectionLine0
= document
.getLineNumber();
346 selectionColumn1
= selectionColumn0
;
347 selectionLine1
= selectionLine0
;
351 if (keypress
.equals(kbLeft
)
352 || keypress
.equals(kbRight
)
353 || keypress
.equals(kbUp
)
354 || keypress
.equals(kbDown
)
355 || keypress
.equals(kbPgDn
)
356 || keypress
.equals(kbPgUp
)
357 || keypress
.equals(kbHome
)
358 || keypress
.equals(kbEnd
)
360 // Non-shifted navigation keys disable selection.
363 if ((selectionColumn0
== selectionColumn1
)
364 && (selectionLine0
== selectionLine1
)
366 // The user clicked a spot and started typing.
371 if (keypress
.equals(kbLeft
)
372 || keypress
.equals(kbShiftLeft
)
376 } else if (keypress
.equals(kbRight
)
377 || keypress
.equals(kbShiftRight
)
381 } else if (keypress
.equals(kbAltLeft
)
382 || keypress
.equals(kbCtrlLeft
)
383 || keypress
.equals(kbAltShiftLeft
)
384 || keypress
.equals(kbCtrlShiftLeft
)
386 document
.backwardsWord();
388 } else if (keypress
.equals(kbAltRight
)
389 || keypress
.equals(kbCtrlRight
)
390 || keypress
.equals(kbAltShiftRight
)
391 || keypress
.equals(kbCtrlShiftRight
)
393 document
.forwardsWord();
395 } else if (keypress
.equals(kbUp
)
396 || keypress
.equals(kbShiftUp
)
400 } else if (keypress
.equals(kbDown
)
401 || keypress
.equals(kbShiftDown
)
405 } else if (keypress
.equals(kbPgUp
)
406 || keypress
.equals(kbShiftPgUp
)
408 document
.up(getHeight() - 1);
410 } else if (keypress
.equals(kbPgDn
)
411 || keypress
.equals(kbShiftPgDn
)
413 document
.down(getHeight() - 1);
415 } else if (keypress
.equals(kbHome
)
416 || keypress
.equals(kbShiftHome
)
418 if (document
.home()) {
420 if (leftColumn
< 0) {
425 } else if (keypress
.equals(kbEnd
)
426 || keypress
.equals(kbShiftEnd
)
428 if (document
.end()) {
431 } else if (keypress
.equals(kbCtrlHome
)
432 || keypress
.equals(kbCtrlShiftHome
)
434 document
.setLineNumber(0);
440 } else if (keypress
.equals(kbCtrlEnd
)
441 || keypress
.equals(kbCtrlShiftEnd
)
443 document
.setLineNumber(document
.getLineCount() - 1);
446 } else if (keypress
.equals(kbIns
)) {
447 document
.setOverwrite(!document
.isOverwrite());
448 } else if (keypress
.equals(kbDel
)) {
457 } else if (keypress
.equals(kbBackspace
)
458 || keypress
.equals(kbBackspaceDel
)
465 document
.backspace();
468 } else if (keypress
.equals(kbTab
)) {
473 } else if (keypress
.equals(kbShiftTab
)) {
478 } else if (keypress
.equals(kbEnter
)) {
483 } else if (!keypress
.getKey().isFnKey()
484 && !keypress
.getKey().isAlt()
485 && !keypress
.getKey().isCtrl()
487 // Plain old keystroke, process it
490 document
.addChar(keypress
.getKey().getChar());
493 // Pass other keys (tab etc.) on to TWidget
494 super.onKeypress(keypress
);
498 selectionColumn1
= document
.getCursor();
499 selectionLine1
= document
.getLineNumber();
504 * Method that subclasses can override to handle window/screen resize
507 * @param resize resize event
510 public void onResize(final TResizeEvent resize
) {
511 // Change my width/height, and pull the cursor in as needed.
512 if (resize
.getType() == TResizeEvent
.Type
.WIDGET
) {
513 setWidth(resize
.getWidth());
514 setHeight(resize
.getHeight());
515 // See if the cursor is now outside the window, and if so move
517 if (getCursorX() >= getWidth()) {
518 leftColumn
+= getCursorX() - (getWidth() - 1);
519 setCursorX(getWidth() - 1);
521 if (getCursorY() >= getHeight()) {
522 topLine
+= getCursorY() - (getHeight() - 1);
523 setCursorY(getHeight() - 1);
526 // Let superclass handle it
527 super.onResize(resize
);
532 * Handle posted command events.
534 * @param command command event
537 public void onCommand(final TCommandEvent command
) {
538 if (command
.equals(cmCut
)) {
539 // Copy text to clipboard, and then remove it.
545 if (command
.equals(cmCopy
)) {
546 // Copy text to clipboard.
551 if (command
.equals(cmPaste
)) {
552 // Delete selected text, then paste text from clipboard.
555 String text
= getClipboard().pasteText();
557 for (int i
= 0; i
< text
.length(); ) {
558 int ch
= text
.codePointAt(i
);
561 onKeypress(new TKeypressEvent(kbEnter
));
564 onKeypress(new TKeypressEvent(kbTab
));
567 if ((ch
>= 0x20) && (ch
!= 0x7F)) {
568 onKeypress(new TKeypressEvent(false, 0, ch
,
569 false, false, false));
574 i
+= Character
.charCount(ch
);
580 if (command
.equals(cmClear
)) {
588 // ------------------------------------------------------------------------
589 // TWidget ----------------------------------------------------------------
590 // ------------------------------------------------------------------------
597 CellAttributes selectedColor
= getTheme().getColor("teditor.selected");
599 boolean drawSelection
= true;
601 int startCol
= selectionColumn0
;
602 int startRow
= selectionLine0
;
603 int endCol
= selectionColumn1
;
604 int endRow
= selectionLine1
;
606 if (((selectionColumn1
< selectionColumn0
)
607 && (selectionLine1
== selectionLine0
))
608 || (selectionLine1
< selectionLine0
)
610 // The user selected from bottom-to-top and/or right-to-left.
611 // Reverse the coordinates for the inverted section.
612 startCol
= selectionColumn1
;
613 startRow
= selectionLine1
;
614 endCol
= selectionColumn0
;
615 endRow
= selectionLine0
;
617 if ((startCol
== endCol
) && (startRow
== endRow
)) {
618 drawSelection
= false;
621 for (int i
= 0; i
< getHeight(); i
++) {
623 getScreen().hLineXY(0, i
, getWidth(), ' ', defaultColor
);
625 // Now draw document's line
626 if (topLine
+ i
< document
.getLineCount()) {
627 Line line
= document
.getLine(topLine
+ i
);
629 for (Word word
: line
.getWords()) {
630 // For now, we are cheating: draw outside the left region
631 // if needed and let screen do the clipping.
632 getScreen().putStringXY(x
- leftColumn
, i
, word
.getText(),
634 x
+= word
.getDisplayLength();
635 if (x
- leftColumn
> getWidth()) {
640 // Highlight selected region
641 if (inSelection
&& drawSelection
) {
642 if (startRow
== endRow
) {
643 if (topLine
+ i
== startRow
) {
644 for (x
= startCol
; x
<= endCol
; x
++) {
645 putAttrXY(x
- leftColumn
, i
, selectedColor
);
649 if (topLine
+ i
== startRow
) {
650 for (x
= startCol
; x
< line
.getDisplayLength(); x
++) {
651 putAttrXY(x
- leftColumn
, i
, selectedColor
);
653 } else if (topLine
+ i
== endRow
) {
654 for (x
= 0; x
<= endCol
; x
++) {
655 putAttrXY(x
- leftColumn
, i
, selectedColor
);
657 } else if ((topLine
+ i
>= startRow
)
658 && (topLine
+ i
<= endRow
)
660 for (x
= 0; x
< getWidth(); x
++) {
661 putAttrXY(x
, i
, selectedColor
);
671 // ------------------------------------------------------------------------
672 // TEditorWidget ----------------------------------------------------------
673 // ------------------------------------------------------------------------
676 * Set the undo level.
678 * @param undoLevel the maximum number of undo operations
680 public void setUndoLevel(final int undoLevel
) {
681 this.undoLevel
= undoLevel
;
685 * Align visible area with document current line.
687 * @param topLineIsTop if true, make the top visible line the document
688 * current line if it was off-screen. If false, make the bottom visible
689 * line the document current line.
691 private void alignTopLine(final boolean topLineIsTop
) {
692 int line
= document
.getLineNumber();
694 if ((line
< topLine
) || (line
> topLine
+ getHeight() - 1)) {
695 // Need to move topLine to bring document back into view.
697 topLine
= line
- (getHeight() - 1);
701 assert (topLine
>= 0);
704 assert (topLine
>= 0);
709 System.err.println("line " + line + " topLine " + topLine);
712 // Document is in view, let's set cursorY
713 assert (line
>= topLine
);
714 setCursorY(line
- topLine
);
719 * Align document current line with visible area.
721 * @param topLineIsTop if true, make the top visible line the document
722 * current line if it was off-screen. If false, make the bottom visible
723 * line the document current line.
725 private void alignDocument(final boolean topLineIsTop
) {
726 int line
= document
.getLineNumber();
727 int cursor
= document
.getCursor();
729 if ((line
< topLine
) || (line
> topLine
+ getHeight() - 1)) {
730 // Need to move document to ensure it fits view.
732 document
.setLineNumber(topLine
);
734 document
.setLineNumber(topLine
+ (getHeight() - 1));
736 if (cursor
< document
.getCurrentLine().getDisplayLength()) {
737 document
.setCursor(cursor
);
742 System.err.println("getLineNumber() " + document.getLineNumber() +
743 " topLine " + topLine);
746 // Document is in view, let's set cursorY
747 setCursorY(document
.getLineNumber() - topLine
);
752 * Align visible cursor with document cursor.
754 private void alignCursor() {
755 int width
= getWidth();
757 int desiredX
= document
.getCursor() - leftColumn
;
759 // We need to push the screen to the left.
760 leftColumn
= document
.getCursor();
761 } else if (desiredX
> width
- 1) {
762 // We need to push the screen to the right.
763 leftColumn
= document
.getCursor() - (width
- 1);
767 System.err.println("document cursor " + document.getCursor() +
768 " leftColumn " + leftColumn);
772 setCursorX(document
.getCursor() - leftColumn
);
776 * Get the number of lines in the underlying Document.
778 * @return the number of lines
780 public int getLineCount() {
781 return document
.getLineCount();
785 * Get the current visible top row number. 1-based.
787 * @return the visible top row number. Row 1 is the first row.
789 public int getVisibleRowNumber() {
794 * Set the current visible row number. 1-based.
796 * @param row the new visible row number. Row 1 is the first row.
798 public void setVisibleRowNumber(final int row
) {
800 if ((row
> 0) && (row
< document
.getLineCount())) {
807 * Get the current editing row number. 1-based.
809 * @return the editing row number. Row 1 is the first row.
811 public int getEditingRowNumber() {
812 return document
.getLineNumber() + 1;
816 * Set the current editing row number. 1-based.
818 * @param row the new editing row number. Row 1 is the first row.
820 public void setEditingRowNumber(final int row
) {
822 if ((row
> 0) && (row
< document
.getLineCount())) {
823 document
.setLineNumber(row
- 1);
829 * Set the current visible column number. 1-based.
831 * @return the visible column number. Column 1 is the first column.
833 public int getVisibleColumnNumber() {
834 return leftColumn
+ 1;
838 * Set the current visible column number. 1-based.
840 * @param column the new visible column number. Column 1 is the first
843 public void setVisibleColumnNumber(final int column
) {
845 if ((column
> 0) && (column
< document
.getLineLengthMax())) {
846 leftColumn
= column
- 1;
852 * Get the current editing column number. 1-based.
854 * @return the editing column number. Column 1 is the first column.
856 public int getEditingColumnNumber() {
857 return document
.getCursor() + 1;
861 * Set the current editing column number. 1-based.
863 * @param column the new editing column number. Column 1 is the first
866 public void setEditingColumnNumber(final int column
) {
867 if ((column
> 0) && (column
< document
.getLineLength())) {
868 document
.setCursor(column
- 1);
874 * Get the maximum possible row number. 1-based.
876 * @return the maximum row number. Row 1 is the first row.
878 public int getMaximumRowNumber() {
879 return document
.getLineCount() + 1;
883 * Get the maximum possible column number. 1-based.
885 * @return the maximum column number. Column 1 is the first column.
887 public int getMaximumColumnNumber() {
888 return document
.getLineLengthMax() + 1;
892 * Get the current editing row plain text. 1-based.
894 * @param row the editing row number. Row 1 is the first row.
895 * @return the plain text of the row
897 public String
getEditingRawLine(final int row
) {
898 Line line
= document
.getLine(row
- 1);
899 return line
.getRawString();
903 * Get the dirty value.
905 * @return true if the buffer is dirty
907 public boolean isDirty() {
908 return document
.isDirty();
912 * Unset the dirty flag.
914 public void setNotDirty() {
915 document
.setNotDirty();
919 * Get the overwrite value.
921 * @return true if new text will overwrite old text
923 public boolean isOverwrite() {
924 return document
.isOverwrite();
928 * Save contents to file.
930 * @param filename file to save to
931 * @throws IOException if a java.io operation throws
933 public void saveToFilename(final String filename
) throws IOException
{
934 document
.saveToFilename(filename
);
938 * Delete text within the selection bounds.
940 private void deleteSelection() {
949 int startCol
= selectionColumn0
;
950 int startRow
= selectionLine0
;
951 int endCol
= selectionColumn1
;
952 int endRow
= selectionLine1
;
955 System.err.println("INITIAL: " + startRow + " " + startCol + " " +
956 endRow + " " + endCol + " " +
957 document.getLineNumber() + " " + document.getCursor());
960 if (((selectionColumn1
< selectionColumn0
)
961 && (selectionLine1
== selectionLine0
))
962 || (selectionLine1
< selectionLine0
)
964 // The user selected from bottom-to-top and/or right-to-left.
965 // Reverse the coordinates for the inverted section.
966 startCol
= selectionColumn1
;
967 startRow
= selectionLine1
;
968 endCol
= selectionColumn0
;
969 endRow
= selectionLine0
;
971 if (endRow
>= document
.getLineCount()) {
972 // The selection started beyond EOF, trim it to EOF.
973 endRow
= document
.getLineCount() - 1;
974 endCol
= document
.getLine(endRow
).getDisplayLength();
975 } else if (endRow
== document
.getLineCount() - 1) {
976 // The selection started beyond EOF, trim it to EOF.
977 if (endCol
>= document
.getLine(endRow
).getDisplayLength()) {
978 endCol
= document
.getLine(endRow
).getDisplayLength() - 1;
983 System.err.println("FLIP: " + startRow + " " + startCol + " " +
984 endRow + " " + endCol + " " +
985 document.getLineNumber() + " " + document.getCursor());
986 System.err.println(" --END: " + endRow + " " + document.getLineCount() +
987 " " + document.getLine(endRow).getDisplayLength());
990 assert (endRow
< document
.getLineCount());
991 if (endCol
>= document
.getLine(endRow
).getDisplayLength()) {
992 endCol
= document
.getLine(endRow
).getDisplayLength() - 1;
997 if (startCol
>= document
.getLine(startRow
).getDisplayLength()) {
998 startCol
= document
.getLine(startRow
).getDisplayLength() - 1;
1004 // Place the cursor on the selection end, and "press backspace" until
1005 // the cursor matches the selection start.
1007 System.err.println("BEFORE: " + startRow + " " + startCol + " " +
1008 endRow + " " + endCol + " " +
1009 document.getLineNumber() + " " + document.getCursor());
1011 document
.setLineNumber(endRow
);
1012 document
.setCursor(endCol
+ 1);
1013 while (!((document
.getLineNumber() == startRow
)
1014 && (document
.getCursor() == startCol
))
1017 System.err.println("DURING: " + startRow + " " + startCol + " " +
1018 endRow + " " + endCol + " " +
1019 document.getLineNumber() + " " + document.getCursor());
1022 document
.backspace();
1028 * Copy text within the selection bounds to clipboard.
1030 private void copySelection() {
1034 getClipboard().copyText(getSelection());
1038 * Set the selection.
1040 * @param startRow the starting row number. 0-based: row 0 is the first
1042 * @param startColumn the starting column number. 0-based: column 0 is
1044 * @param endRow the ending row number. 0-based: row 0 is the first row.
1045 * @param endColumn the ending column number. 0-based: column 0 is the
1048 public void setSelection(final int startRow
, final int startColumn
,
1049 final int endRow
, final int endColumn
) {
1052 selectionLine0
= startRow
;
1053 selectionColumn0
= startColumn
;
1054 selectionLine1
= endRow
;
1055 selectionColumn1
= endColumn
;
1059 * Copy text within the selection bounds to a string.
1061 * @return the selection as a string, or null if there is no selection
1063 public String
getSelection() {
1068 int startCol
= selectionColumn0
;
1069 int startRow
= selectionLine0
;
1070 int endCol
= selectionColumn1
;
1071 int endRow
= selectionLine1
;
1073 if (((selectionColumn1
< selectionColumn0
)
1074 && (selectionLine1
== selectionLine0
))
1075 || (selectionLine1
< selectionLine0
)
1077 // The user selected from bottom-to-top and/or right-to-left.
1078 // Reverse the coordinates for the inverted section.
1079 startCol
= selectionColumn1
;
1080 startRow
= selectionLine1
;
1081 endCol
= selectionColumn0
;
1082 endRow
= selectionLine0
;
1085 StringBuilder sb
= new StringBuilder();
1087 if (endRow
> startRow
) {
1089 String line
= document
.getLine(startRow
).getRawString();
1091 for (int i
= 0; i
< line
.length(); ) {
1092 int ch
= line
.codePointAt(i
);
1094 if (x
>= startCol
) {
1095 sb
.append(Character
.toChars(ch
));
1097 x
+= StringUtils
.width(ch
);
1098 i
+= Character
.charCount(ch
);
1103 for (int y
= startRow
+ 1; y
< endRow
; y
++) {
1104 sb
.append(document
.getLine(y
).getRawString());
1109 line
= document
.getLine(endRow
).getRawString();
1111 for (int i
= 0; i
< line
.length(); ) {
1112 int ch
= line
.codePointAt(i
);
1118 sb
.append(Character
.toChars(ch
));
1119 x
+= StringUtils
.width(ch
);
1120 i
+= Character
.charCount(ch
);
1123 assert (startRow
== endRow
);
1126 String line
= document
.getLine(startRow
).getRawString();
1128 for (int i
= 0; i
< line
.length(); ) {
1129 int ch
= line
.codePointAt(i
);
1131 if ((x
>= startCol
) && (x
<= endCol
)) {
1132 sb
.append(Character
.toChars(ch
));
1135 x
+= StringUtils
.width(ch
);
1136 i
+= Character
.charCount(ch
);
1139 return sb
.toString();
1143 * Get the selection starting row number.
1145 * @return the starting row number, or -1 if there is no selection.
1146 * 0-based: row 0 is the first row.
1148 public int getSelectionStartRow() {
1153 int startCol
= selectionColumn0
;
1154 int startRow
= selectionLine0
;
1155 int endCol
= selectionColumn1
;
1156 int endRow
= selectionLine1
;
1158 if (((selectionColumn1
< selectionColumn0
)
1159 && (selectionLine1
== selectionLine0
))
1160 || (selectionLine1
< selectionLine0
)
1162 // The user selected from bottom-to-top and/or right-to-left.
1163 // Reverse the coordinates for the inverted section.
1164 startCol
= selectionColumn1
;
1165 startRow
= selectionLine1
;
1166 endCol
= selectionColumn0
;
1167 endRow
= selectionLine0
;
1173 * Get the selection starting column number.
1175 * @return the starting column number, or -1 if there is no selection.
1176 * 0-based: column 0 is the first column.
1178 public int getSelectionStartColumn() {
1183 int startCol
= selectionColumn0
;
1184 int startRow
= selectionLine0
;
1185 int endCol
= selectionColumn1
;
1186 int endRow
= selectionLine1
;
1188 if (((selectionColumn1
< selectionColumn0
)
1189 && (selectionLine1
== selectionLine0
))
1190 || (selectionLine1
< selectionLine0
)
1192 // The user selected from bottom-to-top and/or right-to-left.
1193 // Reverse the coordinates for the inverted section.
1194 startCol
= selectionColumn1
;
1195 startRow
= selectionLine1
;
1196 endCol
= selectionColumn0
;
1197 endRow
= selectionLine0
;
1203 * Get the selection ending row number.
1205 * @return the ending row number, or -1 if there is no selection.
1206 * 0-based: row 0 is the first row.
1208 public int getSelectionEndRow() {
1213 int startCol
= selectionColumn0
;
1214 int startRow
= selectionLine0
;
1215 int endCol
= selectionColumn1
;
1216 int endRow
= selectionLine1
;
1218 if (((selectionColumn1
< selectionColumn0
)
1219 && (selectionLine1
== selectionLine0
))
1220 || (selectionLine1
< selectionLine0
)
1222 // The user selected from bottom-to-top and/or right-to-left.
1223 // Reverse the coordinates for the inverted section.
1224 startCol
= selectionColumn1
;
1225 startRow
= selectionLine1
;
1226 endCol
= selectionColumn0
;
1227 endRow
= selectionLine0
;
1233 * Get the selection ending column number.
1235 * @return the ending column number, or -1 if there is no selection.
1236 * 0-based: column 0 is the first column.
1238 public int getSelectionEndColumn() {
1243 int startCol
= selectionColumn0
;
1244 int startRow
= selectionLine0
;
1245 int endCol
= selectionColumn1
;
1246 int endRow
= selectionLine1
;
1248 if (((selectionColumn1
< selectionColumn0
)
1249 && (selectionLine1
== selectionLine0
))
1250 || (selectionLine1
< selectionLine0
)
1252 // The user selected from bottom-to-top and/or right-to-left.
1253 // Reverse the coordinates for the inverted section.
1254 startCol
= selectionColumn1
;
1255 startRow
= selectionLine1
;
1256 endCol
= selectionColumn0
;
1257 endRow
= selectionLine0
;
1263 * Unset the selection.
1265 public void unsetSelection() {
1266 inSelection
= false;
1270 * Replace whatever is being selected with new text. If not in
1271 * selection, nothing is replaced.
1273 * @param text the new replacement text
1275 public void replaceSelection(final String text
) {
1280 // Delete selected text, then paste text from clipboard.
1283 for (int i
= 0; i
< text
.length(); ) {
1284 int ch
= text
.codePointAt(i
);
1287 onKeypress(new TKeypressEvent(kbEnter
));
1290 onKeypress(new TKeypressEvent(kbTab
));
1293 if ((ch
>= 0x20) && (ch
!= 0x7F)) {
1294 onKeypress(new TKeypressEvent(false, 0, ch
,
1295 false, false, false));
1299 i
+= Character
.charCount(ch
);
1304 * Check if selection is available.
1306 * @return true if a selection has been made
1308 public boolean hasSelection() {
1313 * Get the entire contents of the editor as one string.
1315 * @return the editor contents
1317 public String
getText() {
1318 return document
.getText();
1322 * Set the entire contents of the editor from one string.
1324 * @param text the new contents
1326 public void setText(final String text
) {
1327 document
= new Document(text
, defaultColor
);
1333 // ------------------------------------------------------------------------
1334 // EditMenuUser -----------------------------------------------------------
1335 // ------------------------------------------------------------------------
1338 * Check if the cut menu item should be enabled.
1340 * @return true if the cut menu item should be enabled
1342 public boolean isEditMenuCut() {
1347 * Check if the copy menu item should be enabled.
1349 * @return true if the copy menu item should be enabled
1351 public boolean isEditMenuCopy() {
1356 * Check if the paste menu item should be enabled.
1358 * @return true if the paste menu item should be enabled
1360 public boolean isEditMenuPaste() {
1365 * Check if the clear menu item should be enabled.
1367 * @return true if the clear menu item should be enabled
1369 public boolean isEditMenuClear() {
1376 private void saveUndo() {
1377 SavedState state
= new SavedState();
1378 state
.document
= document
.dup();
1379 state
.topLine
= topLine
;
1380 state
.leftColumn
= leftColumn
;
1381 if (undoLevel
> 0) {
1382 while (undoList
.size() > undoLevel
) {
1386 undoList
.add(state
);
1387 undoListI
= undoList
.size() - 1;
1393 public void undo() {
1394 inSelection
= false;
1395 if ((undoListI
>= 0) && (undoListI
< undoList
.size())) {
1396 SavedState state
= undoList
.get(undoListI
);
1397 document
= state
.document
.dup();
1398 topLine
= state
.topLine
;
1399 leftColumn
= state
.leftColumn
;
1401 setCursorY(document
.getLineNumber() - topLine
);
1409 public void redo() {
1410 inSelection
= false;
1411 if ((undoListI
>= 0) && (undoListI
< undoList
.size())) {
1412 SavedState state
= undoList
.get(undoListI
);
1413 document
= state
.document
.dup();
1414 topLine
= state
.topLine
;
1415 leftColumn
= state
.leftColumn
;
1417 setCursorY(document
.getLineNumber() - topLine
);
1423 * Trim trailing whitespace from lines and trailing empty
1424 * lines from the document.
1426 public void cleanWhitespace() {
1427 document
.cleanWhitespace();
1428 setCursorY(document
.getLineNumber() - topLine
);
1433 * Set keyword highlighting.
1435 * @param enabled if true, enable keyword highlighting
1437 public void setHighlighting(final boolean enabled
) {
1438 document
.setHighlighting(enabled
);