Package ket

Source Code of ket.Message

/*
* 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;
  }
}
TOP

Related Classes of ket.Message

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.