| 61 | 61 | |
| 62 | 62 | /** |
| 63 | * Trimmed off the end of a word to match a variable name. | |
| 64 | */ | |
| 65 | private final static String TERMINALS = ":;,.!?-/\\¡¿"; | |
| 66 | ||
| 67 | /** | |
| 68 | 63 | * Contains a view of the definitions. |
| 69 | 64 | */ |
| ... | ||
| 249 | 244 | |
| 250 | 245 | /** |
| 251 | * Returns the leaf that matches the given value. If the value is terminally | |
| 252 | * punctuated, the punctuation is removed if no match was found. | |
| 246 | * Delegates to {@link VariableTreeItem#findLeafExact(String)}. | |
| 253 | 247 | * |
| 254 | * @param value The value to find, never null. | |
| 255 | * @param findMode Defines how to match words. | |
| 256 | * @return The leaf that contains the given value, or null if neither the | |
| 257 | * original value nor the terminally-trimmed value was found. | |
| 248 | * @param text The value to find, never {@code null}. | |
| 249 | * @return The leaf that contains the given value, or {@code null} if | |
| 250 | * not found. | |
| 258 | 251 | */ |
| 259 | public VariableTreeItem<String> findLeaf( | |
| 260 | final String value, final FindMode findMode ) { | |
| 261 | final var root = getTreeRoot(); | |
| 262 | final var leaf = root.findLeaf( value, findMode ); | |
| 263 | ||
| 264 | return leaf == null | |
| 265 | ? root.findLeaf( rtrimTerminalPunctuation( value ) ) | |
| 266 | : leaf; | |
| 252 | public VariableTreeItem<String> findLeafExact( final String text ) { | |
| 253 | return getTreeRoot().findLeafExact( text ); | |
| 267 | 254 | } |
| 268 | 255 | |
| 269 | 256 | /** |
| 270 | * Removes punctuation from the end of a string. | |
| 257 | * Delegates to {@link VariableTreeItem#findLeafContains(String)}. | |
| 271 | 258 | * |
| 272 | * @param s The string to trim, never null. | |
| 273 | * @return The string trimmed of all terminal characters from the end | |
| 259 | * @param text The value to find, never {@code null}. | |
| 260 | * @return The leaf that contains the given value, or {@code null} if | |
| 261 | * not found. | |
| 274 | 262 | */ |
| 275 | private String rtrimTerminalPunctuation( final String s ) { | |
| 276 | assert s != null; | |
| 277 | int index = s.length() - 1; | |
| 263 | public VariableTreeItem<String> findLeafContains( final String text ) { | |
| 264 | return getTreeRoot().findLeafContains( text ); | |
| 265 | } | |
| 278 | 266 | |
| 279 | while( index > 0 && (TERMINALS.indexOf( s.charAt( index ) ) >= 0) ) { | |
| 280 | index--; | |
| 281 | } | |
| 267 | /** | |
| 268 | * Delegates to {@link VariableTreeItem#findLeafContains(String)}. | |
| 269 | * | |
| 270 | * @param text The value to find, never {@code null}. | |
| 271 | * @return The leaf that contains the given value, or {@code null} if | |
| 272 | * not found. | |
| 273 | */ | |
| 274 | public VariableTreeItem<String> findLeafContainsNoCase( final String text ) { | |
| 275 | return getTreeRoot().findLeafContainsNoCase( text ); | |
| 276 | } | |
| 282 | 277 | |
| 283 | return s.substring( 0, index ); | |
| 278 | /** | |
| 279 | * Delegates to {@link VariableTreeItem#findLeafStartsWith(String)}. | |
| 280 | * | |
| 281 | * @param text The value to find, never {@code null}. | |
| 282 | * @return The leaf that contains the given value, or {@code null} if | |
| 283 | * not found. | |
| 284 | */ | |
| 285 | public VariableTreeItem<String> findLeafStartsWith( final String text ) { | |
| 286 | return getTreeRoot().findLeafStartsWith( text ); | |
| 284 | 287 | } |
| 285 | 288 | |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | /** | |
| 31 | * Used to find variable keys by matching values. The values are matched | |
| 32 | * according to the relationships provided in this enumeration. | |
| 33 | */ | |
| 34 | public enum FindMode { | |
| 35 | EXACT, | |
| 36 | CONTAINS, | |
| 37 | STARTS_WITH, | |
| 38 | LEVENSHTEIN | |
| 39 | } | |
| 40 | ||
| 41 | 1 |
| 32 | 32 | import java.text.Normalizer; |
| 33 | 33 | import java.util.Stack; |
| 34 | import java.util.function.BiFunction; | |
| 34 | 35 | |
| 35 | import static com.scrivenvar.definition.FindMode.*; | |
| 36 | 36 | import static java.text.Normalizer.Form.NFD; |
| 37 | 37 | |
| ... | ||
| 54 | 54 | /** |
| 55 | 55 | * Finds a leaf starting at the current node with text that matches the given |
| 56 | * value. | |
| 56 | * value. Search is performed case-sensitively. | |
| 57 | 57 | * |
| 58 | 58 | * @param text The text to match against each leaf in the tree. |
| 59 | * @return The leaf that has a value starting with the given text. | |
| 59 | * @return The leaf that has a value exactly matching the given text. | |
| 60 | 60 | */ |
| 61 | public VariableTreeItem<T> findLeaf( final String text ) { | |
| 62 | return findLeaf( text, STARTS_WITH ); | |
| 61 | public VariableTreeItem<T> findLeafExact( final String text ) { | |
| 62 | return findLeaf( text, VariableTreeItem::valueEquals ); | |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * Finds a leaf starting at the current node with text that matches the given | |
| 67 | * value. Search is performed case-sensitively. | |
| 68 | * | |
| 69 | * @param text The text to match against each leaf in the tree. | |
| 70 | * @return The leaf that has a value that contains the given text. | |
| 71 | */ | |
| 72 | public VariableTreeItem<T> findLeafContains( final String text ) { | |
| 73 | return findLeaf( text, VariableTreeItem::valueContains ); | |
| 74 | } | |
| 75 | ||
| 76 | /** | |
| 77 | * Finds a leaf starting at the current node with text that matches the given | |
| 78 | * value. Search is performed case-insensitively. | |
| 79 | * | |
| 80 | * @param text The text to match against each leaf in the tree. | |
| 81 | * @return The leaf that has a value that contains the given text. | |
| 82 | */ | |
| 83 | public VariableTreeItem<T> findLeafContainsNoCase( final String text ) { | |
| 84 | return findLeaf( text, VariableTreeItem::valueContainsNoCase ); | |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * Finds a leaf starting at the current node with text that matches the given | |
| 89 | * value. Search is performed case-sensitively. | |
| 90 | * | |
| 91 | * @param text The text to match against each leaf in the tree. | |
| 92 | * @return The leaf that has a value that starts with the given text. | |
| 93 | */ | |
| 94 | public VariableTreeItem<T> findLeafStartsWith( final String text ) { | |
| 95 | return findLeaf( text, VariableTreeItem::valueStartsWith ); | |
| 63 | 96 | } |
| 64 | 97 | |
| 65 | 98 | /** |
| 66 | 99 | * Finds a leaf starting at the current node with text that matches the given |
| 67 | 100 | * value. |
| 68 | 101 | * |
| 69 | 102 | * @param text The text to match against each leaf in the tree. |
| 70 | 103 | * @param findMode What algorithm is used to match the given text. |
| 71 | * @return The leaf that has a value starting with the given text. | |
| 104 | * @return The leaf that has a value starting with the given text, or {@code | |
| 105 | * null} if there was no match found. | |
| 72 | 106 | */ |
| 73 | 107 | public VariableTreeItem<T> findLeaf( |
| 74 | final String text, final FindMode findMode ) { | |
| 108 | final String text, | |
| 109 | final BiFunction<VariableTreeItem<T>, String, Boolean> findMode ) { | |
| 75 | 110 | final Stack<VariableTreeItem<T>> stack = new Stack<>(); |
| 76 | final VariableTreeItem<T> root = this; | |
| 77 | ||
| 78 | stack.push( root ); | |
| 111 | stack.push( this ); | |
| 79 | 112 | |
| 80 | // Don't try to find keys for blank/empty variable values. | |
| 113 | // Don't hunt for blank (empty) keys. | |
| 81 | 114 | boolean found = text.isBlank(); |
| 82 | VariableTreeItem<T> node = null; | |
| 83 | 115 | |
| 84 | 116 | while( !found && !stack.isEmpty() ) { |
| 85 | node = stack.pop(); | |
| 117 | final VariableTreeItem<T> node = stack.pop(); | |
| 86 | 118 | |
| 87 | if( findMode == EXACT && node.valueEquals( text ) ) { | |
| 88 | found = true; | |
| 89 | } | |
| 90 | else if( findMode == CONTAINS && node.valueContains( text ) ) { | |
| 91 | found = true; | |
| 92 | } | |
| 93 | else if( findMode == STARTS_WITH && node.valueStartsWith( text ) ) { | |
| 94 | found = true; | |
| 95 | } | |
| 96 | else { | |
| 97 | for( final TreeItem<T> child : node.getChildren() ) { | |
| 98 | stack.push( (VariableTreeItem<T>) child ); | |
| 99 | } | |
| 119 | for( final TreeItem<T> child : node.getChildren() ) { | |
| 120 | final VariableTreeItem<T> result = (VariableTreeItem<T>) child; | |
| 100 | 121 | |
| 101 | // No match found, yet. | |
| 102 | node = null; | |
| 122 | if( result.isLeaf() ) { | |
| 123 | if( found = findMode.apply( result, text ) ) { | |
| 124 | return result; | |
| 125 | } | |
| 126 | } | |
| 127 | else { | |
| 128 | stack.push( result ); | |
| 129 | } | |
| 103 | 130 | } |
| 104 | 131 | } |
| 105 | 132 | |
| 106 | return node; | |
| 133 | return null; | |
| 107 | 134 | } |
| 108 | 135 | |
| ... | ||
| 118 | 145 | |
| 119 | 146 | /** |
| 120 | * Returns true if this node is a leaf and its value starts with the given | |
| 121 | * text. | |
| 147 | * Returns true if this node is a leaf and its value equals the given text. | |
| 122 | 148 | * |
| 123 | 149 | * @param s The text to compare against the node value. |
| 124 | * @return true Node is a leaf and its value starts with the given value. | |
| 150 | * @return true Node is a leaf and its value equals the given value. | |
| 125 | 151 | */ |
| 126 | private boolean valueStartsWith( final String s ) { | |
| 127 | return isLeaf() && getDiacriticlessValue().startsWith( s ); | |
| 152 | private boolean valueEquals( final String s ) { | |
| 153 | return isLeaf() && getValue().equals( s ); | |
| 128 | 154 | } |
| 129 | 155 | |
| ... | ||
| 139 | 165 | |
| 140 | 166 | /** |
| 141 | * Returns true if this node is a leaf and its value equals the given text. | |
| 167 | * Returns true if this node is a leaf and its value contains the given text. | |
| 142 | 168 | * |
| 143 | 169 | * @param s The text to compare against the node value. |
| 144 | * @return true Node is a leaf and its value equals the given value. | |
| 170 | * @return true Node is a leaf and its value contains the given value. | |
| 145 | 171 | */ |
| 146 | private boolean valueEquals( final String s ) { | |
| 147 | return isLeaf() && getValue().equals( s ); | |
| 172 | private boolean valueContainsNoCase( final String s ) { | |
| 173 | return isLeaf() && getDiacriticlessValue() | |
| 174 | .toLowerCase() | |
| 175 | .contains( s.toLowerCase() ); | |
| 176 | } | |
| 177 | ||
| 178 | /** | |
| 179 | * Returns true if this node is a leaf and its value starts with the given | |
| 180 | * text. | |
| 181 | * | |
| 182 | * @param s The text to compare against the node value. | |
| 183 | * @return true Node is a leaf and its value starts with the given value. | |
| 184 | */ | |
| 185 | private boolean valueStartsWith( final String s ) { | |
| 186 | return isLeaf() && getDiacriticlessValue().startsWith( s ); | |
| 148 | 187 | } |
| 149 | 188 | |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | /** | |
| 77 | * Cuts the actively selected text; if no text is selected, this will cut | |
| 78 | * the entire paragraph. | |
| 79 | */ | |
| 76 | 80 | public void cut() { |
| 77 | getEditor().selectParagraph(); | |
| 78 | getEditor().cut(); | |
| 81 | final var editor = getEditor(); | |
| 82 | final var selected = editor.getSelectedText(); | |
| 83 | ||
| 84 | if( selected == null || selected.isEmpty() ) { | |
| 85 | editor.selectParagraph(); | |
| 86 | } | |
| 87 | ||
| 88 | editor.cut(); | |
| 79 | 89 | } |
| 80 | 90 |
| 31 | 31 | import com.scrivenvar.decorators.VariableDecorator; |
| 32 | 32 | import com.scrivenvar.definition.DefinitionPane; |
| 33 | import com.scrivenvar.definition.FindMode; | |
| 34 | 33 | import com.scrivenvar.definition.VariableTreeItem; |
| 35 | 34 | import javafx.scene.control.TreeItem; |
| 36 | 35 | import javafx.scene.input.KeyEvent; |
| 37 | 36 | import org.fxmisc.richtext.StyledTextArea; |
| 38 | 37 | |
| 39 | 38 | import java.nio.file.Path; |
| 40 | 39 | import java.text.BreakIterator; |
| 41 | 40 | |
| 42 | import static com.scrivenvar.definition.FindMode.*; | |
| 43 | 41 | import static javafx.scene.input.KeyCode.SPACE; |
| 44 | 42 | import static javafx.scene.input.KeyCombination.CONTROL_DOWN; |
| ... | ||
| 85 | 83 | |
| 86 | 84 | /** |
| 87 | * Inserts the variable | |
| 85 | * Inserts the currently selected variable from the {@link DefinitionPane}. | |
| 88 | 86 | */ |
| 89 | 87 | public void injectSelectedItem() { |
| 90 | 88 | final var pane = getDefinitionPane(); |
| 91 | 89 | final TreeItem<String> item = pane.getSelectedItem(); |
| 92 | 90 | |
| 93 | 91 | if( item.isLeaf() ) { |
| 94 | final var leaf = pane.findLeaf( item.getValue(), FindMode.EXACT ); | |
| 92 | final var leaf = pane.findLeafExact( item.getValue() ); | |
| 95 | 93 | final var editor = getEditor(); |
| 96 | 94 | |
| ... | ||
| 198 | 196 | } |
| 199 | 197 | |
| 198 | /** | |
| 199 | * Looks for the given word, matching first by exact, next by a starts-with | |
| 200 | * condition with diacritics replaced, then by containment. | |
| 201 | * | |
| 202 | * @param word | |
| 203 | * @return | |
| 204 | */ | |
| 205 | @SuppressWarnings("ConstantConditions") | |
| 200 | 206 | private VariableTreeItem<String> findLeaf( final String word ) { |
| 201 | 207 | assert word != null; |
| 202 | 208 | |
| 203 | VariableTreeItem<String> leaf = findLeafExact( word ); | |
| 209 | final var pane = getDefinitionPane(); | |
| 210 | VariableTreeItem<String> leaf = null; | |
| 204 | 211 | |
| 205 | leaf = leaf == null ? findLeafStartsWith( word ) : leaf; | |
| 206 | leaf = leaf == null ? findLeafContains( word ) : leaf; | |
| 207 | leaf = leaf == null ? findLeafLevenshtein( word ) : leaf; | |
| 212 | leaf = leaf == null ? pane.findLeafExact( word ) : leaf; | |
| 213 | leaf = leaf == null ? pane.findLeafStartsWith( word ) : leaf; | |
| 214 | leaf = leaf == null ? pane.findLeafContains( word ) : leaf; | |
| 215 | leaf = leaf == null ? pane.findLeafContainsNoCase( word ) : leaf; | |
| 208 | 216 | |
| 209 | 217 | return leaf; |
| 210 | } | |
| 211 | ||
| 212 | private VariableTreeItem<String> findLeafExact( final String text ) { | |
| 213 | return findLeaf( text, EXACT ); | |
| 214 | } | |
| 215 | ||
| 216 | private VariableTreeItem<String> findLeafContains( final String text ) { | |
| 217 | return findLeaf( text, CONTAINS ); | |
| 218 | } | |
| 219 | ||
| 220 | private VariableTreeItem<String> findLeafStartsWith( final String text ) { | |
| 221 | return findLeaf( text, STARTS_WITH ); | |
| 222 | } | |
| 223 | ||
| 224 | private VariableTreeItem<String> findLeafLevenshtein( final String text ) { | |
| 225 | return findLeaf( text, LEVENSHTEIN ); | |
| 226 | } | |
| 227 | ||
| 228 | /** | |
| 229 | * Finds the first leaf having a value that starts with the given text, or | |
| 230 | * contains the text if contains is true. | |
| 231 | * | |
| 232 | * @param text The text to find in the definition tree. | |
| 233 | * @param findMode Dictates what search criteria to use for matching words. | |
| 234 | * @return The leaf that starts with the given text, or null if not found. | |
| 235 | */ | |
| 236 | private VariableTreeItem<String> findLeaf( | |
| 237 | final String text, final FindMode findMode ) { | |
| 238 | return getDefinitionPane().findLeaf( text, findMode ); | |
| 239 | 218 | } |
| 240 | 219 | |