X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTEditorWidget.java;h=bea25eda3e74c9c0e2c60c012dfd4e976d1198e4;hb=505be508ae7d3fb48122be548b310a238cfb91eb;hp=7798ad84c6a425b7a798860febcfe24289cbd79f;hpb=ed76cd41d2764d6c5d3ef03449723579af9a2197;p=fanfix.git diff --git a/src/jexer/TEditorWidget.java b/src/jexer/TEditorWidget.java index 7798ad8..bea25ed 100644 --- a/src/jexer/TEditorWidget.java +++ b/src/jexer/TEditorWidget.java @@ -29,6 +29,8 @@ package jexer; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import jexer.bits.CellAttributes; import jexer.bits.StringUtils; @@ -64,10 +66,10 @@ public class TEditorWidget extends TWidget implements EditMenuUser { /** * 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; @@ -106,6 +108,42 @@ public class TEditorWidget extends TWidget implements EditMenuUser { */ private int selectionLine1; + /** + * The list of undo/redo states. + */ + private List undoList = new ArrayList(); + + /** + * 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 ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -164,15 +202,22 @@ public class TEditorWidget extends TWidget implements EditMenuUser { 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); @@ -284,13 +329,23 @@ public class TEditorWidget extends TWidget implements EditMenuUser { @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) @@ -305,6 +360,12 @@ public class TEditorWidget extends TWidget implements EditMenuUser { // 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) @@ -383,32 +444,40 @@ public class TEditorWidget extends TWidget implements EditMenuUser { 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() @@ -417,6 +486,7 @@ public class TEditorWidget extends TWidget implements EditMenuUser { ) { // Plain old keystroke, process it deleteSelection(); + saveUndo(); document.addChar(keypress.getKey().getChar()); alignCursor(); } else { @@ -486,8 +556,21 @@ public class TEditorWidget extends TWidget implements EditMenuUser { 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); } } @@ -513,6 +596,8 @@ public class TEditorWidget extends TWidget implements EditMenuUser { public void draw() { CellAttributes selectedColor = getTheme().getColor("teditor.selected"); + boolean drawSelection = true; + int startCol = selectionColumn0; int startRow = selectionLine0; int endCol = selectionColumn1; @@ -529,6 +614,9 @@ public class TEditorWidget extends TWidget implements EditMenuUser { endCol = selectionColumn0; endRow = selectionLine0; } + if ((startCol == endCol) && (startRow == endRow)) { + drawSelection = false; + } for (int i = 0; i < getHeight(); i++) { // Background line @@ -550,7 +638,7 @@ public class TEditorWidget extends TWidget implements EditMenuUser { } // Highlight selected region - if (inSelection) { + if (inSelection && drawSelection) { if (startRow == endRow) { if (topLine + i == startRow) { for (x = startCol; x <= endCol; x++) { @@ -584,6 +672,15 @@ public class TEditorWidget extends TWidget implements EditMenuUser { // 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. * @@ -818,6 +915,15 @@ public class TEditorWidget extends TWidget implements EditMenuUser { 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. * @@ -832,9 +938,12 @@ public class TEditorWidget extends TWidget implements EditMenuUser { * Delete text within the selection bounds. */ private void deleteSelection() { - if (inSelection == false) { + if (!inSelection) { return; } + + saveUndo(); + inSelection = false; int startCol = selectionColumn0; @@ -919,9 +1028,42 @@ public class TEditorWidget extends TWidget implements EditMenuUser { * 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; @@ -994,29 +1136,127 @@ public class TEditorWidget extends TWidget implements EditMenuUser { 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; } /** @@ -1042,12 +1282,33 @@ public class TEditorWidget extends TWidget implements EditMenuUser { 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. * @@ -1109,4 +1370,72 @@ public class TEditorWidget extends TWidget implements EditMenuUser { 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); + } + }