/*
* Copyright (C) 2011 Alasdair C. Hamilton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package ket;
import java.util.regex.*;
import java.awt.event.*;
import java.util.*;
import ket.math.purpose.Text;
import ket.Selection;
import ket.math.*;
import ketUI.Clipboard;
import ketUI.Document;
import ketUI.chord.Chord;
import ketUI.chord.KeyPress;
import ketUI.modes.DocumentState;
import ketUI.Ket;
public class Message {
// An optional ampersand or an optional backslash then a word.
// Purposely exclude '_' from variable names.
// TODO: Once they are known: This should contain all instances of PREFIX_CHAR.
static final Pattern SUGGESTION_PATTERN = Pattern.compile("([&\\$#\\\\]?)([a-zA-Z]+)$");
static final Pattern DELETE_LAST_WORD = Pattern.compile("^(.*?)(\\w*\\s+|\\w+)$");
public static final int APPEND_MODE = 1;
public static final int CANCEL_MODE = 2;
public static final int COMPLETE_MODE = 3;
public static final int ECHO_MODE = 4;
public static final int RELATIVE_STAY = 0x0;
public static final int RELATIVE_MOVE = 0x1;
public static final int RELATIVE_EDIT = 0x2;
final MathCollection mathCollection;
final Document document;
final KnownArguments knownArguments;
final Clipboard clipboard;
History history = new History();
Vector<String> suggestions;
boolean pasteFromRegister;
int relative; // Relative editing direction.
int mode;
Line line;
public Message(Document document, KnownArguments knownArguments, MathCollection mathCollection, Clipboard clipboard) {
this.document = document;
this.knownArguments = knownArguments;
this.mathCollection = mathCollection;
this.clipboard = clipboard;
cancelAndClear();
}
/**
* Set the cursor index.
*/
public void setIndex(int cursorIndex) {
line.setIndex(cursorIndex);
}
public int getMode() {
return mode;
}
public void setMessage(String messageString) {
assert messageString!=null : "Cannot assign a null message string.";
softReset();
line.setMessage(messageString);
}
public String getCursorLine() {
return getStart() + "\u007C" + getEnd();
}
public String getLine() {
return line.getLine();
}
public String getStart() {
return line.getStart();
}
public String getEnd() {
return line.getEnd();
}
public int length() {
return line.length();
}
public void cancelAndClear() {
mode = CANCEL_MODE;
history.lastLine();
line = new Line();
pasteFromRegister = false;
relative = RELATIVE_STAY;
suggestions = null;
}
public void insertFromClipboard(KeyPress keyPress) {
Ket.out.println("[insert from clipboard]");
if (keyPress.cancelKeyPressed()) {
Ket.out.println(" !!! Paste cancelled !!! ");
softReset();
return;
}
char character = keyPress.getChar();
if ( ! isTextChar(character) ) {
// Ignore non-text characters.
Ket.out.println(" !!! Illegal clipboard register !!! ");
return;
}
Argument clip = clipboard.getArgument("" + character);
if (clip!=null) {
setMessage(getStart()+clip.toString()+getEnd());
} else {
Ket.out.println(" !!! register '"+character+"' empty !!! ");
}
softReset();
}
// Replace current?
private void moveRelative(KeyPress keyPress) {
char character = keyPress.getChar();
Ket.out.println(" --- move relative ("+character+") --- ");
Argument current = getSelection().getCurrent();
Selection s = getSelection();
Cursor c = s.getCursor();
Argument replacement = document.parseArgument(getLine());
boolean ok;
switch (character) {
case 'i': // TODO: Merge with appendNewSibling().
ok = current.isBranch() || s.variableTokenToBranch(knownArguments);
if (!ok) return;
if (replacement!=null) {
s.replace(replacement);
}
if (relative==RELATIVE_EDIT) {
s.prependNewChild();
} else {
c.selectInOrOutLeft();
setMessage(current.toString());
}
break;
case 'o': // TODO: Merge with prependNewSibling().
ok = current.isBranch() || s.variableTokenToBranch(knownArguments);
if (!ok) return;
if (replacement!=null) {
s.replace(replacement);
}
if (relative==RELATIVE_EDIT) {
s.appendNewChild();
} else {
c.selectInOrOutRight();
setMessage(current.toString());
}
break;
case 'h':
if (replacement!=null) {
s.replace(replacement);
}
if (relative==RELATIVE_EDIT) {
s.prependNewSibling();
} else {
c.selectLeft();
setMessage(current.toString());
}
break;
case 'l':
if (replacement!=null) {
s.replace(replacement);
}
if (relative==RELATIVE_EDIT) {
s.appendNewSibling();
} else {
c.selectRight();
setMessage(current.toString());
}
break;
case 'e':
case 'j':
case 'k':
boolean prepend = (character=='k');
insertEquation(prepend);
break;
case ' ':
if (replacement!=null) {
s.replace(replacement);
}
if (relative==RELATIVE_EDIT) {
document.getModes().setDocumentState(DocumentState.APPEND_PURPOSE);
s.addNewIntermediaryParent();
} else {
document.getModes().setDocumentState(DocumentState.APPEND_PURPOSE);
c.selectParent();
setMessage(getSelection().getCurrent().getPurpose().getName());
}
softReset();
// Note: Don't clear the message!
return;
default:
// TODO: If you are only moving then approach this command as 'f*' or 'F+'.
Function knownFunction = knownArguments.getFunctionByChar(character);
if (replacement!=null) {
s.replace(replacement);
}
s.addIntermediaryParent(knownFunction);
s.appendNewChild();
}
clear();
softReset();
}
public void updateSelection() {
mathCollection.updateUndoStack();
Argument current = getSelection().getCurrent();
EquationList el = current.getEquationList();
Selection s = getSelection();
Argument replacement = document.parseArgument(line.getLine());
if (replacement!=null) {
s.replace(replacement);
}
}
public void insertEquation(boolean prepend) {
/*-
mathCollection.updateUndoStack();
Argument current = getSelection().getCurrent();
EquationList el = current.getEquationList();
Selection s = getSelection();
Argument replacement = document.parseArgument(line.getLine());
if (replacement!=null) {
s.replace(replacement);
}
*/
updateSelection();
Selection s = getSelection();
if (prepend) {
s.prependEquation(new Equation());
} else {
s.appendEquation(new Equation());
}
}
public void toggleTextEquation() {
Selection s = getSelection();
Chord chord = document.getKeyboardEventHandler().getChord();
String line = getLine();
boolean nonblank = ! ("".equals(line));
if (document.getModes().getDocumentState()==DocumentState.UPDATE_TEXT) { //! UPDATE_SUBSTITUTE UPDATE_TEXT
// BROKEN
document.getModes().setDocumentState(DocumentState.UPDATE_REPLACE);
Argument replacement = nonblank ? document.parseArgument(line) : null;
if (replacement!=null) {
s.replace(replacement);
setMessage(line);
}
} else {
// WORKS!
document.getModes().setDocumentState(DocumentState.UPDATE_TEXT);
if (nonblank) {
s.replace(new Token(new Text(line)));
setMessage(line);
}
}
}
public void appendEquation() {
Chord chord = document.getKeyboardEventHandler().getChord();
document.getModes().getAddMode().appendEquation(chord);
}
public void appendNewSibling() {
Argument current = getSelection().getCurrent();
Selection s = getSelection();
Cursor c = s.getCursor();
Argument replacement = document.parseArgument(getLine());
boolean ok = current.isBranch() || s.variableTokenToBranch(knownArguments);
if (!ok) return;
// TODO: What about current.isRoot()?
if (replacement!=null) {
s.replace(replacement);
}
s.appendNewSibling();
clear();
}
public void prependNewSibling() {
Argument current = getSelection().getCurrent();
Selection s = getSelection();
Cursor c = s.getCursor();
Argument replacement = document.parseArgument(getLine());
boolean ok = current.isBranch() || s.variableTokenToBranch(knownArguments);
if (!ok) return;
// TODO: What about current.isRoot()?
if (replacement!=null) {
s.replace(replacement);
}
s.prependNewSibling();
clear();
}
public void appendNewChild() {
Argument current = getSelection().getCurrent();
Selection s = getSelection();
Cursor c = s.getCursor();
Argument replacement = document.parseArgument(getLine());
boolean ok = current.isBranch() || s.variableTokenToBranch(knownArguments);
if (!ok) return;
if (replacement!=null) {
s.replace(replacement);
}
s.appendNewChild();
clear();
}
public void prependNewChild() {
Argument current = getSelection().getCurrent();
Selection s = getSelection();
Cursor c = s.getCursor();
Argument replacement = document.parseArgument(getLine());
boolean ok = current.isBranch() || s.variableTokenToBranch(knownArguments);
if (!ok) return;
if (replacement!=null) {
s.replace(replacement);
}
s.prependNewChild();
clear();
}
public void updateMessage(KeyPress keyPress, boolean textMode) {
char character = keyPress.getChar();
ensureEditingStarted();
if (pasteFromRegister) {
Ket.out.println("[paste from register]");
insertFromClipboard(keyPress);
return;
} else if (relative!=RELATIVE_STAY) {
moveRelative(keyPress);
return;
} // else...
if (textMode) {
// Override a few commands when in text mode.
if (keyPress.ctrlPressed()) {
switch (keyPress.getCode()) {
case "<Enter>": // <Enter> Insert a new line at the cursor.
Ket.out.println("t_<Ctrl-Enter>");
//- clear();
//- insertNewTextLineSplit();
//- insertEquation(false);
appendEquation();
return;
// IS THIS USEFUL?
case "<OpenBracket>": // <Ctrl-[> Leave mode and parse the current message.
if (keyPress.ctrlPressed()) {
Ket.out.println("<Ctrl-[>");
enter();
return;
}
break;
}
switch (character) {
case 'd': // <Ctrl-d> Delete character to the right of the cursor.
if (!line.canMoveRight()) {
mergeWithNextLine();
return;
} // otherwise:
break;
case 'p': // <Ctrl-p> Edit previous text line.
selectPreviousTextLine();
return;
case 'n': // <Ctrl-n> Edit next text line.
selectNextTextLine();
return;
//- case 'o': // <Ctrl-o> Insert a new line at the cursor.
case 'y': // <Ctrl-y> Insert a new line at the cursor.
//- insertNewTextLineSplit();
//- insertIntermediaryEquation();
parseTextAsEquation();
return;
case 'm': // <Ctrl-m> Insert a new line at the cursor.
case 'j': // <Ctrl-j> Insert a new line at the cursor.
insertNewTextLineSplit();
return;
case 'k': // <Ctrl-k> Insert as equation and move to next text line.
insertEquation(false);
clear();
return;
case 'c': // <Ctrl-c> Leave mode and parse the current message.
enter();
return;
case 'h': // <Ctrl>-'h' Delete to the left of the cursor.
if (!line.canMoveLeft()) {
mergeWithPreviousLine();
return;
}
// CHECK THE FOLLOWING:
case '^': // <ctrl>-'^' Join this and the previous line together. (In emacs this is M-^).
case '6': // <ctrl>-'6' Join this and the previous line together.
mergeWithPreviousLine();
return;
}
}
switch (keyPress.getCode()) {
case "<Enter>": // <Enter> Insert a new line at the cursor.
Ket.out.println("<Enter>");
insertNewTextLineSplit();
return;
case "<Escape>": // <Escape> Leave mode and parse the current message.
Ket.out.println("<Escape>");
enter();
return;
case "<BackSpace>": // <Backspace> Join this and the previous line together.
if (!line.canMoveLeft()) {
mergeWithPreviousLine();
return;
} // otherwise...
}
}
if (keyPress.ctrlPressed() || keyPress.shiftPressed()) {
switch (keyPress.getCode()) {
case "<Enter>": // <Enter> Append an equation...
insertEquation(false);
clear();
return;
}
}
switch (keyPress.getCode()) {
case "<Home>": // <home> Move the cursor to the start of the line.
line.home();
return;
case "<End>": // <end> Move the cursor to the end of the line.
line.end();
return;
case "<Enter>": // <enter> Leave mode and parse the current message.
enter();
return;
case "<Escape>": // <Esc> Cancel editing and leave the current mode.
cancelAndClear();
return;
case "<Left>": // <left> Move the cursor one character to the left.
moveLeft();
return;
case "<Right>": // <right> Move the cursor one character to the right.
moveRight();
return;
case "<Down>": // <down> Step forward through previous messages.
moveDown();
return;
case "<Up>": // <up> Step backwards through the previous messages.
moveUp();
return;
case "<BackSpace>": // <Backspace> Remove the character to the left of the cursor.
if (keyPress.altPressed()) { // <M-Backspace> Delete the last word.
removePreviousWord();
} else {
backspace();
}
return;
case "<Delete>": // <del> Delete the character to the right of the cursor.
delete();
return;
}
if (keyPress.altPressed()) {
switch (character) {
case 'v': // <Alt-v> Scroll view up.
document.getKetPanel().moveViewHalfPageUp();
return;
case 'a': // <alt>-'a' Move back to the start of the sentence.
if (line.canMoveLeft() && line.isSentenceEnd()) {
moveLeft();
}
while (line.canMoveLeft() && !line.isSentenceEnd()) {
moveLeft();
}
return;
case 'e': // <alt>+'e' Move forwards to the end of the sentence.
if (line.canMoveRight() && line.isSentenceStart()) {
moveRight();
}
while (line.canMoveRight() && !line.isSentenceStart()) {
moveRight();
}
return;
case 'f': // <alt>-'f' Next word.
if (line.canMoveRight() && !Character.isLetter(line.getRightChar())) {
moveRight();
}
while (line.canMoveRight() && Character.isLetter(line.getRightChar())) {
moveRight();
}
return;
case 'b': // <alt>-'b' Previous word.
if (line.canMoveLeft() && !Character.isLetter(line.getLeftChar())) {
moveLeft();
}
while (line.canMoveLeft() && Character.isLetter(line.getLeftChar())) {
moveLeft();
}
return;
}
}
if (keyPress.ctrlPressed()) {
switch (character) {
case 'v': // <Ctrl-v> Scroll view down.
document.getKetPanel().moveViewHalfPageDown();
return;
case 'a': // <ctrl>-'a' Move the cursor to the start of the line.
line.home();
return;
//~ ijlqsvxy
case 'i': // <Ctrl-i> Prepend a new sibling.
case 'H': // <Ctrl-H> Prepend a new sibling.
prependNewSibling();
return;
case 'o': // <Ctrl-o> Append a new sibling.
case 'L': // <Ctrl-L> Append a new sibling.
appendNewSibling();
return;
case 'I': // <Ctrl-i> Prepend a new child.
prependNewChild();
return;
case 'O': // <Ctrl-o> Append new child.
appendNewChild();
return;
case 'l': // <Ctrl-l> Insert and edit relative...
relative = RELATIVE_MOVE;
return;
case 'q': // <Ctrl-q> Submit the line for parsing and incorporation into the maths collection.
case 'm': // <Ctrl-m> Submit the line for parsing and incorporation into the maths collection.
case 'j': // <Ctrl-j> Submit the line for parsing and incorporation into the maths collection.
enter();
return;
case 'k': // <Ctrl-k> Insert as equation and move to next text line.
insertEquation(false);
clear();
return;
case 't': // <Ctrl-t> Toggle text/equation.
toggleTextEquation();
return;
case 'z': // <Ctrl-z> Insert and edit relative...
relative = RELATIVE_EDIT;
return;
case 's': // <Ctrl-s> Update selection.
updateSelection();
return;
case 'e': // <ctrl>-'e' Move the cursor to the end of the line.
line.end();
return;
case 'w': // <ctrl>-'w' Remove the last 'word' from the line.
removePreviousWord();
return;
case 'f': // <ctrl>-'f' Move the cursor to the right.
moveRight();
return;
case 'd': // <ctrl>-'d' Delete character to the right of the cursor.
delete();
return;
case 'r': // <ctrl>-'r' Insert from clipboard.
pasteFromRegister = true;
return;
case 'u': // <ctrl>-'u' Clear the entire message.
clear();
return;
case 'b': // <ctrl>-'b' Move the cursor to the left.
moveLeft();
return;
case 'p': // <ctrl>-'p' Auto-complete, or change to the previous auto-completion suggestion.
suggestPrevious();
return;
case 'n': // <ctrl>-'n' Auto-complete, or change to the next auto-completion suggestion.
suggestNext();
return;
case 'g': // <ctrl>-'g' Toggle between greek and roman letter.
toggleGreekRoman();
return;
case 'h': // <ctrl>-'h' Delete to the left of the cursor.
backspace();
return;
default:
Ket.out.println(" !!! Unhandled CTRL-'"+character+"' keypress !!! ");
return;
}
}
// Handle unknown characters.
if (isTextChar(character)) {
setMessage(getStart()+character+getEnd());
suggestions = null;
} else {
Ket.out.println("!!! Message::updateMessage: unexpected ketUI.chord '"+keyPress+"' !!!");
}
}
private void removePreviousWord() {
String startString = getStart();
if (startString.length()>0) {
Matcher matcher = DELETE_LAST_WORD.matcher(startString);
if ( ! matcher.find() ) {
// Delete the last character.
backspace();
} else if (matcher.groupCount()>0) {
// Delete the last word.
startString = matcher.group(1);
setMessage(startString + getEnd());
}
}
}
private void toggleGreekRoman() {
String messageStart = getStart();
if (messageStart.length()==0) {
Ket.out.println(" !!! Can't convert an empty character to greek !!! ");
return;
}
char c = messageStart.charAt(messageStart.length()-1);
Symbol g = Symbol.ROMAN_TO_GREEK.get(c);
if (g==null) {
Ket.out.println(" !!! No Greek symbol matched '"+g+"' !!! ");
} else {
setMessage(messageStart.substring(0, messageStart.length()-1) + g.getFullName() + getEnd());
}
softReset(); //?
}
private void suggestNext() {
if (suggestions==null) {
autocompleteArgumentNames();
} else {
// Move to next
if (suggestions.size()>=2) {
suggestions.add(suggestions.remove(0));
}
}
if (suggestions!=null && suggestions.size()>0) {
String word = suggestions.firstElement();
replaceLastWord(word);
}
}
private void suggestPrevious() {
if (suggestions==null) {
autocompleteArgumentNames();
} else {
// Move to previous
if (suggestions.size()>=2) {
String lastElement = suggestions.lastElement();
suggestions.remove(lastElement);
suggestions.add(0, lastElement);
}
} // either way
if (suggestions.size()>0) {
String word = suggestions.firstElement();
replaceLastWord(word);
}
}
private void enter() {
mathCollection.updateUndoStack();
mode = COMPLETE_MODE;
history.appendLine(getLine());
suggestions = null;
line.end(); // TODO: end() or rest(); i.e do you want to keep the messageString while message is hidden?
}
private void parseTextAsEquation() {
mathCollection.updateUndoStack();
Argument current = getSelection().getCurrent();
EquationList el = current.getEquationList();
Argument replacement = document.parseArgument(getStart());
//- Equation appended = new Equation(new Token(new Text(getStart())));
Equation appended = new Equation(replacement);
el.addBefore(current.getEquation(), appended);
setMessage(getEnd());
}
private void insertNewTextLineSplit() {
mathCollection.updateUndoStack();
// Note: This avoids calling selection.setCurrent() implicitly.
Argument current = getSelection().getCurrent();
EquationList el = current.getEquationList();
Equation appended = new Equation(new Token(new Text(getStart()))); // Don't reset: record as done and move on.
el.addBefore(current.getEquation(), appended);
setMessage(getEnd());
}
private void selectPreviousTextLine() {
mathCollection.updateUndoStack();
Argument current = getSelection().getCurrent();
EquationList el = current.getEquationList();
Equation equation = current.getEquation();
Equation previous = equation.getPreviousTextEquation();
if (previous==null) return;
el.swapEquations(previous.getEquationIndex(), equation.getEquationIndex());
String newText = previous.getText();
String oldText = line.getLine();
int index = line.getCursorIndex();
if (newText.length() < line.length()) {
index = newText.length();
}
previous.getRoot().asToken().setState(new Text(oldText));
line.setMessage(newText);
line.setIndex(index);
}
private void selectNextTextLine() {
mathCollection.updateUndoStack();
Argument current = getSelection().getCurrent();
EquationList el = current.getEquationList();
Equation equation = current.getEquation();
Equation next = equation.getNextTextEquation();
if (next==null) return;
el.swapEquations(next.getEquationIndex(), equation.getEquationIndex());
String newText = next.getText();
String oldText = line.getLine();
int index = line.getCursorIndex();
if (newText.length() < line.length()) {
index = newText.length();
}
next.getRoot().asToken().setState(new Text(oldText));
line.setMessage(newText);
line.setIndex(index);
}
private void insertIntermediaryEquation() {
mathCollection.updateUndoStack();
Argument current = getSelection().getCurrent();
EquationList el = current.getEquationList();
Equation appended = new Equation(new Token(new Text(getStart())));
el.addBefore(current.getEquation(), appended);
document.getKeyboardEventHandler().setToInitialState(); // Don't reset: record as done and move on.
Chord chord = document.getKeyboardEventHandler().getChord();
document.getModes().getNormalMode().replaceCurrentArgument(chord);
setMessage(getEnd());
}
private void mergeWithPreviousLine() {
mathCollection.updateUndoStack();
Equation previous = getSelection().getCurrent().getPreviousEquation();
if (previous==null || !previous.isText()) return;
//?~ Equation previous = getSelection().getCurrent().getEquation().getPreviousTextEquation();
// if (previous==null) return;
Text text = (Text) (previous.getRoot().getState());
String content = text.getUnquotedString();
line.home();
setMessage(content+getLine());
previous.removeEquation();
}
private void mergeWithNextLine() {
mathCollection.updateUndoStack();
Equation next = getSelection().getCurrent().getNextEquation();
if (next==null || !next.isText()) return;
Text text = (Text) (next.getRoot().getState());
String content = text.getUnquotedString();
line.home();
//? line.end();
setMessage(getLine() + content);
next.removeEquation();
}
private void delete() {
String endString = getEnd();
if (endString.length()>0) {
endString = endString.substring(1, endString.length());
setMessage( getStart() + endString );
line.moveRight();
softReset();
}
}
private void clear() {
String startString = getStart();
if (startString.length()>0) {
setMessage("");
softReset();
}
}
/**
* Delete the character to the left of the cursor.
*/
private void backspace() {
String startString = getStart();
if (startString.length()>0) {
startString = startString.substring(0, startString.length()-1);
setMessage( startString + getEnd() );
softReset();
}
}
private void moveLeft() {
line.moveLeft();
}
private void moveRight() {
line.moveRight();
}
private void moveDown() {
String message = history.nextLine();
if (message==null) {
return;
}
setMessage(message);
softReset();
}
private void moveUp() {
String message = history.previousLine();
if (message==null) {
return;
}
setMessage(message);
softReset();
}
/**
* Auto-complete the message string currently being edited. From the
* current message, extract the last word fragment, using its
* (optional) first symbolic character to restrict type while
* 'suggestions' is filled with all functions or states that begin with
* the word fragment.
*/
private void autocompleteArgumentNames() {
// Autocomplete from the last word before the cursor, ie the end of messageStart.
String messageStart = getStart();
Matcher matcher = SUGGESTION_PATTERN.matcher(messageStart);
if (matcher.find() && matcher.groupCount()==2) {
// Matched: messageStart=...<symbol><lastWord>
String typeString = matcher.group(1);
char typeChar = typeString.length()==1 ? typeString.charAt(0) : Purpose.DEFAULT_PREFIX_CHAR;
String wordFragment = matcher.group(2);
suggestions = knownArguments.autocompleteArgumentNames(wordFragment, typeChar);
if ( ! suggestions.contains(wordFragment) ) {
// Ensure the search can be undone by cycling through all possible matches and returning to the initial guess.
suggestions.add(matcher.group(0));
}
} else if (messageStart.length()>0) {
// Format: messageStart=...<letter>
char lastChar = messageStart.charAt(messageStart.length()-1);
Function function = knownArguments.getFunctionByChar(lastChar);
if (function!=null) {
// Convert from a character associated with a function to the function's name.
suggestions = new Vector<String>();
String name = function.getName();
suggestions.add(name);
} else {
Ket.out.println(" !!! Cannot process single character !!! ");
suggestions = null;
}
} else {
Ket.out.println(" !!! Problem parsing user input into "+
"<char>?<word>+ (|group|="+matcher.groupCount()+"/2)!!! ");
suggestions = null;
}
}
private void replaceLastWord(String word) {
String messageStart = getStart();
String messageEnd = getEnd();
Matcher matcher = SUGGESTION_PATTERN.matcher(messageStart);
if (matcher.find()) {
// Replace the found word.
if (matcher.start()>0) {
// Retain old message content.
messageStart = messageStart.substring(0, matcher.start()) + word;
} else {
messageStart = word;
}
} else if (messageStart.length()!=0) {
// Replace the last character.
messageStart = messageStart.substring(0, messageStart.length()-1) + word;
} else {
Ket.out.println(" !!! No match found !!! ");
}
setMessage(messageStart + messageEnd);
}
/**
* Returns true if message has been completed.
*/
public boolean isComplete() {
return mode==COMPLETE_MODE;
}
/**
* If editing has not started, then reset the message and the responder.
*/
public void ensureEditingStarted() {
if (mode!=APPEND_MODE) {
cancelAndClear();
softReset();
}
}
public void softReset() {
mode = APPEND_MODE;
suggestions = null;
pasteFromRegister = false;
relative = RELATIVE_STAY;
}
/**
* Returns true if a message was cancelled.
*/
public boolean isCancelled() {
return mode==CANCEL_MODE;
}
/**
* Returns true once a message has been completed or cancelled.
*/
// TODO: This is ambiguous: you can append from cancel responder as a new string.
public boolean isAppendable() {
return mode==APPEND_MODE;
}
/**
* Check that a character can be regularly typed.
*/
public static boolean isTextChar(char c) { // TODO: This is too keyboard specific.
boolean brackets =-1!="()[]{}<>".indexOf(c);
boolean symbol = -1!=" =/\\!\"£$%^&*".indexOf(c);
// WARNING: back-quote and 'not' symbols were removed.
// TODO: Match them by unicode or similar.
boolean other = -1!="_+-;\'#:@~,.?|".indexOf(c);
boolean letter = Character.isLetter(c);
boolean digit = Character.isDigit(c);
return letter || digit || symbol || brackets || other;
}
// -----------------------------------------------------------
// --- ANYTHING BELOW THIS LINE IS SIMPLE, HIGH-LEVEL CODE ---
// -----------------------------------------------------------
private Selection getSelection() {
return mathCollection.getSelection();
}
/**
* Summarize the state of the current message as a string.
*/
public String toString() {
String string = "responder=";
switch (mode) {
case APPEND_MODE:
string += "append-responder";
break;
case CANCEL_MODE :
string += "cancel-responder";
break;
case COMPLETE_MODE :
string += "complete-responder";
break;
case ECHO_MODE :
string += "echo-responder";
break;
}
string += ", message='"+getLine()+"'";
return string;
}
}