The MIT License (MIT)
-Copyright (c) 2016 Kevin Lamonte
+Copyright (c) 2013-2017 Kevin Lamonte
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
obviously expected behavior did not happen or when a specification was
ambiguous. This section describes such issues.
+ - The JVM needs some warmup time to exhibit the true performance
+ behavior. Drag a window around for a bit to see this: the initial
+ performance is slow, then the JIT compiler kicks in and Jexer can
+ be visually competitive with C/C++ curses applications.
+
- See jexer.tterminal.ECMA48 for more specifics of terminal
emulation limitations.
0.0.5
-- TApplication
- - getAllWindows()
- - Expose menu management functions (addMenu, getMenu, getAllMenus,
- removeMenu, ...)
-
- TEditor
-
- - Swich Line from String to ArrayList<Cell>
- - StringUtils.justify functions for ArrayList<Cell>
+ - TEditorWidget:
+ - Mouse wheel is buggy as hell
+ - Actual editing
+ - Cut and Paste
- TEditorWindow extends TScrollableWindow
- - TEditor widget with keystroke functions:
- - cursorRight/Left/...
- - insertChar
- - deleteForwardChar
- - deleteBackwardChar
- - deleteBackwardWord
- - wordCount
- - ...
-
-- Eliminate all Eclipse warnings
+ - TTextArea extends TScrollableWidget
0.0.6
+- TEditor
+ - True tokenization and syntax highlighting: Java, C, Clojure
+
- Finish up multiscreen support:
- cmAbort to cmScreenDisconnected
- cmScreenConnected
Eliminate DEBUG, System.err prints
-Version in:
-
Update written by date to current year:
All code headers
VERSION
Upload to SF
+Upload to sonatype
Brainstorm Wishlist
Jexer Work Log
==============
+August 12, 2017
+
+TEditor is stubbed in about 50% complete now. I have a Highlighter
+class that provides different colors based on Word text values, but it
+is a lot too simple to do true syntax highlighting. I am noodling on
+the right design that would let TEditor be both a programmer's editor
+(so Highlighter needs to have state and do a lexical scan) and a word
+processor (where Word needs to tokenize on whitespace). I estimate
+probably a good 2-4 weeks left to get the editor behavior where I want
+it, and then after that will be the 0.0.5 release.
+
+Finding more minor paper cuts and fixing them: the mouse cursor being
+ahead of a window drag event, SwingTerminal resetting blink on new
+input, prevent TWindow from resizing down into the status bar.
+
August 8, 2017
Multiscreen is looking really cool! Demo6 now brings up three
}
/**
- * Get the list of windows.
+ * Get a (shallow) copy of the window list.
*
* @return a copy of the list of windows for this application
*/
return;
}
- // Peek at the mouse position
- if (event instanceof TMouseEvent) {
- TMouseEvent mouse = (TMouseEvent) event;
- synchronized (getScreen()) {
- if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
- oldMouseX = mouseX;
- oldMouseY = mouseY;
- mouseX = mouse.getX();
- mouseY = mouse.getY();
- }
- }
- }
-
// Put into the main queue
drainEventQueue.add(event);
}
// Peek at the mouse position
if (event instanceof TMouseEvent) {
+ TMouseEvent mouse = (TMouseEvent) event;
+ if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
+ oldMouseX = mouseX;
+ oldMouseY = mouseY;
+ mouseX = mouse.getX();
+ mouseY = mouse.getY();
+ }
+
// See if we need to switch focus to another window or the menu
checkSwitchFocus((TMouseEvent) event);
}
* @see #primaryHandleEvent(TInputEvent event)
*/
private void secondaryHandleEvent(final TInputEvent event) {
+ // Peek at the mouse position
+ if (event instanceof TMouseEvent) {
+ TMouseEvent mouse = (TMouseEvent) event;
+ if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
+ oldMouseX = mouseX;
+ oldMouseY = mouseY;
+ mouseX = mouse.getX();
+ mouseY = mouse.getY();
+ }
+ }
+
secondaryEventReceiver.handleEvent(event);
}
}
}
+ /**
+ * Get a (shallow) copy of the menu list.
+ *
+ * @return a copy of the menu list
+ */
+ public final List<TMenu> getAllMenus() {
+ return new LinkedList<TMenu>(menus);
+ }
+
+ /**
+ * Add a top-level menu to the list.
+ *
+ * @param menu the menu to add
+ * @throws IllegalArgumentException if the menu is already used in
+ * another TApplication
+ */
+ public final void addMenu(final TMenu menu) {
+ if ((menu.getApplication() != null)
+ && (menu.getApplication() != this)
+ ) {
+ throw new IllegalArgumentException("Menu " + menu + " is already " +
+ "part of application " + menu.getApplication());
+ }
+ closeMenu();
+ menus.add(menu);
+ recomputeMenuX();
+ }
+
+ /**
+ * Remove a top-level menu from the list.
+ *
+ * @param menu the menu to remove
+ * @throws IllegalArgumentException if the menu is already used in
+ * another TApplication
+ */
+ public final void removeMenu(final TMenu menu) {
+ if ((menu.getApplication() != null)
+ && (menu.getApplication() != this)
+ ) {
+ throw new IllegalArgumentException("Menu " + menu + " is already " +
+ "part of application " + menu.getApplication());
+ }
+ closeMenu();
+ menus.remove(menu);
+ recomputeMenuX();
+ }
+
/**
* Turn off a sub-menu.
*/
import jexer.bits.CellAttributes;
import jexer.event.TKeypressEvent;
import jexer.event.TMouseEvent;
+import jexer.event.TResizeEvent;
import jexer.teditor.Document;
import jexer.teditor.Line;
import jexer.teditor.Word;
*/
private Document document;
+ /**
+ * The default color for the TEditor class.
+ */
+ private CellAttributes defaultColor = null;
+
+ /**
+ * The topmost line number in the visible area. 0-based.
+ */
+ private int topLine = 0;
+
+ /**
+ * The leftmost column number in the visible area. 0-based.
+ */
+ private int leftColumn = 0;
+
/**
* Public constructor.
*
super(parent, x, y, width, height);
setCursorVisible(true);
- document = new Document(text);
+
+ defaultColor = getTheme().getColor("teditor");
+ document = new Document(text, defaultColor);
}
/**
*/
@Override
public void draw() {
- // Setup my color
- CellAttributes color = getTheme().getColor("teditor");
-
- int lineNumber = document.getLineNumber();
for (int i = 0; i < getHeight(); i++) {
// Background line
- getScreen().hLineXY(0, i, getWidth(), ' ', color);
+ getScreen().hLineXY(0, i, getWidth(), ' ', defaultColor);
// Now draw document's line
- if (lineNumber + i < document.getLineCount()) {
- Line line = document.getLine(lineNumber + i);
+ if (topLine + i < document.getLineCount()) {
+ Line line = document.getLine(topLine + i);
int x = 0;
for (Word word: line.getWords()) {
- getScreen().putStringXY(x, i, word.getText(),
+ // For now, we are cheating: draw outside the left region
+ // if needed and let screen do the clipping.
+ getScreen().putStringXY(x - leftColumn, i, word.getText(),
word.getColor());
x += word.getDisplayLength();
- if (x > getWidth()) {
+ if (x - leftColumn > getWidth()) {
break;
}
}
@Override
public void onMouseDown(final TMouseEvent mouse) {
if (mouse.isMouseWheelUp()) {
- document.up();
+ if (getCursorY() == getHeight() - 1) {
+ if (document.up()) {
+ if (topLine > 0) {
+ topLine--;
+ }
+ alignCursor();
+ }
+ } else {
+ if (topLine > 0) {
+ topLine--;
+ setCursorY(getCursorY() + 1);
+ }
+ }
return;
}
if (mouse.isMouseWheelDown()) {
- document.down();
+ if (getCursorY() == 0) {
+ if (document.down()) {
+ if (topLine < document.getLineNumber()) {
+ topLine++;
+ }
+ alignCursor();
+ }
+ } else {
+ if (topLine < document.getLineCount() - getHeight()) {
+ topLine++;
+ setCursorY(getCursorY() - 1);
+ }
+ }
return;
}
- // TODO: click sets row and column
+ if (mouse.isMouse1()) {
+ // Set the row and column
+ int newLine = topLine + mouse.getY();
+ int newX = leftColumn + mouse.getX();
+ if (newLine > document.getLineCount()) {
+ // Go to the end
+ document.setLineNumber(document.getLineCount() - 1);
+ document.end();
+ if (document.getLineCount() > getHeight()) {
+ setCursorY(getHeight() - 1);
+ } else {
+ setCursorY(document.getLineCount() - 1);
+ }
+ alignCursor();
+ return;
+ }
+
+ document.setLineNumber(newLine);
+ setCursorY(mouse.getY());
+ if (newX > document.getCurrentLine().getDisplayLength()) {
+ document.end();
+ alignCursor();
+ } else {
+ setCursorX(mouse.getX());
+ }
+ return;
+ }
// Pass to children
super.onMouseDown(mouse);
}
+ /**
+ * Align visible cursor with document cursor.
+ */
+ private void alignCursor() {
+ int width = getWidth();
+
+ int desiredX = document.getCursor() - leftColumn;
+ if (desiredX < 0) {
+ // We need to push the screen to the left.
+ leftColumn = document.getCursor();
+ } else if (desiredX > width - 1) {
+ // We need to push the screen to the right.
+ leftColumn = document.getCursor() - (width - 1);
+ }
+
+ /*
+ System.err.println("document cursor " + document.getCursor() +
+ " leftColumn " + leftColumn);
+ */
+
+ setCursorX(document.getCursor() - leftColumn);
+ }
+
/**
* Handle keystrokes.
*
@Override
public void onKeypress(final TKeypressEvent keypress) {
if (keypress.equals(kbLeft)) {
- document.left();
+ if (document.left()) {
+ alignCursor();
+ }
} else if (keypress.equals(kbRight)) {
- document.right();
+ if (document.right()) {
+ alignCursor();
+ }
} else if (keypress.equals(kbUp)) {
- document.up();
+ if (document.up()) {
+ if (getCursorY() > 0) {
+ setCursorY(getCursorY() - 1);
+ } else {
+ if (topLine > 0) {
+ topLine--;
+ }
+ }
+ alignCursor();
+ }
} else if (keypress.equals(kbDown)) {
- document.down();
+ if (document.down()) {
+ if (getCursorY() < getHeight() - 1) {
+ setCursorY(getCursorY() + 1);
+ } else {
+ if (topLine < document.getLineCount() - getHeight()) {
+ topLine++;
+ }
+ }
+ alignCursor();
+ }
} else if (keypress.equals(kbPgUp)) {
- document.up(getHeight() - 1);
+ 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;
+ }
+ }
} else if (keypress.equals(kbPgDn)) {
- document.down(getHeight() - 1);
+ 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;
+ }
+ }
} else if (keypress.equals(kbHome)) {
- document.home();
+ if (document.home()) {
+ leftColumn = 0;
+ if (leftColumn < 0) {
+ leftColumn = 0;
+ }
+ setCursorX(0);
+ }
} else if (keypress.equals(kbEnd)) {
- document.end();
+ if (document.end()) {
+ alignCursor();
+ }
} else if (keypress.equals(kbCtrlHome)) {
document.setLineNumber(0);
document.home();
+ topLine = 0;
+ leftColumn = 0;
+ setCursorX(0);
+ setCursorY(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();
} else if (keypress.equals(kbIns)) {
document.setOverwrite(!document.getOverwrite());
} else if (keypress.equals(kbDel)) {
document.del();
} else if (keypress.equals(kbBackspace)) {
document.backspace();
+ alignCursor();
} else if (!keypress.getKey().isFnKey()
&& !keypress.getKey().isAlt()
&& !keypress.getKey().isCtrl()
}
}
+ /**
+ * Method that subclasses can override to handle window/screen resize
+ * events.
+ *
+ * @param resize resize event
+ */
+ @Override
+ public void onResize(final TResizeEvent resize) {
+ // Change my width/height, and pull the cursor in as needed.
+ if (resize.getType() == TResizeEvent.Type.WIDGET) {
+ setWidth(resize.getWidth());
+ setHeight(resize.getHeight());
+ // See if the cursor is now outside the window, and if so move
+ // things.
+ if (getCursorX() >= getWidth()) {
+ leftColumn += getCursorX() - (getWidth() - 1);
+ setCursorX(getWidth() - 1);
+ }
+ if (getCursorY() >= getHeight()) {
+ topLine += getCursorY() - (getHeight() - 1);
+ setCursorY(getHeight() - 1);
+ }
+ } else {
+ // Let superclass handle it
+ super.onResize(resize);
+ }
+ }
+
}
}
}
+ /**
+ * Reset the blink timer.
+ */
+ private void resetBlinkTimer() {
+ // See if it is time to flip the blink time.
+ long nowTime = (new Date()).getTime();
+ lastBlinkTime = nowTime;
+ cursorBlinkVisible = true;
+ }
+
/**
* Paint redraws the whole screen.
*
// Save it and we are done.
synchronized (eventQueue) {
eventQueue.add(new TKeypressEvent(keypress));
+ resetBlinkTimer();
}
if (listener != null) {
synchronized (listener) {
// Drop a cmAbort and walk away
synchronized (eventQueue) {
eventQueue.add(new TCommandEvent(cmAbort));
+ resetBlinkTimer();
}
if (listener != null) {
synchronized (listener) {
TResizeEvent windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
eventQueue.add(windowResize);
+ resetBlinkTimer();
}
if (listener != null) {
synchronized (listener) {
synchronized (eventQueue) {
eventQueue.add(mouseEvent);
+ resetBlinkTimer();
}
if (listener != null) {
synchronized (listener) {
synchronized (eventQueue) {
eventQueue.add(mouseEvent);
+ resetBlinkTimer();
}
if (listener != null) {
synchronized (listener) {
synchronized (eventQueue) {
eventQueue.add(mouseEvent);
+ resetBlinkTimer();
}
if (listener != null) {
synchronized (listener) {
synchronized (eventQueue) {
eventQueue.add(mouseEvent);
+ resetBlinkTimer();
}
if (listener != null) {
synchronized (listener) {
synchronized (eventQueue) {
eventQueue.add(mouseEvent);
+ resetBlinkTimer();
}
if (listener != null) {
synchronized (listener) {
// TText text
color = new CellAttributes();
color.setForeColor(Color.WHITE);
- color.setBackColor(Color.BLACK);
+ color.setBackColor(Color.BLUE);
color.setBold(false);
colors.put("ttext", color);
// TEditor
color = new CellAttributes();
color.setForeColor(Color.WHITE);
- color.setBackColor(Color.BLACK);
+ color.setBackColor(Color.BLUE);
color.setBold(false);
colors.put("teditor", color);
"on many more platforms.\n" +
"\n" +
"This library is licensed MIT. See the file LICENSE for the full license\n" +
-"for the details.\n");
+"for the details.\n" +
+"\n" +
+"package jexer.demos;\n" +
+"\n" +
+"import jexer.*;\n" +
+"import jexer.event.*;\n" +
+"import static jexer.TCommand.*;\n" +
+"import static jexer.TKeypress.*;\n" +
+"\n" +
+"/**\n" +
+" * This window demonstates the TText, THScroller, and TVScroller widgets.\n" +
+" */\n" +
+"public class DemoEditorWindow extends TWindow {\n" +
+"\n" +
+"1 2 3 123\n" +
+"\n"
+ );
}
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, 22, flags);
+ super(parent, "Demo Window", 0, 0, 60, 23, flags);
int row = 1;
import java.util.ArrayList;
import java.util.List;
+import jexer.bits.CellAttributes;
+
/**
* A Document represents a text file, as a collection of lines.
*/
*/
private boolean overwrite = false;
+ /**
+ * The default color for the TEditor class.
+ */
+ private CellAttributes defaultColor = null;
+
+ /**
+ * The text highlighter to use.
+ */
+ private Highlighter highlighter = new Highlighter();
+
/**
* Get the overwrite flag.
*
return lineNumber;
}
+ /**
+ * Get the current editing line.
+ *
+ * @return the line
+ */
+ public Line getCurrentLine() {
+ return lines.get(lineNumber);
+ }
+
/**
* Get a specific line by number.
*
*/
public void setLineNumber(final int n) {
if ((n < 0) || (n > lines.size())) {
- throw new IndexOutOfBoundsException("Line size is " + lines.size() +
- ", requested index " + n);
+ throw new IndexOutOfBoundsException("Lines array size is " +
+ lines.size() + ", requested index " + n);
}
lineNumber = n;
}
+ /**
+ * Get the current cursor position of the editing line.
+ *
+ * @return the cursor position
+ */
+ public int getCursor() {
+ return lines.get(lineNumber).getCursor();
+ }
+
+ /**
+ * Construct a new Document from an existing text string.
+ *
+ * @param str the text string
+ * @param defaultColor the color for unhighlighted text
+ */
+ public Document(final String str, final CellAttributes defaultColor) {
+ this.defaultColor = defaultColor;
+
+ // TODO: set different colors based on file extension
+ highlighter.setJavaColors();
+
+ String [] rawLines = str.split("\n");
+ for (int i = 0; i < rawLines.length; i++) {
+ lines.add(new Line(rawLines[i], this.defaultColor, highlighter));
+ }
+ }
+
/**
* Increment the line number by one. If at the last line, do nothing.
+ *
+ * @return true if the editing line changed
*/
- public void down() {
+ public boolean down() {
if (lineNumber < lines.size() - 1) {
+ int x = lines.get(lineNumber).getCursor();
lineNumber++;
+ if (x > lines.get(lineNumber).getDisplayLength()) {
+ lines.get(lineNumber).end();
+ } else {
+ lines.get(lineNumber).setCursor(x);
+ }
+ return true;
}
+ return false;
}
/**
* increment only to the last line.
*
* @param n the number of lines to increment by
+ * @return true if the editing line changed
*/
- public void down(final int n) {
- lineNumber += n;
- if (lineNumber > lines.size() - 1) {
- lineNumber = lines.size() - 1;
+ public boolean down(final int n) {
+ if (lineNumber < lines.size() - 1) {
+ int x = lines.get(lineNumber).getCursor();
+ lineNumber += n;
+ if (lineNumber > lines.size() - 1) {
+ lineNumber = lines.size() - 1;
+ }
+ if (x > lines.get(lineNumber).getDisplayLength()) {
+ lines.get(lineNumber).end();
+ } else {
+ lines.get(lineNumber).setCursor(x);
+ }
+ return true;
}
+ return false;
}
/**
* Decrement the line number by one. If at the first line, do nothing.
+ *
+ * @return true if the editing line changed
*/
- public void up() {
+ public boolean up() {
if (lineNumber > 0) {
+ int x = lines.get(lineNumber).getCursor();
lineNumber--;
+ if (x > lines.get(lineNumber).getDisplayLength()) {
+ lines.get(lineNumber).end();
+ } else {
+ lines.get(lineNumber).setCursor(x);
+ }
+ return true;
}
+ return false;
}
/**
* decrement only to the first line.
*
* @param n the number of lines to decrement by
+ * @return true if the editing line changed
*/
- public void up(final int n) {
- lineNumber -= n;
- if (lineNumber < 0) {
- lineNumber = 0;
+ public boolean up(final int n) {
+ if (lineNumber > 0) {
+ int x = lines.get(lineNumber).getCursor();
+ lineNumber -= n;
+ if (lineNumber < 0) {
+ lineNumber = 0;
+ }
+ if (x > lines.get(lineNumber).getDisplayLength()) {
+ lines.get(lineNumber).end();
+ } else {
+ lines.get(lineNumber).setCursor(x);
+ }
+ return true;
}
+ return false;
}
/**
* Decrement the cursor by one. If at the first column, do nothing.
+ *
+ * @return true if the cursor position changed
*/
- public void left() {
- lines.get(lineNumber).left();
+ public boolean left() {
+ return lines.get(lineNumber).left();
}
/**
* Increment the cursor by one. If at the last column, do nothing.
+ *
+ * @return true if the cursor position changed
*/
- public void right() {
- lines.get(lineNumber).right();
+ public boolean right() {
+ return lines.get(lineNumber).right();
}
/**
* Go to the first column of this line.
+ *
+ * @return true if the cursor position changed
*/
- public void home() {
- lines.get(lineNumber).home();
+ public boolean home() {
+ return lines.get(lineNumber).home();
}
/**
* Go to the last column of this line.
+ *
+ * @return true if the cursor position changed
*/
- public void end() {
- lines.get(lineNumber).end();
+ public boolean end() {
+ return lines.get(lineNumber).end();
}
/**
* @param ch the character to replace or insert
*/
public void addChar(final char ch) {
- lines.get(lineNumber).addChar(ch);
+ if (overwrite) {
+ lines.get(lineNumber).replaceChar(ch);
+ } else {
+ lines.get(lineNumber).addChar(ch);
+ }
}
/**
return n;
}
- /**
- * Construct a new Document from an existing text string.
- *
- * @param str the text string
- */
- public Document(final String str) {
- String [] rawLines = str.split("\n");
- for (int i = 0; i < rawLines.length; i++) {
- lines.add(new Line(rawLines[i]));
- }
- }
-
}
--- /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.teditor;
+
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import jexer.bits.CellAttributes;
+import jexer.bits.Color;
+
+/**
+ * Highlighter provides color choices for certain text strings.
+ */
+public class Highlighter {
+
+ /**
+ * The highlighter colors.
+ */
+ private SortedMap<String, CellAttributes> colors;
+
+ /**
+ * Public constructor sets the theme to the default.
+ */
+ public Highlighter() {
+ colors = new TreeMap<String, CellAttributes>();
+ }
+
+ /**
+ * See if this is a character that should split a word.
+ *
+ * @param ch the character
+ * @return true if the word should be split
+ */
+ public boolean shouldSplit(final char ch) {
+ // For now, split on punctuation
+ String punctuation = "'\"\\<>{}[]!@#$%^&*();:.,-+/*?";
+ if (punctuation.indexOf(ch) != -1) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the CellAttributes for a named theme color.
+ *
+ * @param name theme color name, e.g. "twindow.border"
+ * @return color associated with name, e.g. bold yellow on blue
+ */
+ public CellAttributes getColor(final String name) {
+ CellAttributes attr = (CellAttributes) colors.get(name);
+ return attr;
+ }
+
+ /**
+ * Sets to defaults that resemble the Borland IDE colors.
+ */
+ public void setJavaColors() {
+ CellAttributes color;
+
+ String [] keywords = {
+ "boolean", "byte", "short", "int", "long", "char", "float",
+ "double", "void", "new",
+ "static", "final", "volatile", "synchronized", "abstract",
+ "public", "private", "protected",
+ "class", "interface", "extends", "implements",
+ "if", "else", "do", "while", "for", "break", "continue",
+ "switch", "case", "default",
+ };
+ color = new CellAttributes();
+ color.setForeColor(Color.WHITE);
+ color.setBackColor(Color.BLUE);
+ color.setBold(true);
+ for (String str: keywords) {
+ colors.put(str, color);
+ }
+
+ String [] operators = {
+ "[", "]", "(", ")", "{", "}",
+ "*", "-", "+", "/", "=", "%",
+ "^", "&", "!", "<<", ">>", "<<<", ">>>",
+ "&&", "||",
+ ">", "<", ">=", "<=", "!=", "==",
+ ",", ";", ".", "?", ":",
+ };
+ color = new CellAttributes();
+ color.setForeColor(Color.CYAN);
+ color.setBackColor(Color.BLUE);
+ color.setBold(true);
+ for (String str: operators) {
+ colors.put(str, color);
+ }
+
+ String [] packageKeywords = {
+ "package", "import",
+ };
+ color = new CellAttributes();
+ color.setForeColor(Color.GREEN);
+ color.setBackColor(Color.BLUE);
+ color.setBold(true);
+ for (String str: packageKeywords) {
+ colors.put(str, color);
+ }
+
+ }
+
+
+}
import java.util.ArrayList;
import java.util.List;
+import jexer.bits.CellAttributes;
+
/**
* A Line represents a single line of text on the screen, as a collection of
* words.
*/
private ArrayList<Word> words = new ArrayList<Word>();
+ /**
+ * The default color for the TEditor class.
+ */
+ private CellAttributes defaultColor = null;
+
+ /**
+ * The text highlighter to use.
+ */
+ private Highlighter highlighter = null;
+
/**
* The current cursor position on this line.
*/
- private int cursorX;
+ private int cursor = 0;
/**
* The current word that the cursor position is in.
*/
private int displayLength = -1;
+ /**
+ * Get the current cursor position.
+ *
+ * @return the cursor position
+ */
+ public int getCursor() {
+ return cursor;
+ }
+
+ /**
+ * Set the current cursor position.
+ *
+ * @param cursor the new cursor position
+ */
+ public void setCursor(final int cursor) {
+ if ((cursor < 0)
+ || ((cursor >= getDisplayLength())
+ && (getDisplayLength() > 0))
+ ) {
+ throw new IndexOutOfBoundsException("Max length is " +
+ getDisplayLength() + ", requested position " + cursor);
+ }
+ this.cursor = cursor;
+ // TODO: set word
+ }
+
/**
* Get a (shallow) copy of the list of words.
*
n += word.getDisplayLength();
}
displayLength = n;
+
+ // If we have any visible characters, add one to the display so that
+ // the cursor is immediately after the data.
+ if (displayLength > 0) {
+ displayLength++;
+ }
return displayLength;
}
/**
- * Construct a new Line from an existing text string.
+ * Construct a new Line from an existing text string, and highlight
+ * certain strings.
*
* @param str the text string
+ * @param defaultColor the color for unhighlighted text
+ * @param highlighter the highlighter to use
*/
- public Line(final String str) {
- currentWord = new Word();
+ public Line(final String str, final CellAttributes defaultColor,
+ final Highlighter highlighter) {
+
+ this.defaultColor = defaultColor;
+ this.highlighter = highlighter;
+
+ currentWord = new Word(this.defaultColor, this.highlighter);
words.add(currentWord);
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
currentWord = newWord;
}
}
+ for (Word word: words) {
+ word.applyHighlight();
+ }
+ }
+
+ /**
+ * Construct a new Line from an existing text string.
+ *
+ * @param str the text string
+ * @param defaultColor the color for unhighlighted text
+ */
+ public Line(final String str, final CellAttributes defaultColor) {
+ this(str, defaultColor, null);
}
/**
* Decrement the cursor by one. If at the first column, do nothing.
+ *
+ * @return true if the cursor position changed
*/
- public void left() {
- if (cursorX == 0) {
- return;
+ public boolean left() {
+ if (cursor == 0) {
+ return false;
}
- // TODO
+ // TODO: switch word
+ cursor--;
+ return true;
}
/**
* Increment the cursor by one. If at the last column, do nothing.
+ *
+ * @return true if the cursor position changed
*/
- public void right() {
- if (cursorX == getDisplayLength() - 1) {
- return;
+ public boolean right() {
+ if (getDisplayLength() == 0) {
+ return false;
}
- // TODO
+ if (cursor == getDisplayLength() - 1) {
+ return false;
+ }
+ // TODO: switch word
+ cursor++;
+ return true;
}
/**
* Go to the first column of this line.
+ *
+ * @return true if the cursor position changed
*/
- public void home() {
- // TODO
+ public boolean home() {
+ if (cursor > 0) {
+ cursor = 0;
+ currentWord = words.get(0);
+ return true;
+ }
+ return false;
}
/**
* Go to the last column of this line.
+ *
+ * @return true if the cursor position changed
*/
- public void end() {
- // TODO
+ public boolean end() {
+ if (cursor != getDisplayLength() - 1) {
+ cursor = getDisplayLength() - 1;
+ if (cursor < 0) {
+ cursor = 0;
+ }
+ currentWord = words.get(words.size() - 1);
+ return true;
+ }
+ return false;
}
/**
}
/**
- * Replace or insert a character at the cursor, depending on overwrite
- * flag.
+ * Insert a character at the cursor.
*
- * @param ch the character to replace or insert
+ * @param ch the character to insert
*/
public void addChar(final char ch) {
// TODO
}
+ /**
+ * Replace a character at the cursor.
+ *
+ * @param ch the character to replace
+ */
+ public void replaceChar(final char ch) {
+ // TODO
+ }
+
}
/**
* A Word represents text that was entered by the user. It can be either
* whitespace or non-whitespace.
+ *
+ * Very dumb highlighting is supported, it has no sense of parsing (not even
+ * comments). For now this only highlights some Java keywords and
+ * puctuation.
*/
public class Word {
*/
private CellAttributes color = new CellAttributes();
+ /**
+ * The default color for the TEditor class.
+ */
+ private CellAttributes defaultColor = null;
+
+ /**
+ * The text highlighter to use.
+ */
+ private Highlighter highlighter = null;
+
/**
* The actual text of this word. Average word length is 6 characters,
* with a lot of shorter ones, so start with 3.
* Construct a word with one character.
*
* @param ch the first character of the word
+ * @param defaultColor the color for unhighlighted text
+ * @param highlighter the highlighter to use
*/
- public Word(final char ch) {
+ public Word(final char ch, final CellAttributes defaultColor,
+ final Highlighter highlighter) {
+
+ this.defaultColor = defaultColor;
+ this.highlighter = highlighter;
text.append(ch);
}
/**
* Construct a word with an empty string.
+ *
+ * @param defaultColor the color for unhighlighted text
+ * @param highlighter the highlighter to use
+ */
+ public Word(final CellAttributes defaultColor,
+ final Highlighter highlighter) {
+
+ this.defaultColor = defaultColor;
+ this.highlighter = highlighter;
+ }
+
+ /**
+ * Perform highlighting.
*/
- public Word() {}
+ public void applyHighlight() {
+ color.setTo(defaultColor);
+ if (highlighter == null) {
+ return;
+ }
+ String key = text.toString();
+ CellAttributes newColor = highlighter.getColor(key);
+ if (newColor != null) {
+ color.setTo(newColor);
+ }
+ }
/**
* Add a character to this word. If this is a whitespace character
text.append(ch);
return this;
}
+
+ // Give the highlighter the option to split here.
+ if (highlighter != null) {
+ if (highlighter.shouldSplit(ch)
+ || highlighter.shouldSplit(text.charAt(0))
+ ) {
+ Word newWord = new Word(ch, defaultColor, highlighter);
+ return newWord;
+ }
+ }
+
+ // Highlighter didn't care, so split at whitespace.
if (Character.isWhitespace(text.charAt(0))
&& Character.isWhitespace(ch)
) {
+ // Adding to a whitespace word, keep at it.
text.append(ch);
return this;
}
if (!Character.isWhitespace(text.charAt(0))
&& !Character.isWhitespace(ch)
) {
+ // Adding to a non-whitespace word, keep at it.
text.append(ch);
return this;
}
- // We will be splitting here.
- Word newWord = new Word(ch);
+ // Switching from whitespace to non-whitespace or vice versa, so
+ // split here.
+ Word newWord = new Word(ch, defaultColor, highlighter);
return newWord;
}