package jexer;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import jexer.bits.CellAttributes;
import jexer.bits.StringUtils;
/**
* The document being edited.
*/
- private Document document;
+ protected Document document;
/**
- * The default color for the TEditor class.
+ * The default color for the editable text.
*/
private CellAttributes defaultColor = null;
*/
private int selectionLine1;
+ /**
+ * The list of undo/redo states.
+ */
+ private List<SavedState> undoList = new ArrayList<SavedState>();
+
+ /**
+ * The position in undoList for undo/redo.
+ */
+ private int undoListI = 0;
+
+ /**
+ * The maximum size of the undo list.
+ */
+ private int undoLevel = 50;
+
+ /**
+ * The saved state for an undo/redo operation.
+ */
+ private class SavedState {
+ /**
+ * The Document state.
+ */
+ public Document document;
+
+ /**
+ * The topmost line number in the visible area. 0-based.
+ */
+ public int topLine = 0;
+
+ /**
+ * The leftmost column number in the visible area. 0-based.
+ */
+ public int leftColumn = 0;
+
+ }
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
if (mouse.isMouse1()) {
// Selection.
+ int newLine = topLine + mouse.getY();
+ int newX = leftColumn + mouse.getX();
+
inSelection = true;
+ if (newLine > document.getLineCount() - 1) {
+ selectionLine0 = document.getLineCount() - 1;
+ } else {
+ selectionLine0 = topLine + mouse.getY();
+ }
selectionColumn0 = leftColumn + mouse.getX();
- selectionLine0 = topLine + mouse.getY();
+ selectionColumn0 = Math.max(0, Math.min(selectionColumn0,
+ document.getLine(selectionLine0).getDisplayLength() - 1));
selectionColumn1 = selectionColumn0;
selectionLine1 = selectionLine0;
// Set the row and column
- int newLine = topLine + mouse.getY();
- int newX = leftColumn + mouse.getX();
if (newLine > document.getLineCount() - 1) {
// Go to the end
document.setLineNumber(document.getLineCount() - 1);
@Override
public void onKeypress(final TKeypressEvent keypress) {
if (keypress.getKey().isShift()) {
- // Selection.
- if (!inSelection) {
- inSelection = true;
- selectionColumn0 = document.getCursor();
- selectionLine0 = document.getLineNumber();
- selectionColumn1 = selectionColumn0;
- selectionLine1 = selectionLine0;
+ if (keypress.equals(kbShiftLeft)
+ || keypress.equals(kbShiftRight)
+ || keypress.equals(kbShiftUp)
+ || keypress.equals(kbShiftDown)
+ || keypress.equals(kbShiftPgDn)
+ || keypress.equals(kbShiftPgUp)
+ || keypress.equals(kbShiftHome)
+ || keypress.equals(kbShiftEnd)
+ ) {
+ // Shifted navigation keys enable selection
+ if (!inSelection) {
+ inSelection = true;
+ selectionColumn0 = document.getCursor();
+ selectionLine0 = document.getLineNumber();
+ selectionColumn1 = selectionColumn0;
+ selectionLine1 = selectionLine0;
+ }
}
} else {
if (keypress.equals(kbLeft)
// Non-shifted navigation keys disable selection.
inSelection = false;
}
+ if ((selectionColumn0 == selectionColumn1)
+ && (selectionLine0 == selectionLine1)
+ ) {
+ // The user clicked a spot and started typing.
+ inSelection = false;
+ }
}
if (keypress.equals(kbLeft)
document.end();
alignTopLine(false);
} else if (keypress.equals(kbIns)) {
- document.setOverwrite(!document.getOverwrite());
+ document.setOverwrite(!document.isOverwrite());
} else if (keypress.equals(kbDel)) {
if (inSelection) {
deleteSelection();
+ alignCursor();
} else {
+ saveUndo();
document.del();
+ alignCursor();
}
- alignCursor();
} else if (keypress.equals(kbBackspace)
|| keypress.equals(kbBackspaceDel)
) {
if (inSelection) {
deleteSelection();
+ alignTopLine(false);
} else {
+ saveUndo();
document.backspace();
+ alignTopLine(false);
}
- alignTopLine(false);
} else if (keypress.equals(kbTab)) {
deleteSelection();
- // Add spaces until we hit modulo 8.
- for (int i = document.getCursor(); (i + 1) % 8 != 0; i++) {
- document.addChar(' ');
- }
+ saveUndo();
+ document.tab();
+ alignCursor();
+ } else if (keypress.equals(kbShiftTab)) {
+ deleteSelection();
+ saveUndo();
+ document.backTab();
alignCursor();
} else if (keypress.equals(kbEnter)) {
deleteSelection();
+ saveUndo();
document.enter();
alignTopLine(true);
} else if (!keypress.getKey().isFnKey()
) {
// Plain old keystroke, process it
deleteSelection();
+ saveUndo();
document.addChar(keypress.getKey().getChar());
alignCursor();
} else {
if (text != null) {
for (int i = 0; i < text.length(); ) {
int ch = text.codePointAt(i);
- onKeypress(new TKeypressEvent(false, 0, ch, false, false,
- false));
+ switch (ch) {
+ case '\n':
+ onKeypress(new TKeypressEvent(kbEnter));
+ break;
+ case '\t':
+ onKeypress(new TKeypressEvent(kbTab));
+ break;
+ default:
+ if ((ch >= 0x20) && (ch != 0x7F)) {
+ onKeypress(new TKeypressEvent(false, 0, ch,
+ false, false, false));
+ }
+ break;
+ }
+
i += Character.charCount(ch);
}
}
public void draw() {
CellAttributes selectedColor = getTheme().getColor("teditor.selected");
+ boolean drawSelection = true;
+
int startCol = selectionColumn0;
int startRow = selectionLine0;
int endCol = selectionColumn1;
endCol = selectionColumn0;
endRow = selectionLine0;
}
+ if ((startCol == endCol) && (startRow == endRow)) {
+ drawSelection = false;
+ }
for (int i = 0; i < getHeight(); i++) {
// Background line
}
// Highlight selected region
- if (inSelection) {
+ if (inSelection && drawSelection) {
if (startRow == endRow) {
if (topLine + i == startRow) {
for (x = startCol; x <= endCol; x++) {
// TEditorWidget ----------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Set the undo level.
+ *
+ * @param undoLevel the maximum number of undo operations
+ */
+ public void setUndoLevel(final int undoLevel) {
+ this.undoLevel = undoLevel;
+ }
+
/**
* Align visible area with document current line.
*
document.setNotDirty();
}
+ /**
+ * Get the overwrite value.
+ *
+ * @return true if new text will overwrite old text
+ */
+ public boolean isOverwrite() {
+ return document.isOverwrite();
+ }
+
/**
* Save contents to file.
*
* Delete text within the selection bounds.
*/
private void deleteSelection() {
- if (inSelection == false) {
+ if (!inSelection) {
return;
}
+
+ saveUndo();
+
inSelection = false;
int startCol = selectionColumn0;
* Copy text within the selection bounds to clipboard.
*/
private void copySelection() {
- if (inSelection == false) {
+ if (!inSelection) {
return;
}
+ getClipboard().copyText(getSelection());
+ }
+
+ /**
+ * Set the selection.
+ *
+ * @param startRow the starting row number. 0-based: row 0 is the first
+ * row.
+ * @param startColumn the starting column number. 0-based: column 0 is
+ * the first column.
+ * @param endRow the ending row number. 0-based: row 0 is the first row.
+ * @param endColumn the ending column number. 0-based: column 0 is the
+ * first column.
+ */
+ public void setSelection(final int startRow, final int startColumn,
+ final int endRow, final int endColumn) {
+
+ inSelection = true;
+ selectionLine0 = startRow;
+ selectionColumn0 = startColumn;
+ selectionLine1 = endRow;
+ selectionColumn1 = endColumn;
+ }
+
+ /**
+ * Copy text within the selection bounds to a string.
+ *
+ * @return the selection as a string, or null if there is no selection
+ */
+ public String getSelection() {
+ if (!inSelection) {
+ return null;
+ }
int startCol = selectionColumn0;
int startRow = selectionLine0;
i += Character.charCount(ch);
}
}
+ return sb.toString();
+ }
- getClipboard().copyText(sb.toString());
+ /**
+ * Get the selection starting row number.
+ *
+ * @return the starting row number, or -1 if there is no selection.
+ * 0-based: row 0 is the first row.
+ */
+ public int getSelectionStartRow() {
+ if (!inSelection) {
+ return -1;
+ }
+
+ int startCol = selectionColumn0;
+ int startRow = selectionLine0;
+ int endCol = selectionColumn1;
+ int endRow = selectionLine1;
+
+ if (((selectionColumn1 < selectionColumn0)
+ && (selectionLine1 == selectionLine0))
+ || (selectionLine1 < selectionLine0)
+ ) {
+ // The user selected from bottom-to-top and/or right-to-left.
+ // Reverse the coordinates for the inverted section.
+ startCol = selectionColumn1;
+ startRow = selectionLine1;
+ endCol = selectionColumn0;
+ endRow = selectionLine0;
+ }
+ return startRow;
}
/**
- * Set the selection.
+ * Get the selection starting column number.
*
- * @param startRow the starting row number. 0-based: row 0 is the first
- * row.
- * @param startColumn the starting column number. 0-based: column 0 is
- * the first column.
- * @param endRow the ending row number. 0-based: row 0 is the first row.
- * @param endColumn the ending column number. 0-based: column 0 is the
- * first column.
+ * @return the starting column number, or -1 if there is no selection.
+ * 0-based: column 0 is the first column.
*/
- public void setSelection(final int startRow, final int startColumn,
- final int endRow, final int endColumn) {
+ public int getSelectionStartColumn() {
+ if (!inSelection) {
+ return -1;
+ }
- inSelection = true;
- selectionLine0 = startRow;
- selectionColumn0 = startColumn;
- selectionLine1 = endRow;
- selectionColumn1 = endColumn;
+ int startCol = selectionColumn0;
+ int startRow = selectionLine0;
+ int endCol = selectionColumn1;
+ int endRow = selectionLine1;
+
+ if (((selectionColumn1 < selectionColumn0)
+ && (selectionLine1 == selectionLine0))
+ || (selectionLine1 < selectionLine0)
+ ) {
+ // The user selected from bottom-to-top and/or right-to-left.
+ // Reverse the coordinates for the inverted section.
+ startCol = selectionColumn1;
+ startRow = selectionLine1;
+ endCol = selectionColumn0;
+ endRow = selectionLine0;
+ }
+ return startCol;
+ }
+
+ /**
+ * Get the selection ending row number.
+ *
+ * @return the ending row number, or -1 if there is no selection.
+ * 0-based: row 0 is the first row.
+ */
+ public int getSelectionEndRow() {
+ if (!inSelection) {
+ return -1;
+ }
+
+ int startCol = selectionColumn0;
+ int startRow = selectionLine0;
+ int endCol = selectionColumn1;
+ int endRow = selectionLine1;
+
+ if (((selectionColumn1 < selectionColumn0)
+ && (selectionLine1 == selectionLine0))
+ || (selectionLine1 < selectionLine0)
+ ) {
+ // The user selected from bottom-to-top and/or right-to-left.
+ // Reverse the coordinates for the inverted section.
+ startCol = selectionColumn1;
+ startRow = selectionLine1;
+ endCol = selectionColumn0;
+ endRow = selectionLine0;
+ }
+ return endRow;
+ }
+
+ /**
+ * Get the selection ending column number.
+ *
+ * @return the ending column number, or -1 if there is no selection.
+ * 0-based: column 0 is the first column.
+ */
+ public int getSelectionEndColumn() {
+ if (!inSelection) {
+ return -1;
+ }
+
+ int startCol = selectionColumn0;
+ int startRow = selectionLine0;
+ int endCol = selectionColumn1;
+ int endRow = selectionLine1;
+
+ if (((selectionColumn1 < selectionColumn0)
+ && (selectionLine1 == selectionLine0))
+ || (selectionLine1 < selectionLine0)
+ ) {
+ // The user selected from bottom-to-top and/or right-to-left.
+ // Reverse the coordinates for the inverted section.
+ startCol = selectionColumn1;
+ startRow = selectionLine1;
+ endCol = selectionColumn0;
+ endRow = selectionLine0;
+ }
+ return endCol;
}
/**
for (int i = 0; i < text.length(); ) {
int ch = text.codePointAt(i);
- onKeypress(new TKeypressEvent(false, 0, ch, false, false,
- false));
+ switch (ch) {
+ case '\n':
+ onKeypress(new TKeypressEvent(kbEnter));
+ break;
+ case '\t':
+ onKeypress(new TKeypressEvent(kbTab));
+ break;
+ default:
+ if ((ch >= 0x20) && (ch != 0x7F)) {
+ onKeypress(new TKeypressEvent(false, 0, ch,
+ false, false, false));
+ }
+ break;
+ }
i += Character.charCount(ch);
}
}
+ /**
+ * Check if selection is available.
+ *
+ * @return true if a selection has been made
+ */
+ public boolean hasSelection() {
+ return inSelection;
+ }
+
/**
* Get the entire contents of the editor as one string.
*
return true;
}
+ /**
+ * Save undo state.
+ */
+ private void saveUndo() {
+ SavedState state = new SavedState();
+ state.document = document.dup();
+ state.topLine = topLine;
+ state.leftColumn = leftColumn;
+ if (undoLevel > 0) {
+ while (undoList.size() > undoLevel) {
+ undoList.remove(0);
+ }
+ }
+ undoList.add(state);
+ undoListI = undoList.size() - 1;
+ }
+
+ /**
+ * Undo an edit.
+ */
+ public void undo() {
+ inSelection = false;
+ if ((undoListI >= 0) && (undoListI < undoList.size())) {
+ SavedState state = undoList.get(undoListI);
+ document = state.document.dup();
+ topLine = state.topLine;
+ leftColumn = state.leftColumn;
+ undoListI--;
+ setCursorY(document.getLineNumber() - topLine);
+ alignCursor();
+ }
+ }
+
+ /**
+ * Redo an edit.
+ */
+ public void redo() {
+ inSelection = false;
+ if ((undoListI >= 0) && (undoListI < undoList.size())) {
+ SavedState state = undoList.get(undoListI);
+ document = state.document.dup();
+ topLine = state.topLine;
+ leftColumn = state.leftColumn;
+ undoListI++;
+ setCursorY(document.getLineNumber() - topLine);
+ alignCursor();
+ }
+ }
+
+ /**
+ * Trim trailing whitespace from lines and trailing empty
+ * lines from the document.
+ */
+ public void cleanWhitespace() {
+ document.cleanWhitespace();
+ setCursorY(document.getLineNumber() - topLine);
+ alignCursor();
+ }
+
+ /**
+ * Set keyword highlighting.
+ *
+ * @param enabled if true, enable keyword highlighting
+ */
+ public void setHighlighting(final boolean enabled) {
+ document.setHighlighting(enabled);
+ }
+
}