Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git

Comments. Code reorg. Don't add a period if the child node at the current path is a leaf. Added methods for each case condition.

Authordjarvis <email>
Date2016-11-09 23:05:44 GMT-0800
Commite30d4375ad311a14403d4f2091bb5f9f30f00e87
Parent0b45052
Delta424 lines added, 382 lines removed, 42-line increase
src/main/java/com/scrivendor/editor/VariableEditor.java
startEventCapture();
setInitialCaretPosition();
- }
-
- /**
- * Receives key presses until the user completes the variable selection. This
- * allows the arrow keys to be used for selecting variables.
- *
- * @param e The key that was pressed.
- */
- private void vModeKeyPressed( KeyEvent e ) {
- final KeyCode keyCode = e.getCode();
-
- switch( keyCode ) {
- case BACK_SPACE:
- deleteSelection();
-
- // Break out of variable mode by back spacing to the original position.
- if( getCurrentCaretPosition() > getInitialCaretPosition() ) {
- autocomplete();
- break;
- }
-
- case ESCAPE:
- stopEventCapture();
- break;
-
- case PERIOD:
- case RIGHT:
- case END:
- conditionalAutocomplete();
- break;
-
- case ENTER:
- acceptPath();
- stopEventCapture();
- break;
-
- case UP:
- cyclePathPrev();
- break;
-
- case DOWN:
- cyclePathNext();
- break;
-
- default:
- if( isVariableNameKey( e ) ) {
- typed( e.getText() );
- }
- }
-
- e.consume();
- }
-
- /**
- * Updates the text with the path selected (or typed) by the user.
- */
- private void autocomplete() {
- System.out.println( "------------" );
- System.out.println( "autocomplete" );
-
- final String path = getCurrentPath();
- System.out.println( "word = '" + path + "'" );
-
- final TreeItem<String> node = findNode( path );
-
- if( !node.isLeaf() ) {
- final String word = getLastPathWord();
- final String label = node.getValue();
- final int delta = difference( label, word );
-
- String remainder = label;
-
- if( delta != NO_DIFFERENCE ) {
- remainder = label.substring( delta );
- }
-
- System.out.println( "word = '" + word + "'" );
- System.out.println( "label = '" + label + "'" );
- System.out.println( "delta = '" + delta + "'" );
- System.out.println( "remain = '" + remainder + "'" );
-
- final StyledTextArea t = getEditor();
- final int posBegan = getCurrentCaretPosition();
- final int posEnded = posBegan + remainder.length();
-
- t.replaceSelection( remainder );
-
- if( posEnded - posBegan > 0 ) {
- t.selectRange( posEnded, posBegan );
- }
-
- expand( node );
- } else {
- System.out.println( "LEAF: " + node );
- }
- }
-
- /**
- * Performs an autocomplete depending on whether the user has finished typing
- * in a word. If there is a selected range, then this will complete the most
- * recent word and jump to the next child.
- */
- private void conditionalAutocomplete() {
- acceptPath();
- typed( SEPARATOR );
- }
-
- /**
- * Inserts text that the user typed at the current caret position, then
- * performs an autocomplete for the variable name.
- *
- * @param text The text to insert, never null.
- */
- private void typed( final String text ) {
- getEditor().replaceSelection( text );
- autocomplete();
- }
-
- /**
- * Called when the user presses either End or Enter key.
- */
- private void acceptPath() {
- final IndexRange range = getSelectionRange();
-
- if( range != null ) {
- final int rangeEnd = range.getEnd();
- final StyledTextArea textArea = getEditor();
- textArea.deselect();
- textArea.moveTo( rangeEnd );
- }
- }
-
- /**
- * Called when the user presses the Backspace key.
- */
- private void deleteSelection() {
- final StyledTextArea textArea = getEditor();
- textArea.replaceSelection( "" );
- textArea.deletePreviousChar();
- }
-
- /**
- * Cycles the selected text through the nodes.
- *
- * @param direction true - next; false - previous
- */
- private void cycleSelection( final boolean direction ) {
- final String path = getCurrentPath();
- final String word = getLastPathWord();
- final TreeItem<String> node = findNode( path );
-
- // Find the sibling for the current selection and replace the current
- // selection with the sibling's value
- TreeItem< String> cycled = direction
- ? node.nextSibling()
- : node.previousSibling();
-
- // When cycling at the end (or beginning) of the list, jump to the first
- // (or last) sibling depending on the cycle direction.
- if( cycled == null ) {
- cycled = direction ? getFirstSibling( node ) : getLastSibling( node );
- }
-
- String cycledWord = cycled.getValue();
-
- final int index = path.indexOf( word );
- final String cycledPath = path.substring( 0, index ) + cycledWord;
-
- expand( cycled );
- replacePath( path, cycledPath );
- }
-
- /**
- * Replaces the entirety of the existing path (from the initial caret
- * position) with the given path.
- *
- * @param oldPath The path to replace.
- * @param newPath The replacement path.
- */
- private void replacePath( final String oldPath, final String newPath ) {
- final StyledTextArea textArea = getEditor();
- final int posBegan = getInitialCaretPosition();
- final int posEnded = posBegan + oldPath.length();
-
- textArea.deselect();
- textArea.replaceText( posBegan, posEnded, newPath );
- }
-
- /**
- * Cycles to the next sibling of the currently selected tree node.
- */
- private void cyclePathNext() {
- cycleSelection( true );
- }
-
- private void cyclePathPrev() {
- cycleSelection( false );
- }
-
- /**
- * Returns the index where the two strings diverge.
- *
- * @param s1 The string that could be a substring of s2, null allowed.
- * @param s2 The string that could be a substring of s1, null allowed.
- *
- * @return NO_DIFFERENCE if the strings are the same, otherwise the index
- * where they differ.
- */
- @SuppressWarnings( "StringEquality" )
- public int difference( final CharSequence s1, final CharSequence s2 ) {
- if( s1 == s2 ) {
- return NO_DIFFERENCE;
- }
-
- if( s1 == null || s2 == null ) {
- return 0;
- }
-
- int i = 0;
- final int limit = min( s1.length(), s2.length() );
-
- while( i < limit && s1.charAt( i ) == s2.charAt( i ) ) {
- i++;
- }
-
- // If one string was shorter than the other, that's where they differ.
- return i;
- }
-
- private <T> ObservableList<TreeItem<T>> getSiblings(
- final TreeItem<T> item ) {
- final TreeItem<T> parent = item.getParent();
- return parent == null ? item.getChildren() : parent.getChildren();
- }
-
- private <T> TreeItem<T> getFirstSibling( final TreeItem<T> item ) {
- return getFirst( getSiblings( item ), item );
- }
-
- private <T> TreeItem<T> getLastSibling( final TreeItem<T> item ) {
- return getLast( getSiblings( item ), item );
- }
-
- private int getCurrentCaretPosition() {
- return getEditor().getCaretPosition();
- }
-
- /**
- * Returns all the characters from the initial caret column to the the first
- * whitespace character. This will return a path that contains zero or more
- * separators.
- *
- * @return A non-null string, possibly empty.
- */
- private String getCurrentPath() {
- final String s = globText();
-
- int i = 0;
-
- while( i < s.length() && !Character.isWhitespace( s.charAt( i ) ) ) {
- i++;
- }
-
- return s.substring( 0, i );
- }
-
- /**
- * Returns the last word from the path.
- *
- * @return The last token.
- */
- private String getLastPathWord() {
- String path = getCurrentPath();
-
- int i = path.indexOf( SEPARATOR );
-
- while( i > 0 ) {
- path = path.substring( i + 1 );
- i = path.indexOf( SEPARATOR );
- }
-
- return path;
- }
-
- /**
- * Returns a swath of text from the initial caret position until .
- *
- * @return
- */
- private String globText() {
- final StyledTextArea textArea = getEditor();
- final int textBegan = getInitialCaretPosition();
- final int remaining = textArea.getLength() - textBegan;
- final int textEnded = Math.min( remaining, getMaxVarLength() );
-
- return textArea.getText( textBegan, textEnded );
- }
-
- /**
- * Finds the node that most closely matches the given path.
- *
- * @param path The path that represents a node.
- *
- * @return The node for the path, or the root node if the path could not be
- * found, but never null.
- */
- private TreeItem<String> findNode( final String path ) {
- return getDefinitionPane().findNode( path );
- }
-
- /**
- * Used to ignore typed keys in favour of trapping pressed keys.
- *
- * @param e The key that was typed.
- */
- private void vModeKeyTyped( KeyEvent e ) {
- e.consume();
- }
-
- /**
- * Used to lazily initialize the keyboard map.
- *
- * @return Mappings for keyTyped and keyPressed.
- */
- protected InputMap<InputEvent> createKeyboardMap() {
- return sequence(
- consume( keyTyped(), this::vModeKeyTyped ),
- consume( keyPressed(), this::vModeKeyPressed )
- );
- }
-
- private InputMap<InputEvent> getKeyboardMap() {
- if( this.keyboardMap == null ) {
- this.keyboardMap = createKeyboardMap();
- }
-
- return this.keyboardMap;
- }
-
- private void expand( final TreeItem<String> node ) {
- final DefinitionPane pane = getDefinitionPane();
- pane.collapse();
- pane.expand( node );
- pane.select( node );
- }
-
- /**
- * Trap the AT key for inserting YAML variables.
- */
- private void initKeyboardEventListeners() {
- addEventListener( keyPressed( DIGIT2, SHIFT_DOWN ), this::atPressed );
- }
-
- /**
- * Returns true iff the key code the user typed can be used as part of a YAML
- * variable name.
- *
- * @param keyEvent Keyboard key press event information.
- *
- * @return true The key is a value that can be inserted into the text.
- */
- private boolean isVariableNameKey( final KeyEvent keyEvent ) {
- final KeyCode kc = keyEvent.getCode();
-
- return (kc.isLetterKey()
- || kc.isDigitKey()
- || (keyEvent.isShiftDown() && kc == MINUS))
- && !keyEvent.isControlDown();
- }
-
- /**
- * Starts to capture user input events.
- */
- private void startEventCapture() {
- addEventListener( getKeyboardMap() );
- }
-
- /**
- * Restores capturing of user input events to the previous event listener.
- */
- private void stopEventCapture() {
- removeEventListener( getKeyboardMap() );
+ autocomplete();
+ }
+
+ /**
+ * Receives key presses until the user completes the variable selection. This
+ * allows the arrow keys to be used for selecting variables.
+ *
+ * @param e The key that was pressed.
+ */
+ private void vModeKeyPressed( KeyEvent e ) {
+ final KeyCode keyCode = e.getCode();
+
+ switch( keyCode ) {
+ case BACK_SPACE:
+ backspace();
+ break;
+
+ case ESCAPE:
+ stopEventCapture();
+ break;
+
+ case ENTER:
+ stopEventCapture();
+
+ // Fall through.
+ case PERIOD:
+ case RIGHT:
+ case END:
+ conditionalAutocomplete();
+ break;
+
+ case UP:
+ cyclePathPrev();
+ break;
+
+ case DOWN:
+ cyclePathNext();
+ break;
+
+ default:
+ filterKey( e );
+ }
+
+ e.consume();
+ }
+
+ /**
+ * Updates the text with the path selected (or typed) by the user.
+ */
+ private void autocomplete() {
+ final TreeItem<String> node = getCurrentNode();
+
+ if( !node.isLeaf() ) {
+ final String word = getLastPathWord();
+ final String label = node.getValue();
+ final int delta = difference( label, word );
+
+ String remainder = label;
+
+ if( delta != NO_DIFFERENCE ) {
+ remainder = label.substring( delta );
+ }
+
+ final StyledTextArea t = getEditor();
+ final int posBegan = getCurrentCaretPosition();
+ final int posEnded = posBegan + remainder.length();
+
+ t.replaceSelection( remainder );
+
+ if( posEnded - posBegan > 0 ) {
+ t.selectRange( posEnded, posBegan );
+ }
+
+ expand( node );
+ }
+ }
+
+ /**
+ * Only variable name keys can pass through the filter.
+ *
+ * @param e The key that was pressed.
+ */
+ private void filterKey( final KeyEvent e ) {
+ if( isVariableNameKey( e ) ) {
+ typed( e.getText() );
+ }
+ }
+
+ private void backspace() {
+ deleteSelection();
+
+ // Break out of variable mode by back spacing to the original position.
+ if( getCurrentCaretPosition() > getInitialCaretPosition() ) {
+ autocomplete();
+ } else {
+ stopEventCapture();
+ }
+ }
+
+ /**
+ * Performs an autocomplete depending on whether the user has finished typing
+ * in a word. If there is a selected range, then this will complete the most
+ * recent word and jump to the next child.
+ */
+ private void conditionalAutocomplete() {
+ acceptPath();
+
+ final TreeItem<String> node = getCurrentNode();
+
+ if( !isTerminal( node ) ) {
+ typed( SEPARATOR );
+ }
+ }
+
+ /**
+ * Returns true if the node has children that can be selected (i.e., any
+ * non-leaves).
+ *
+ * @param <T> The type that the TreeItem contains.
+ * @param node The node to test for terminality.
+ *
+ * @return true The node has one branch and its a leaf.
+ */
+ private <T> boolean isTerminal( final TreeItem<T> node ) {
+ final ObservableList<TreeItem<T>> branches = node.getChildren();
+
+ return branches.size() == 1 && branches.get( 0 ).isLeaf();
+ }
+
+ /**
+ * Inserts text that the user typed at the current caret position, then
+ * performs an autocomplete for the variable name.
+ *
+ * @param text The text to insert, never null.
+ */
+ private void typed( final String text ) {
+ getEditor().replaceSelection( text );
+ autocomplete();
+ }
+
+ /**
+ * Called when the user presses either End or Enter key.
+ */
+ private void acceptPath() {
+ final IndexRange range = getSelectionRange();
+
+ if( range != null ) {
+ final int rangeEnd = range.getEnd();
+ final StyledTextArea textArea = getEditor();
+ textArea.deselect();
+ textArea.moveTo( rangeEnd );
+ }
+ }
+
+ /**
+ * Replaces the entirety of the existing path (from the initial caret
+ * position) with the given path.
+ *
+ * @param oldPath The path to replace.
+ * @param newPath The replacement path.
+ */
+ private void replacePath( final String oldPath, final String newPath ) {
+ final StyledTextArea textArea = getEditor();
+ final int posBegan = getInitialCaretPosition();
+ final int posEnded = posBegan + oldPath.length();
+
+ textArea.deselect();
+ textArea.replaceText( posBegan, posEnded, newPath );
+ }
+
+ /**
+ * Called when the user presses the Backspace key.
+ */
+ private void deleteSelection() {
+ final StyledTextArea textArea = getEditor();
+ textArea.replaceSelection( "" );
+ textArea.deletePreviousChar();
+ }
+
+ /**
+ * Cycles the selected text through the nodes.
+ *
+ * @param direction true - next; false - previous
+ */
+ private void cycleSelection( final boolean direction ) {
+ final TreeItem<String> node = getCurrentNode();
+
+ // Find the sibling for the current selection and replace the current
+ // selection with the sibling's value
+ TreeItem< String> cycled = direction
+ ? node.nextSibling()
+ : node.previousSibling();
+
+ // When cycling at the end (or beginning) of the list, jump to the first
+ // (or last) sibling depending on the cycle direction.
+ if( cycled == null ) {
+ cycled = direction ? getFirstSibling( node ) : getLastSibling( node );
+ }
+
+ final String path = getCurrentPath();
+ final String cycledWord = cycled.getValue();
+ final String word = getLastPathWord();
+ final int index = path.indexOf( word );
+ final String cycledPath = path.substring( 0, index ) + cycledWord;
+
+ expand( cycled );
+ replacePath( path, cycledPath );
+ }
+
+ /**
+ * Cycles to the next sibling of the currently selected tree node.
+ */
+ private void cyclePathNext() {
+ cycleSelection( true );
+ }
+
+ /**
+ * Cycles to the previous sibling of the currently selected tree node.
+ */
+ private void cyclePathPrev() {
+ cycleSelection( false );
+ }
+
+ /**
+ * Returns all the characters from the initial caret column to the the first
+ * whitespace character. This will return a path that contains zero or more
+ * separators.
+ *
+ * @return A non-null string, possibly empty.
+ */
+ private String getCurrentPath() {
+ final String s = extractTextChunk();
+ final int length = s.length();
+
+ int i = 0;
+
+ while( i < length && !Character.isWhitespace( s.charAt( i ) ) ) {
+ i++;
+ }
+
+ return s.substring( 0, i );
+ }
+
+ private <T> ObservableList<TreeItem<T>> getSiblings(
+ final TreeItem<T> item ) {
+ final TreeItem<T> parent = item.getParent();
+ return parent == null ? item.getChildren() : parent.getChildren();
+ }
+
+ private <T> TreeItem<T> getFirstSibling( final TreeItem<T> item ) {
+ return getFirst( getSiblings( item ), item );
+ }
+
+ private <T> TreeItem<T> getLastSibling( final TreeItem<T> item ) {
+ return getLast( getSiblings( item ), item );
+ }
+
+ private int getCurrentCaretPosition() {
+ return getEditor().getCaretPosition();
+ }
+
+ /**
+ * Returns the last word from the path.
+ *
+ * @return The last token.
+ */
+ private String getLastPathWord() {
+ String path = getCurrentPath();
+
+ int i = path.indexOf( SEPARATOR );
+
+ while( i > 0 ) {
+ path = path.substring( i + 1 );
+ i = path.indexOf( SEPARATOR );
+ }
+
+ return path;
+ }
+
+ /**
+ * Returns text from the initial caret position until some arbitrarily long
+ * number of characters. The number of characters extracted will be
+ * getMaxVarLength, or fewer, depending on how many characters remain to be
+ * extracted. The result from this method is trimmed to the first whitespace
+ * character.
+ *
+ * @return A chunk of text that includes all the words representing a path,
+ * and then some.
+ */
+ private String extractTextChunk() {
+ final StyledTextArea textArea = getEditor();
+ final int textBegan = getInitialCaretPosition();
+ final int remaining = textArea.getLength() - textBegan;
+ final int textEnded = Math.min( remaining, getMaxVarLength() );
+
+ return textArea.getText( textBegan, textEnded );
+ }
+
+ /**
+ * Returns the node for the current path.
+ */
+ private TreeItem<String> getCurrentNode() {
+ return findNode( getCurrentPath() );
+ }
+
+ /**
+ * Finds the node that most closely matches the given path.
+ *
+ * @param path The path that represents a node.
+ *
+ * @return The node for the path, or the root node if the path could not be
+ * found, but never null.
+ */
+ private TreeItem<String> findNode( final String path ) {
+ return getDefinitionPane().findNode( path );
+ }
+
+ /**
+ * Used to ignore typed keys in favour of trapping pressed keys.
+ *
+ * @param e The key that was typed.
+ */
+ private void vModeKeyTyped( KeyEvent e ) {
+ e.consume();
+ }
+
+ /**
+ * Used to lazily initialize the keyboard map.
+ *
+ * @return Mappings for keyTyped and keyPressed.
+ */
+ protected InputMap<InputEvent> createKeyboardMap() {
+ return sequence(
+ consume( keyTyped(), this::vModeKeyTyped ),
+ consume( keyPressed(), this::vModeKeyPressed )
+ );
+ }
+
+ private InputMap<InputEvent> getKeyboardMap() {
+ if( this.keyboardMap == null ) {
+ this.keyboardMap = createKeyboardMap();
+ }
+
+ return this.keyboardMap;
+ }
+
+ /**
+ * Collapses the tree then expands and selects the given node.
+ *
+ * @param node The node to expand.
+ */
+ private void expand( final TreeItem<String> node ) {
+ final DefinitionPane pane = getDefinitionPane();
+ pane.collapse();
+ pane.expand( node );
+ pane.select( node );
+ }
+
+ /**
+ * Trap the AT key for inserting YAML variables.
+ */
+ private void initKeyboardEventListeners() {
+ addEventListener( keyPressed( DIGIT2, SHIFT_DOWN ), this::atPressed );
+ }
+
+ /**
+ * Returns true iff the key code the user typed can be used as part of a YAML
+ * variable name.
+ *
+ * @param keyEvent Keyboard key press event information.
+ *
+ * @return true The key is a value that can be inserted into the text.
+ */
+ private boolean isVariableNameKey( final KeyEvent keyEvent ) {
+ final KeyCode kc = keyEvent.getCode();
+
+ return (kc.isLetterKey()
+ || kc.isDigitKey()
+ || (keyEvent.isShiftDown() && kc == MINUS))
+ && !keyEvent.isControlDown();
+ }
+
+ /**
+ * Starts to capture user input events.
+ */
+ private void startEventCapture() {
+ addEventListener( getKeyboardMap() );
+ }
+
+ /**
+ * Restores capturing of user input events to the previous event listener.
+ */
+ private void stopEventCapture() {
+ removeEventListener( getKeyboardMap() );
+ }
+
+ /**
+ * Returns the index where the two strings diverge.
+ *
+ * @param s1 The string that could be a substring of s2, null allowed.
+ * @param s2 The string that could be a substring of s1, null allowed.
+ *
+ * @return NO_DIFFERENCE if the strings are the same, otherwise the index
+ * where they differ.
+ */
+ @SuppressWarnings( "StringEquality" )
+ private int difference( final CharSequence s1, final CharSequence s2 ) {
+ if( s1 == s2 ) {
+ return NO_DIFFERENCE;
+ }
+
+ if( s1 == null || s2 == null ) {
+ return 0;
+ }
+
+ int i = 0;
+ final int limit = min( s1.length(), s2.length() );
+
+ while( i < limit && s1.charAt( i ) == s2.charAt( i ) ) {
+ i++;
+ }
+
+ // If one string was shorter than the other, that's where they differ.
+ return i;
}