001 /** 002 * jline - Java console input library 003 * Copyright (c) 2002-2006, Marc Prud'hommeaux mwp1@cornell.edu 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or 007 * without modification, are permitted provided that the following 008 * conditions are met: 009 * 010 * Redistributions of source code must retain the above copyright 011 * notice, this list of conditions and the following disclaimer. 012 * 013 * Redistributions in binary form must reproduce the above copyright 014 * notice, this list of conditions and the following disclaimer 015 * in the documentation and/or other materials provided with 016 * the distribution. 017 * 018 * Neither the name of JLine nor the names of its contributors 019 * may be used to endorse or promote products derived from this 020 * software without specific prior written permission. 021 * 022 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 023 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 024 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 025 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 026 * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 027 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 028 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 029 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 030 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 031 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 032 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 033 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 034 * OF THE POSSIBILITY OF SUCH DAMAGE. 035 */ 036 package jline; 037 038 import java.util.*; 039 040 041 /** 042 * A {@link Completor} implementation that invokes a child completor 043 * using the appropriate <i>separator</i> argument. This 044 * can be used instead of the individual completors having to 045 * know about argument parsing semantics. 046 * <p> 047 * <strong>Example 1</strong>: Any argument of the command line can 048 * use file completion. 049 * <p> 050 * <pre> 051 * consoleReader.addCompletor (new ArgumentCompletor ( 052 * new {@link FileNameCompletor} ())) 053 * </pre> 054 * <p> 055 * <strong>Example 2</strong>: The first argument of the command line 056 * can be completed with any of "foo", "bar", or "baz", and remaining 057 * arguments can be completed with a file name. 058 * <p> 059 * <pre> 060 * consoleReader.addCompletor (new ArgumentCompletor ( 061 * new {@link SimpleCompletor} (new String [] { "foo", "bar", "baz"}))); 062 * consoleReader.addCompletor (new ArgumentCompletor ( 063 * new {@link FileNameCompletor} ())); 064 * </pre> 065 * 066 * <p> 067 * When the argument index is past the last embedded completors, the last 068 * completors is always used. To disable this behavior, have the last 069 * completor be a {@link NullCompletor}. For example: 070 * </p> 071 * 072 * <pre> 073 * consoleReader.addCompletor (new ArgumentCompletor ( 074 * new {@link SimpleCompletor} (new String [] { "foo", "bar", "baz"}), 075 * new {@link SimpleCompletor} (new String [] { "xxx", "yyy", "xxx"}), 076 * new {@link NullCompletor} 077 * )); 078 * </pre> 079 * <p> 080 * TODO: handle argument quoting and escape characters 081 * </p> 082 * 083 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 084 */ 085 public class ArgumentCompletor 086 implements Completor 087 { 088 final Completor [] completors; 089 final ArgumentDelimiter delim; 090 boolean strict = true; 091 092 093 /** 094 * Constuctor: create a new completor with the default 095 * argument separator of " ". 096 * 097 * @param completor the embedded completor 098 */ 099 public ArgumentCompletor (final Completor completor) 100 { 101 this (new Completor [] { completor }); 102 } 103 104 105 /** 106 * Constuctor: create a new completor with the default 107 * argument separator of " ". 108 * 109 * @param completors the List of completors to use 110 */ 111 public ArgumentCompletor (final List completors) 112 { 113 this ((Completor [])completors.toArray ( 114 new Completor [completors.size ()])); 115 } 116 117 118 /** 119 * Constuctor: create a new completor with the default 120 * argument separator of " ". 121 * 122 * @param completors the embedded argument completors 123 */ 124 public ArgumentCompletor (final Completor [] completors) 125 { 126 this (completors, new WhitespaceArgumentDelimiter ()); 127 } 128 129 130 /** 131 * Constuctor: create a new completor with the specified 132 * argument delimiter. 133 * 134 * @param completor the embedded completor 135 * @param delim the delimiter for parsing arguments 136 */ 137 public ArgumentCompletor (final Completor completor, 138 final ArgumentDelimiter delim) 139 { 140 this (new Completor [] { completor }, delim); 141 } 142 143 144 /** 145 * Constuctor: create a new completor with the specified 146 * argument delimiter. 147 * 148 * @param completors the embedded completors 149 * @param delim the delimiter for parsing arguments 150 */ 151 public ArgumentCompletor (final Completor [] completors, 152 final ArgumentDelimiter delim) 153 { 154 this.completors = completors; 155 this.delim = delim; 156 } 157 158 159 /** 160 * If true, a completion at argument index N will only succeed 161 * if all the completions from 0-(N-1) also succeed. 162 */ 163 public void setStrict (final boolean strict) 164 { 165 this.strict = strict; 166 } 167 168 169 /** 170 * Returns whether a completion at argument index N will succees 171 * if all the completions from arguments 0-(N-1) also succeed. 172 */ 173 public boolean getStrict () 174 { 175 return this.strict; 176 } 177 178 179 public int complete (final String buffer, final int cursor, 180 final List candidates) 181 { 182 ArgumentList list = delim.delimit (buffer, cursor); 183 int argpos = list.getArgumentPosition (); 184 int argIndex = list.getCursorArgumentIndex (); 185 186 if (argIndex < 0) 187 return -1; 188 189 final Completor comp; 190 191 // if we are beyond the end of the completors, just use the last one 192 if (argIndex >= completors.length) 193 comp = completors [completors.length - 1]; 194 else 195 comp = completors [argIndex]; 196 197 // ensure that all the previous completors are successful before 198 // allowing this completor to pass (only if strict is true). 199 for (int i = 0; getStrict () && i < argIndex; i++) 200 { 201 Completor sub = completors [i >= completors.length 202 ? completors.length - 1 : i]; 203 String [] args = list.getArguments (); 204 String arg = args == null || i >= args.length ? "" : args [i]; 205 206 List subCandidates = new LinkedList (); 207 if (sub.complete (arg, arg.length (), subCandidates) == -1) 208 return -1; 209 210 if (subCandidates.size () == 0) 211 return -1; 212 } 213 214 int ret = comp.complete (list.getCursorArgument (), argpos, candidates); 215 if (ret == -1) 216 return -1; 217 218 int pos = ret + (list.getBufferPosition () - argpos) + 1; 219 220 /** 221 * Special case: when completing in the middle of a line, and the 222 * area under the cursor is a delimiter, then trim any delimiters 223 * from the candidates, since we do not need to have an extra 224 * delimiter. 225 * 226 * E.g., if we have a completion for "foo", and we 227 * enter "f bar" into the buffer, and move to after the "f" 228 * and hit TAB, we want "foo bar" instead of "foo bar". 229 */ 230 if (cursor != buffer.length () && delim.isDelimiter (buffer, cursor)) 231 { 232 for (int i = 0; i < candidates.size (); i++) 233 { 234 String val = candidates.get (i).toString (); 235 while (val.length () > 0 && 236 delim.isDelimiter (val, val.length () - 1)) 237 val = val.substring (0, val.length () - 1); 238 239 candidates.set (i, val); 240 } 241 } 242 243 ConsoleReader.debug ("Completing " + buffer + "(pos=" + cursor + ") " 244 + "with: " + candidates + ": offset=" + pos); 245 246 return pos; 247 } 248 249 250 /** 251 * The {@link ArgumentCompletor.ArgumentDelimiter} allows custom 252 * breaking up of a {@link String} into individual arguments in 253 * order to dispatch the arguments to the nested {@link Completor}. 254 * 255 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 256 */ 257 public static interface ArgumentDelimiter 258 { 259 /** 260 * Break the specified buffer into individual tokens 261 * that can be completed on their own. 262 * 263 * @param buffer the buffer to split 264 * @param argumentPosition the current position of the 265 * cursor in the buffer 266 * @return the tokens 267 */ 268 ArgumentList delimit (String buffer, int argumentPosition); 269 270 271 /** 272 * Returns true if the specified character is a whitespace 273 * parameter. 274 * 275 * @param buffer the complete command buffer 276 * @param pos the index of the character in the buffer 277 * @return true if the character should be a delimiter 278 */ 279 boolean isDelimiter (String buffer, int pos); 280 } 281 282 283 /** 284 * Abstract implementation of a delimiter that uses the 285 * {@link #isDelimiter} method to determine if a particular 286 * character should be used as a delimiter. 287 * 288 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 289 */ 290 public static abstract class AbstractArgumentDelimiter 291 implements ArgumentDelimiter 292 { 293 private char [] quoteChars = new char [] { '\'', '"' }; 294 private char [] escapeChars = new char [] { '\\' }; 295 296 297 public void setQuoteChars (final char [] quoteChars) 298 { 299 this.quoteChars = quoteChars; 300 } 301 302 303 public char [] getQuoteChars () 304 { 305 return this.quoteChars; 306 } 307 308 309 public void setEscapeChars (final char [] escapeChars) 310 { 311 this.escapeChars = escapeChars; 312 } 313 314 315 public char [] getEscapeChars () 316 { 317 return this.escapeChars; 318 } 319 320 321 322 public ArgumentList delimit (final String buffer, final int cursor) 323 { 324 List args = new LinkedList (); 325 StringBuffer arg = new StringBuffer (); 326 int argpos = -1; 327 int bindex = -1; 328 329 for (int i = 0; buffer != null && i <= buffer.length (); i++) 330 { 331 // once we reach the cursor, set the 332 // position of the selected index 333 if (i == cursor) 334 { 335 bindex = args.size (); 336 // the position in the current argument is just the 337 // length of the current argument 338 argpos = arg.length (); 339 } 340 341 if (i == buffer.length () || isDelimiter (buffer, i)) 342 { 343 if (arg.length () > 0) 344 { 345 args.add (arg.toString ()); 346 arg.setLength (0); // reset the arg 347 } 348 } 349 else 350 { 351 arg.append (buffer.charAt (i)); 352 } 353 } 354 355 return new ArgumentList ( 356 (String [])args.toArray (new String [args.size ()]), 357 bindex, argpos, cursor); 358 } 359 360 361 /** 362 * Returns true if the specified character is a whitespace 363 * parameter. Check to ensure that the character is not 364 * escaped by any of 365 * {@link #getQuoteChars}, and is not escaped by ant of the 366 * {@link #getEscapeChars}, and returns true from 367 * {@link #isDelimiterChar}. 368 * 369 * @param buffer the complete command buffer 370 * @param pos the index of the character in the buffer 371 * @return true if the character should be a delimiter 372 */ 373 public boolean isDelimiter (final String buffer, final int pos) 374 { 375 if (isQuoted (buffer, pos)) 376 return false; 377 if (isEscaped (buffer, pos)) 378 return false; 379 380 return isDelimiterChar (buffer, pos); 381 } 382 383 384 public boolean isQuoted (final String buffer, final int pos) 385 { 386 return false; 387 } 388 389 390 public boolean isEscaped (final String buffer, final int pos) 391 { 392 if (pos <= 0) 393 return false; 394 395 for (int i = 0; escapeChars != null && i < escapeChars.length; i++) 396 { 397 if (buffer.charAt (pos) == escapeChars [i]) 398 return !isEscaped (buffer, pos - 1); // escape escape 399 } 400 401 return false; 402 } 403 404 405 /** 406 * Returns true if the character at the specified position 407 * if a delimiter. This method will only be called if the 408 * character is not enclosed in any of the 409 * {@link #getQuoteChars}, and is not escaped by ant of the 410 * {@link #getEscapeChars}. To perform escaping manually, 411 * override {@link #isDelimiter} instead. 412 */ 413 public abstract boolean isDelimiterChar (String buffer, int pos); 414 } 415 416 417 /** 418 * {@link ArgumentCompletor.ArgumentDelimiter} 419 * implementation that counts all 420 * whitespace (as reported by {@link Character#isWhitespace}) 421 * as being a delimiter. 422 * 423 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 424 */ 425 public static class WhitespaceArgumentDelimiter 426 extends AbstractArgumentDelimiter 427 { 428 /** 429 * The character is a delimiter if it is whitespace, and the 430 * preceeding character is not an escape character. 431 */ 432 public boolean isDelimiterChar (String buffer, int pos) 433 { 434 return Character.isWhitespace (buffer.charAt (pos)); 435 } 436 } 437 438 439 /** 440 * The result of a delimited buffer. 441 * 442 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 443 */ 444 public static class ArgumentList 445 { 446 private String [] arguments; 447 private int cursorArgumentIndex; 448 private int argumentPosition; 449 private int bufferPosition; 450 451 /** 452 * @param arguments the array of tokens 453 * @param cursorArgumentIndex the token index of the cursor 454 * @param argumentPosition the position of the cursor in the 455 * current token 456 * @param bufferPosition the position of the cursor in 457 * the whole buffer 458 */ 459 public ArgumentList (String [] arguments, int cursorArgumentIndex, 460 int argumentPosition, int bufferPosition) 461 { 462 this.arguments = arguments; 463 this.cursorArgumentIndex = cursorArgumentIndex; 464 this.argumentPosition = argumentPosition; 465 this.bufferPosition = bufferPosition; 466 } 467 468 469 public void setCursorArgumentIndex (int cursorArgumentIndex) 470 { 471 this.cursorArgumentIndex = cursorArgumentIndex; 472 } 473 474 475 public int getCursorArgumentIndex () 476 { 477 return this.cursorArgumentIndex; 478 } 479 480 481 public String getCursorArgument () 482 { 483 if (cursorArgumentIndex < 0 484 || cursorArgumentIndex >= arguments.length) 485 return null; 486 487 return arguments [cursorArgumentIndex]; 488 } 489 490 491 public void setArgumentPosition (int argumentPosition) 492 { 493 this.argumentPosition = argumentPosition; 494 } 495 496 497 public int getArgumentPosition () 498 { 499 return this.argumentPosition; 500 } 501 502 503 public void setArguments (String [] arguments) 504 { 505 this.arguments = arguments; 506 } 507 508 509 public String [] getArguments () 510 { 511 return this.arguments; 512 } 513 514 515 public void setBufferPosition (int bufferPosition) 516 { 517 this.bufferPosition = bufferPosition; 518 } 519 520 521 public int getBufferPosition () 522 { 523 return this.bufferPosition; 524 } 525 } 526 } 527