0.0.5
- TEditor
+ - Document
+ - Filename
+ - Pick appropriate Highlighter: plain, Java, XML, ...
- TEditorWidget:
- - Mouse wheel is buggy as hell
- - Actual editing
- Cut and Paste
- - TEditorWindow extends TScrollableWindow
- TTextArea extends TScrollableWidget
0.0.6
- TEditor
- - True tokenization and syntax highlighting: Java, C, Clojure
+ - True tokenization and syntax highlighting: Java, C, Clojure, XML
+ - Tab character support
- Finish up multiscreen support:
- cmAbort to cmScreenDisconnected
return true;
}
+ if (command.equals(cmMenu)) {
+ if (!modalWindowActive() && (activeMenu == null)) {
+ if (menus.size() > 0) {
+ menus.get(0).setActive(true);
+ activeMenu = menus.get(0);
+ return true;
+ }
+ }
+ }
+
return false;
}
*/
public static final int HELP = 20;
+ /**
+ * Enter first menu.
+ */
+ public static final int MENU = 21;
+
+ /**
+ * Save file.
+ */
+ public static final int SAVE = 30;
+
/**
* Type of command, one of EXIT, CASCADE, etc.
*/
public static final TCommand cmWindowPrevious = new TCommand(WINDOW_PREVIOUS);
public static final TCommand cmWindowClose = new TCommand(WINDOW_CLOSE);
public static final TCommand cmHelp = new TCommand(HELP);
+ public static final TCommand cmSave = new TCommand(SAVE);
+ public static final TCommand cmMenu = new TCommand(MENU);
}
*/
package jexer;
+import java.io.IOException;
+
import jexer.bits.CellAttributes;
import jexer.event.TKeypressEvent;
import jexer.event.TMouseEvent;
@Override
public void onMouseDown(final TMouseEvent mouse) {
if (mouse.isMouseWheelUp()) {
- if (getCursorY() == getHeight() - 1) {
- if (document.up()) {
- if (topLine > 0) {
- topLine--;
- }
- alignCursor();
- }
- } else {
- if (topLine > 0) {
- topLine--;
- setCursorY(getCursorY() + 1);
- }
+ if (topLine > 0) {
+ topLine--;
+ alignDocument(false);
}
return;
}
if (mouse.isMouseWheelDown()) {
- if (getCursorY() == 0) {
- if (document.down()) {
- if (topLine < document.getLineNumber()) {
- topLine++;
- }
- alignCursor();
- }
- } else {
- if (topLine < document.getLineCount() - getHeight()) {
- topLine++;
- setCursorY(getCursorY() - 1);
- }
+ if (topLine < document.getLineCount() - 1) {
+ topLine++;
+ alignDocument(true);
}
return;
}
super.onMouseDown(mouse);
}
+ /**
+ * Align visible area with document current line.
+ *
+ * @param topLineIsTop if true, make the top visible line the document
+ * current line if it was off-screen. If false, make the bottom visible
+ * line the document current line.
+ */
+ private void alignTopLine(final boolean topLineIsTop) {
+ int line = document.getLineNumber();
+
+ if ((line < topLine) || (line > topLine + getHeight() - 1)) {
+ // Need to move topLine to bring document back into view.
+ if (topLineIsTop) {
+ topLine = line - (getHeight() - 1);
+ } else {
+ topLine = line;
+ }
+ }
+
+ /*
+ System.err.println("line " + line + " topLine " + topLine);
+ */
+
+ // Document is in view, let's set cursorY
+ setCursorY(line - topLine);
+ alignCursor();
+ }
+
+ /**
+ * Align document current line with visible area.
+ *
+ * @param topLineIsTop if true, make the top visible line the document
+ * current line if it was off-screen. If false, make the bottom visible
+ * line the document current line.
+ */
+ private void alignDocument(final boolean topLineIsTop) {
+ int line = document.getLineNumber();
+
+ if ((line < topLine) || (line > topLine + getHeight() - 1)) {
+ // Need to move document to ensure it fits view.
+ if (topLineIsTop) {
+ document.setLineNumber(topLine);
+ } else {
+ document.setLineNumber(topLine + (getHeight() - 1));
+ }
+ }
+
+ /*
+ System.err.println("getLineNumber() " + document.getLineNumber() +
+ " topLine " + topLine);
+ */
+
+ // Document is in view, let's set cursorY
+ setCursorY(document.getLineNumber() - topLine);
+ alignCursor();
+ }
+
/**
* Align visible cursor with document cursor.
*/
alignCursor();
}
} else if (keypress.equals(kbUp)) {
- if (document.up()) {
- if (getCursorY() > 0) {
- setCursorY(getCursorY() - 1);
- } else {
- if (topLine > 0) {
- topLine--;
- }
- }
- alignCursor();
- }
+ document.up();
+ alignTopLine(false);
} else if (keypress.equals(kbDown)) {
- if (document.down()) {
- if (getCursorY() < getHeight() - 1) {
- setCursorY(getCursorY() + 1);
- } else {
- if (topLine < document.getLineCount() - getHeight()) {
- topLine++;
- }
- }
- alignCursor();
- }
+ document.down();
+ alignTopLine(true);
} else if (keypress.equals(kbPgUp)) {
- for (int i = 0; i < getHeight() - 1; i++) {
- if (document.up()) {
- if (getCursorY() > 0) {
- setCursorY(getCursorY() - 1);
- } else {
- if (topLine > 0) {
- topLine--;
- }
- }
- alignCursor();
- } else {
- break;
- }
- }
+ document.up(getHeight() - 1);
+ alignTopLine(false);
} else if (keypress.equals(kbPgDn)) {
- for (int i = 0; i < getHeight() - 1; i++) {
- if (document.down()) {
- if (getCursorY() < getHeight() - 1) {
- setCursorY(getCursorY() + 1);
- } else {
- if (topLine < document.getLineCount() - getHeight()) {
- topLine++;
- }
- }
- alignCursor();
- } else {
- break;
- }
- }
+ document.down(getHeight() - 1);
+ alignTopLine(true);
} else if (keypress.equals(kbHome)) {
if (document.home()) {
leftColumn = 0;
} else if (keypress.equals(kbCtrlEnd)) {
document.setLineNumber(document.getLineCount() - 1);
document.end();
- topLine = document.getLineCount() - getHeight();
- if (topLine < 0) {
- topLine = 0;
- }
- if (document.getLineCount() > getHeight()) {
- setCursorY(getHeight() - 1);
- } else {
- setCursorY(document.getLineCount() - 1);
- }
- alignCursor();
+ alignTopLine(false);
} else if (keypress.equals(kbIns)) {
document.setOverwrite(!document.getOverwrite());
} else if (keypress.equals(kbDel)) {
+ // TODO: join lines
document.del();
+ alignCursor();
} else if (keypress.equals(kbBackspace)) {
document.backspace();
alignCursor();
+ } else if (keypress.equals(kbEnter)) {
+ // TODO: split lines
} else if (!keypress.getKey().isFnKey()
&& !keypress.getKey().isAlt()
&& !keypress.getKey().isCtrl()
) {
// Plain old keystroke, process it
document.addChar(keypress.getKey().getChar());
+ alignCursor();
} else {
// Pass other keys (tab etc.) on to TWidget
super.onKeypress(keypress);
}
}
+ /**
+ * Get the number of lines in the underlying Document.
+ *
+ * @return the number of lines
+ */
+ public int getLineCount() {
+ return document.getLineCount();
+ }
+
+ /**
+ * Get the current editing row number. 1-based.
+ *
+ * @return the editing row number. Row 1 is the first row.
+ */
+ public int getEditingRowNumber() {
+ return document.getLineNumber() + 1;
+ }
+
+ /**
+ * Set the current editing row number. 1-based.
+ *
+ * @param row the new editing row number. Row 1 is the first row.
+ */
+ public void setEditingRowNumber(final int row) {
+ document.setLineNumber(row - 1);
+ }
+
+ /**
+ * Get the current editing column number. 1-based.
+ *
+ * @return the editing column number. Column 1 is the first column.
+ */
+ public int getEditingColumnNumber() {
+ return document.getCursor() + 1;
+ }
+
+ /**
+ * Set the current editing column number. 1-based.
+ *
+ * @param column the new editing column number. Column 1 is the first
+ * column.
+ */
+ public void setEditingColumnNumber(final int column) {
+ document.setCursor(column - 1);
+ }
+
+ /**
+ * Get the maximum possible row number. 1-based.
+ *
+ * @return the maximum row number. Row 1 is the first row.
+ */
+ public int getMaximumRowNumber() {
+ return document.getLineCount() + 1;
+ }
+
+ /**
+ * Get the maximum possible column number. 1-based.
+ *
+ * @return the maximum column number. Column 1 is the first column.
+ */
+ public int getMaximumColumnNumber() {
+ return document.getLineLengthMax() + 1;
+ }
+
+ /**
+ * Get the dirty value.
+ *
+ * @return true if the buffer is dirty
+ */
+ public boolean isDirty() {
+ return document.isDirty();
+ }
+
+ /**
+ * Save contents to file.
+ *
+ * @param filename file to save to
+ * @throws IOException if a java.io operation throws
+ */
+ public void saveToFilename(final String filename) throws IOException {
+ document.saveToFilename(filename);
+ }
+
}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * @author Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Scanner;
+
+import jexer.TApplication;
+import jexer.TEditorWidget;
+import jexer.THScroller;
+import jexer.TScrollableWindow;
+import jexer.TVScroller;
+import jexer.TWidget;
+import jexer.bits.CellAttributes;
+import jexer.bits.GraphicsChars;
+import jexer.event.TCommandEvent;
+import jexer.event.TMouseEvent;
+import jexer.event.TResizeEvent;
+import static jexer.TCommand.*;
+import static jexer.TKeypress.*;
+
+/**
+ * TEditorWindow is a basic text file editor.
+ */
+public class TEditorWindow extends TScrollableWindow {
+
+ /**
+ * Hang onto my TEditor so I can resize it with the window.
+ */
+ private TEditorWidget editField;
+
+ /**
+ * The fully-qualified name of the file being edited.
+ */
+ private String filename = "";
+
+ /**
+ * Setup other fields after the editor is created.
+ */
+ private void setupAfterEditor() {
+ hScroller = new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
+ vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
+ setMinimumWindowWidth(25);
+ setMinimumWindowHeight(10);
+ setTopValue(1);
+ setBottomValue(editField.getMaximumRowNumber());
+ setLeftValue(1);
+ setRightValue(editField.getMaximumColumnNumber());
+
+ statusBar = newStatusBar("Editor");
+ statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
+ statusBar.addShortcutKeypress(kbF2, cmSave, "Save");
+ statusBar.addShortcutKeypress(kbF3, cmOpen, "Open");
+ statusBar.addShortcutKeypress(kbF10, cmMenu, "Menu");
+ }
+
+ /**
+ * Public constructor sets window title.
+ *
+ * @param parent the main application
+ * @param title the window title
+ */
+ public TEditorWindow(final TApplication parent, final String title) {
+
+ super(parent, title, 0, 0, parent.getScreen().getWidth(),
+ parent.getScreen().getHeight() - 2, RESIZABLE);
+
+ editField = addEditor("", 0, 0, getWidth() - 2, getHeight() - 2);
+ setupAfterEditor();
+ }
+
+ /**
+ * Public constructor sets window title and contents.
+ *
+ * @param parent the main application
+ * @param title the window title, usually a filename
+ * @param contents the data for the editing window, usually the file data
+ */
+ public TEditorWindow(final TApplication parent, final String title,
+ final String contents) {
+
+ super(parent, title, 0, 0, parent.getScreen().getWidth(),
+ parent.getScreen().getHeight() - 2, RESIZABLE);
+
+ filename = title;
+ editField = addEditor(contents, 0, 0, getWidth() - 2, getHeight() - 2);
+ setupAfterEditor();
+ }
+
+ /**
+ * Public constructor.
+ *
+ * @param parent the main application
+ */
+ public TEditorWindow(final TApplication parent) {
+ this(parent, "New Text Document");
+ }
+
+ /**
+ * Draw the window.
+ */
+ @Override
+ public void draw() {
+ // Draw as normal.
+ super.draw();
+
+ // Add the row:col on the bottom row
+ CellAttributes borderColor = getBorder();
+ String location = String.format(" %d:%d ",
+ editField.getEditingRowNumber(),
+ editField.getEditingColumnNumber());
+ int colon = location.indexOf(':');
+ putStringXY(10 - colon, getHeight() - 1, location, borderColor);
+
+ if (editField.isDirty()) {
+ putCharXY(2, getHeight() - 1, GraphicsChars.OCTOSTAR, borderColor);
+ }
+ }
+
+ /**
+ * Check if a mouse press/release/motion event coordinate is over the
+ * editor.
+ *
+ * @param mouse a mouse-based event
+ * @return whether or not the mouse is on the emulator
+ */
+ private final boolean mouseOnEditor(final TMouseEvent mouse) {
+ if ((mouse.getAbsoluteX() >= getAbsoluteX() + 1)
+ && (mouse.getAbsoluteX() < getAbsoluteX() + getWidth() - 1)
+ && (mouse.getAbsoluteY() >= getAbsoluteY() + 1)
+ && (mouse.getAbsoluteY() < getAbsoluteY() + getHeight() - 1)
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle mouse press events.
+ *
+ * @param mouse mouse button press event
+ */
+ @Override
+ public void onMouseDown(final TMouseEvent mouse) {
+ if (mouseOnEditor(mouse)) {
+ editField.onMouseDown(mouse);
+ setBottomValue(editField.getMaximumRowNumber());
+ setVerticalValue(editField.getEditingRowNumber());
+ setRightValue(editField.getMaximumColumnNumber());
+ setHorizontalValue(editField.getEditingColumnNumber());
+ } else {
+ // Let the scrollbars get the event
+ super.onMouseDown(mouse);
+
+ if (mouse.isMouseWheelUp() || mouse.isMouseWheelDown()) {
+ editField.setEditingRowNumber(getVerticalValue());
+ }
+ // TODO: horizontal scrolling
+ }
+ }
+
+ /**
+ * Handle window/screen resize events.
+ *
+ * @param event resize event
+ */
+ @Override
+ public void onResize(final TResizeEvent event) {
+ if (event.getType() == TResizeEvent.Type.WIDGET) {
+ // Resize the text field
+ TResizeEvent editSize = new TResizeEvent(TResizeEvent.Type.WIDGET,
+ event.getWidth() - 2, event.getHeight() - 2);
+ editField.onResize(editSize);
+
+ // Have TScrollableWindow handle the scrollbars
+ super.onResize(event);
+ return;
+ }
+
+ // Pass to children instead
+ for (TWidget widget: getChildren()) {
+ widget.onResize(event);
+ }
+ }
+
+ /**
+ * Method that subclasses can override to handle posted command events.
+ *
+ * @param command command event
+ */
+ @Override
+ public void onCommand(final TCommandEvent command) {
+ if (command.equals(cmOpen)) {
+ try {
+ String filename = fileOpenBox(".");
+ if (filename != null) {
+ try {
+ File file = new File(filename);
+ StringBuilder fileContents = new StringBuilder();
+ Scanner scanner = new Scanner(file);
+ String EOL = System.getProperty("line.separator");
+
+ try {
+ while (scanner.hasNextLine()) {
+ fileContents.append(scanner.nextLine() + EOL);
+ }
+ new TEditorWindow(getApplication(), filename,
+ fileContents.toString());
+ } finally {
+ scanner.close();
+ }
+ } catch (IOException e) {
+ // TODO: make this a message box
+ e.printStackTrace();
+ }
+ }
+ } catch (IOException e) {
+ // TODO: make this a message box
+ e.printStackTrace();
+ }
+ return;
+ }
+
+ if (command.equals(cmSave)) {
+ if (filename.length() > 0) {
+ try {
+ editField.saveToFilename(filename);
+ } catch (IOException e) {
+ // TODO: make this a message box
+ e.printStackTrace();
+ }
+ }
+ return;
+ }
+
+ // Didn't handle it, let children get it instead
+ super.onCommand(command);
+ }
+
+}
protected void placeScrollbars() {
if (hScroller != null) {
hScroller.setY(getHeight() - 2);
- hScroller.setWidth(getWidth() - 3);
- hScroller.setBigChange(getWidth() - 3);
+ hScroller.setWidth(getWidth() - hScroller.getX() - 3);
+ hScroller.setBigChange(getWidth() - hScroller.getX() - 3);
}
if (vScroller != null) {
vScroller.setX(getWidth() - 2);
* @param maximumWindowWidth new maximum width
*/
public final void setMaximumWindowWidth(final int maximumWindowWidth) {
+ if ((maximumWindowWidth != -1)
+ && (maximumWindowWidth < minimumWindowWidth + 1)
+ ) {
+ throw new IllegalArgumentException("Maximum window width cannot " +
+ "be smaller than minimum window width + 1");
+ }
this.maximumWindowWidth = maximumWindowWidth;
}
+ /**
+ * Set the minimum width for this window.
+ *
+ * @param minimumWindowWidth new minimum width
+ */
+ public final void setMinimumWindowWidth(final int minimumWindowWidth) {
+ if ((maximumWindowWidth != -1)
+ && (minimumWindowWidth > maximumWindowWidth - 1)
+ ) {
+ throw new IllegalArgumentException("Minimum window width cannot " +
+ "be larger than maximum window width - 1");
+ }
+ this.minimumWindowWidth = minimumWindowWidth;
+ }
+
+ /**
+ * Set the maximum height for this window.
+ *
+ * @param maximumWindowHeight new maximum height
+ */
+ public final void setMaximumWindowHeight(final int maximumWindowHeight) {
+ if ((maximumWindowHeight != -1)
+ && (maximumWindowHeight < minimumWindowHeight + 1)
+ ) {
+ throw new IllegalArgumentException("Maximum window height cannot " +
+ "be smaller than minimum window height + 1");
+ }
+ this.maximumWindowHeight = maximumWindowHeight;
+ }
+
+ /**
+ * Set the minimum height for this window.
+ *
+ * @param minimumWindowHeight new minimum height
+ */
+ public final void setMinimumWindowHeight(final int minimumWindowHeight) {
+ if ((maximumWindowHeight != -1)
+ && (minimumWindowHeight > maximumWindowHeight - 1)
+ ) {
+ throw new IllegalArgumentException("Minimum window height cannot " +
+ "be larger than maximum window height - 1");
+ }
+ this.minimumWindowHeight = minimumWindowHeight;
+ }
+
/**
* Recenter the window on-screen.
*/
public static final char WINDOW_LEFT_BOTTOM_DOUBLE = CP437[0xC8];
public static final char WINDOW_RIGHT_BOTTOM_DOUBLE = CP437[0xBC];
public static final char VERTICAL_BAR = CP437[0xB3];
+ public static final char OCTOSTAR = CP437[0x0F];
}
item = subMenu.addItem(2002, "&Normal (sub)");
if (getScreen() instanceof SwingTerminal) {
- TMenu swingMenu = addMenu("&Swing");
+ TMenu swingMenu = addMenu("Swin&g");
item = swingMenu.addItem(3000, "&Bigger +2");
item = swingMenu.addItem(3001, "&Smaller -2");
}
import static jexer.TKeypress.*;
/**
- * This window demonstates the TText, THScroller, and TVScroller widgets.
+ * This window demonstates the TEditor widget.
*/
public class DemoEditorWindow extends TWindow {
super(parent, title, 0, 0, 44, 22, RESIZABLE);
editField = addEditor(text, 0, 0, 42, 20);
- statusBar = newStatusBar("Editable text window");
+ statusBar = newStatusBar("Editable text demo window");
statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
statusBar.addShortcutKeypress(kbF2, cmShell, "Shell");
- statusBar.addShortcutKeypress(kbF3, cmOpen, "Open");
statusBar.addShortcutKeypress(kbF10, cmExit, "Exit");
}
private DemoMainWindow(final TApplication parent, final int flags) {
// Construct a demo window. X and Y don't matter because it will be
// centered on screen.
- super(parent, "Demo Window", 0, 0, 60, 23, flags);
+ super(parent, "Demo Window", 0, 0, 64, 23, flags);
int row = 1;
row += 2;
addLabel("Editor window", 1, row);
- addButton("Edito&r", 35, row,
+ addButton("&1 Widget", 35, row,
new TAction() {
public void DO() {
new DemoEditorWindow(getApplication());
}
}
);
+ addButton("&2 Window", 48, row,
+ new TAction() {
+ public void DO() {
+ new TEditorWindow(getApplication());
+ }
+ }
+ );
row += 2;
addLabel("Text areas", 1, row);
*/
package jexer.teditor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
*/
private boolean overwrite = false;
+ /**
+ * If true, the document has been edited.
+ */
+ private boolean dirty = false;
+
/**
* The default color for the TEditor class.
*/
return overwrite;
}
+ /**
+ * Get the dirty value.
+ *
+ * @return true if the buffer is dirty
+ */
+ public boolean isDirty() {
+ return dirty;
+ }
+
+ /**
+ * Save contents to file.
+ *
+ * @param filename file to save to
+ * @throws IOException if a java.io operation throws
+ */
+ public void saveToFilename(final String filename) throws IOException {
+ OutputStreamWriter output = null;
+ try {
+ output = new OutputStreamWriter(new FileOutputStream(filename),
+ "UTF-8");
+
+ for (Line line: lines) {
+ output.write(line.getRawString());
+ output.write("\n");
+ }
+
+ dirty = false;
+ }
+ finally {
+ if (output != null) {
+ output.close();
+ }
+ }
+ }
+
/**
* Set the overwrite flag.
*
return lines.get(lineNumber).getCursor();
}
+ /**
+ * Set the current cursor position of the editing line. 0-based.
+ *
+ * @param cursor the new cursor position
+ */
+ public void setCursor(final int cursor) {
+ lines.get(lineNumber).setCursor(cursor);
+ }
+
/**
* Construct a new Document from an existing text string.
*
* Delete the character under the cursor.
*/
public void del() {
+ dirty = true;
lines.get(lineNumber).del();
}
* Delete the character immediately preceeding the cursor.
*/
public void backspace() {
+ dirty = true;
lines.get(lineNumber).backspace();
}
* @param ch the character to replace or insert
*/
public void addChar(final char ch) {
+ dirty = true;
if (overwrite) {
lines.get(lineNumber).replaceChar(ch);
} else {
private int cursor = 0;
/**
- * The current word that the cursor position is in.
+ * The raw text of this line, what is passed to Word to determine
+ * highlighting behavior.
*/
- private Word currentWord;
+ private StringBuilder rawText;
/**
- * We use getDisplayLength() a lot, so cache the value.
+ * Get a (shallow) copy of the words in this line.
+ *
+ * @return a copy of the word list
*/
- private int displayLength = -1;
+ public List<Word> getWords() {
+ return new ArrayList<Word>(words);
+ }
/**
* Get the current cursor position.
getDisplayLength() + ", requested position " + cursor);
}
this.cursor = cursor;
- // TODO: set word
}
/**
- * Get a (shallow) copy of the list of words.
+ * Get the on-screen display length.
*
- * @return the list of words
+ * @return the number of cells needed to display this line
*/
- public List<Word> getWords() {
- return new ArrayList<Word>(words);
+ public int getDisplayLength() {
+ int n = rawText.length();
+
+ // For now just return the raw text length.
+ if (n > 0) {
+ // If we have any visible characters, add one to the display so
+ // that the cursor is immediately after the data.
+ return n + 1;
+ }
+ return n;
}
/**
- * Get the on-screen display length.
+ * Get the raw string that matches this line.
*
- * @return the number of cells needed to display this line
+ * @return the string
*/
- public int getDisplayLength() {
- if (displayLength != -1) {
- return displayLength;
- }
- int n = 0;
- for (Word word: words) {
- n += word.getDisplayLength();
- }
- displayLength = n;
+ public String getRawString() {
+ return rawText.toString();
+ }
- // If we have any visible characters, add one to the display so that
- // the cursor is immediately after the data.
- if (displayLength > 0) {
- displayLength++;
+ /**
+ * Scan rawText and make words out of it.
+ */
+ private void scanLine() {
+ words.clear();
+ Word word = new Word(this.defaultColor, this.highlighter);
+ words.add(word);
+ for (int i = 0; i < rawText.length(); i++) {
+ char ch = rawText.charAt(i);
+ Word newWord = word.addChar(ch);
+ if (newWord != word) {
+ words.add(newWord);
+ word = newWord;
+ }
+ }
+ for (Word w: words) {
+ w.applyHighlight();
}
- return displayLength;
}
/**
this.defaultColor = defaultColor;
this.highlighter = highlighter;
+ this.rawText = new StringBuilder(str);
- currentWord = new Word(this.defaultColor, this.highlighter);
- words.add(currentWord);
- for (int i = 0; i < str.length(); i++) {
- char ch = str.charAt(i);
- Word newWord = currentWord.addChar(ch);
- if (newWord != currentWord) {
- words.add(newWord);
- currentWord = newWord;
- }
- }
- for (Word word: words) {
- word.applyHighlight();
- }
+ scanLine();
}
/**
if (cursor == 0) {
return false;
}
- // TODO: switch word
cursor--;
return true;
}
if (cursor == getDisplayLength() - 1) {
return false;
}
- // TODO: switch word
cursor++;
return true;
}
public boolean home() {
if (cursor > 0) {
cursor = 0;
- currentWord = words.get(0);
return true;
}
return false;
if (cursor < 0) {
cursor = 0;
}
- currentWord = words.get(words.size() - 1);
return true;
}
return false;
* Delete the character under the cursor.
*/
public void del() {
- // TODO
+ assert (words.size() > 0);
+
+ if (cursor < getDisplayLength()) {
+ rawText.deleteCharAt(cursor);
+ }
+
+ // Re-scan the line to determine the new word boundaries.
+ scanLine();
}
/**
* Delete the character immediately preceeding the cursor.
*/
public void backspace() {
- // TODO
+ if (left()) {
+ del();
+ }
}
/**
* @param ch the character to insert
*/
public void addChar(final char ch) {
- // TODO
+ if (cursor < getDisplayLength() - 1) {
+ rawText.insert(cursor, ch);
+ } else {
+ rawText.append(ch);
+ }
+ scanLine();
+ cursor++;
}
/**
* @param ch the character to replace
*/
public void replaceChar(final char ch) {
- // TODO
+ if (cursor < getDisplayLength() - 1) {
+ rawText.setCharAt(cursor, ch);
+ } else {
+ rawText.append(ch);
+ }
+ scanLine();
+ cursor++;
}
}
* Add a character to this word. If this is a whitespace character
* adding to a non-whitespace word, create a new word and return that;
* similarly if this a non-whitespace character adding to a whitespace
- * word, create a new word and return that.
+ * word, create a new word and return that. Note package private access:
+ * this is only called by Line to figure out highlighting boundaries.
*
* @param ch the new character to add
* @return either this word (if it was added), or a new word that