001 /* 002 * Copyright (C) 2012 eXo Platform SAS. 003 * 004 * This is free software; you can redistribute it and/or modify it 005 * under the terms of the GNU Lesser General Public License as 006 * published by the Free Software Foundation; either version 2.1 of 007 * the License, or (at your option) any later version. 008 * 009 * This software is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public 015 * License along with this software; if not, write to the Free 016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 018 */ 019 020 package org.crsh.console; 021 022 import org.crsh.cli.impl.Delimiter; 023 import org.crsh.cli.impl.completion.CompletionMatch; 024 import org.crsh.cli.impl.line.LineParser; 025 import org.crsh.cli.impl.line.MultiLineVisitor; 026 import org.crsh.cli.spi.Completion; 027 import org.crsh.util.Utils; 028 029 import java.io.IOException; 030 import java.util.Iterator; 031 import java.util.List; 032 import java.util.Map; 033 034 /** 035 * An action on the editor. 036 */ 037 class EditorAction { 038 039 static class InsertKey extends EditorAction { 040 041 private final int[] sequence; 042 043 public InsertKey(int[] sequence) { 044 this.sequence = sequence; 045 } 046 047 void perform(Editor editor, EditorBuffer buffer) throws IOException { 048 StringBuilder sb = new StringBuilder(sequence.length); 049 for (int c : sequence) { 050 sb.appendCodePoint(c); 051 } 052 buffer.append(sb); 053 } 054 } 055 056 static EditorAction COMPLETE = new EditorAction() { 057 @Override 058 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException { 059 060 // Compute prefix 061 MultiLineVisitor visitor = new MultiLineVisitor(); 062 LineParser parser = new LineParser(visitor); 063 List<String> lines = buffer.getLines(); 064 for (int i = 0;i < lines.size();i++) { 065 if (i > 0) { 066 parser.crlf(); 067 } 068 parser.append(lines.get(i)); 069 } 070 String prefix = visitor.getRaw(); 071 072 // log.log(Level.FINE, "About to get completions for " + prefix); 073 CompletionMatch completion = editor.console.shell.complete(prefix); 074 // log.log(Level.FINE, "Completions for " + prefix + " are " + completions); 075 076 // 077 if (completion != null) { 078 Completion completions = completion.getValue(); 079 080 // 081 Delimiter delimiter = completion.getDelimiter(); 082 083 try { 084 // Try to find the greatest prefix among all the results 085 if (completions.getSize() == 0) { 086 // Do nothing 087 } else if (completions.getSize() == 1) { 088 Map.Entry<String, Boolean> entry = completions.iterator().next(); 089 String insert = entry.getKey(); 090 StringBuilder sb = new StringBuilder(); 091 sb.append(delimiter.escape(insert)); 092 if (entry.getValue()) { 093 sb.append(completion.getDelimiter().getValue()); 094 } 095 buffer.append(sb); 096 editor.console.driver.flush(); 097 } else { 098 String commonCompletion = Utils.findLongestCommonPrefix(completions.getValues()); 099 100 // Format stuff 101 int width = editor.console.driver.getWidth(); 102 103 // 104 String completionPrefix = completions.getPrefix(); 105 106 // Get the max length 107 int max = 0; 108 for (String suffix : completions.getValues()) { 109 max = Math.max(max, completionPrefix.length() + suffix.length()); 110 } 111 112 // Separator : use two whitespace like in BASH 113 max += 2; 114 115 // 116 StringBuilder sb = new StringBuilder().append('\n'); 117 if (max < width) { 118 int columns = width / max; 119 int index = 0; 120 for (String suffix : completions.getValues()) { 121 sb.append(completionPrefix).append(suffix); 122 for (int l = completionPrefix.length() + suffix.length();l < max;l++) { 123 sb.append(' '); 124 } 125 if (++index >= columns) { 126 index = 0; 127 sb.append('\n'); 128 } 129 } 130 if (index > 0) { 131 sb.append('\n'); 132 } 133 } else { 134 for (Iterator<String> i = completions.getValues().iterator();i.hasNext();) { 135 String suffix = i.next(); 136 sb.append(commonCompletion).append(suffix); 137 if (i.hasNext()) { 138 sb.append('\n'); 139 } 140 } 141 sb.append('\n'); 142 } 143 144 // Add current buffer 145 int index = 0; 146 for (String line : lines) { 147 if (index == 0) { 148 String prompt = editor.console.shell.getPrompt(); 149 sb.append(prompt == null ? "" : prompt); 150 } else { 151 sb.append("\n> "); 152 } 153 sb.append(line); 154 index++; 155 } 156 157 // Redraw everything 158 editor.console.driver.write(sb.toString()); 159 160 // If we have common completion we append it now in the buffer 161 if (commonCompletion.length() > 0) { 162 buffer.append(delimiter.escape(commonCompletion)); 163 } 164 165 // Flush 166 buffer.flush(true); 167 } 168 } 169 catch (IOException e) { 170 // log.log(Level.SEVERE, "Could not write completion", e); 171 } 172 } 173 174 // 175 return null; 176 } 177 }; 178 179 static EditorAction INTERRUPT = new EditorAction() { 180 @Override 181 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException { 182 editor.lineParser.reset(); 183 buffer.reset(); 184 editor.console.driver.writeCRLF(); 185 String prompt = editor.console.shell.getPrompt(); 186 if (prompt != null) { 187 editor.console.driver.write(prompt); 188 } 189 if (flush) { 190 editor.console.driver.flush(); 191 } 192 return null; 193 } 194 }; 195 196 static EditorAction EOF_MAYBE = new EditorAction() { 197 @Override 198 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException { 199 if (editor.isEmpty()) { 200 editor.console.status = Console.CLOSING; 201 return null; 202 } else { 203 if (editor.console.getMode() == Mode.EMACS) { 204 return EditorAction.DELETE_PREV_CHAR.execute(editor, buffer, sequence, true); 205 } else { 206 return EditorAction.ENTER.execute(editor, buffer, sequence, true); 207 } 208 } 209 } 210 }; 211 212 public abstract static class History extends EditorAction { 213 214 protected abstract int getNext(Editor editor); 215 216 @Override 217 void perform(Editor editor, EditorBuffer buffer) throws IOException { 218 int nextHistoryCursor = getNext(editor); 219 if (nextHistoryCursor >= -1 && nextHistoryCursor < editor.history.size()) { 220 String s = nextHistoryCursor == -1 ? editor.historyBuffer : editor.history.get(nextHistoryCursor); 221 while (buffer.moveRight()) { 222 // Do nothing 223 } 224 String t = buffer.replace(s); 225 if (editor.historyCursor == -1) { 226 editor.historyBuffer = t; 227 } else { 228 editor.history.set(editor.historyCursor, t); 229 } 230 editor.historyCursor = nextHistoryCursor; 231 } 232 } 233 } 234 235 static EditorAction HISTORY_FIRST = new History() { 236 @Override 237 protected int getNext(Editor editor) { 238 return editor.history.size() - 1; 239 } 240 }; 241 242 static EditorAction HISTORY_LAST = new History() { 243 @Override 244 protected int getNext(Editor editor) { 245 return 0; 246 } 247 }; 248 249 static EditorAction HISTORY_PREV = new History() { 250 @Override 251 protected int getNext(Editor editor) { 252 return editor.historyCursor + 1; 253 } 254 }; 255 256 static EditorAction HISTORY_NEXT = new History() { 257 @Override 258 protected int getNext(Editor editor) { 259 return editor.historyCursor - 1; 260 } 261 }; 262 263 static EditorAction LEFT = new EditorAction() { 264 @Override 265 void perform(Editor editor, EditorBuffer buffer) throws IOException { 266 buffer.moveLeft(); 267 } 268 }; 269 270 static EditorAction RIGHT = new EditorAction() { 271 @Override 272 void perform(Editor editor, EditorBuffer buffer) throws IOException { 273 if (buffer.getCursor() < editor.getCursorBound()) { 274 buffer.moveRight(); 275 } 276 } 277 }; 278 279 static EditorAction MOVE_BEGINNING = new EditorAction() { 280 @Override 281 void perform(Editor editor, EditorBuffer buffer) throws IOException { 282 int cursor = buffer.getCursor(); 283 if (cursor > 0) { 284 buffer.moveLeftBy(cursor); 285 } 286 } 287 }; 288 289 static class MovePrevWord extends EditorAction { 290 291 final boolean atBeginning /* otherwise at end */; 292 293 public MovePrevWord(boolean atBeginning) { 294 this.atBeginning = atBeginning; 295 } 296 297 @Override 298 void perform(Editor editor, EditorBuffer buffer) throws IOException { 299 int cursor = buffer.getCursor(); 300 int pos = cursor; 301 while (pos > 0) { 302 char c = buffer.charAt(pos - 1); 303 if ((atBeginning && Character.isLetterOrDigit(c)) || (!atBeginning && !Character.isLetterOrDigit(c))) { 304 break; 305 } else { 306 pos--; 307 } 308 } 309 while (pos > 0) { 310 char c = buffer.charAt(pos - 1); 311 if ((atBeginning && !Character.isLetterOrDigit(c)) || (!atBeginning && Character.isLetterOrDigit(c))) { 312 break; 313 } else { 314 pos--; 315 } 316 } 317 if (pos < cursor) { 318 buffer.moveLeftBy(cursor - pos); 319 } 320 } 321 } 322 323 static EditorAction MOVE_PREV_WORD_AT_BEGINNING = new MovePrevWord(true); 324 325 static EditorAction MOVE_PREV_WORD_AT_END = new MovePrevWord(false); 326 327 static class MoveNextWord extends EditorAction { 328 329 final At at; 330 331 public MoveNextWord(At at) { 332 this.at = at; 333 } 334 335 @Override 336 void perform(Editor editor, EditorBuffer buffer) throws IOException { 337 int to = editor.getCursorBound(); 338 int from = buffer.getCursor(); 339 int pos = from; 340 while (true) { 341 int look = at == At.BEFORE_END ? pos + 1 : pos; 342 if (look < to) { 343 char c = buffer.charAt(look); 344 if ((at != At.BEGINNING && Character.isLetterOrDigit(c)) || (at == At.BEGINNING && !Character.isLetterOrDigit(c))) { 345 break; 346 } else { 347 pos++; 348 } 349 } else { 350 break; 351 } 352 } 353 while (true) { 354 int look = at == At.BEFORE_END ? pos + 1 : pos; 355 if (look < to) { 356 char c = buffer.charAt(look); 357 if ((at != At.BEGINNING && !Character.isLetterOrDigit(c)) || (at == At.BEGINNING && Character.isLetterOrDigit(c))) { 358 break; 359 } else { 360 pos++; 361 } 362 } else { 363 break; 364 } 365 } 366 if (pos > from) { 367 buffer.moveRightBy(pos - from); 368 } 369 } 370 } 371 372 static EditorAction MOVE_NEXT_WORD_AT_BEGINNING = new MoveNextWord(At.BEGINNING); 373 374 static EditorAction MOVE_NEXT_WORD_AFTER_END = new MoveNextWord(At.AFTER_END); 375 376 static EditorAction MOVE_NEXT_WORD_BEFORE_END = new MoveNextWord(At.BEFORE_END); 377 378 static EditorAction DELETE_PREV_WORD = new EditorAction() { 379 @Override 380 void perform(Editor editor, EditorBuffer buffer) throws IOException { 381 editor.killBuffer.setLength(0); 382 boolean chars = false; 383 while (true) { 384 int cursor = buffer.getCursor(); 385 if (cursor > 0) { 386 if (buffer.charAt(cursor - 1) == ' ') { 387 if (!chars) { 388 editor.killBuffer.appendCodePoint(buffer.del()); 389 } else { 390 break; 391 } 392 } else { 393 editor.killBuffer.appendCodePoint(buffer.del()); 394 chars = true; 395 } 396 } else { 397 break; 398 } 399 } 400 editor.killBuffer.reverse(); 401 } 402 }; 403 404 static EditorAction DELETE_NEXT_WORD = new EditorAction() { 405 @Override 406 void perform(Editor editor, EditorBuffer buffer) throws IOException { 407 int count = 0; 408 boolean chars = false; 409 while (true) { 410 if (buffer.getCursor() < buffer.getSize()) { 411 char c = buffer.charAt(buffer.getCursor()); 412 if (!Character.isLetterOrDigit(c)) { 413 if (!chars) { 414 count++; 415 buffer.moveRight(); 416 } else { 417 break; 418 } 419 } else { 420 chars = true; 421 count++; 422 buffer.moveRight(); 423 } 424 } else { 425 break; 426 } 427 } 428 editor.killBuffer.setLength(0); 429 while (count-- > 0) { 430 editor.killBuffer.appendCodePoint(buffer.del()); 431 } 432 editor.killBuffer.reverse(); 433 } 434 }; 435 436 static EditorAction DELETE_UNTIL_NEXT_WORD = new EditorAction() { 437 @Override 438 void perform(Editor editor, EditorBuffer buffer) throws IOException { 439 int pos = buffer.getCursor(); 440 EditorAction.MOVE_NEXT_WORD_AT_BEGINNING.perform(editor, buffer); 441 while (buffer.getCursor() > pos) { 442 buffer.del(); 443 } 444 } 445 }; 446 447 static EditorAction DELETE_END = new EditorAction() { 448 @Override 449 void perform(Editor editor, EditorBuffer buffer) throws IOException { 450 int count = 0; 451 while (buffer.moveRight()) { 452 count++; 453 } 454 editor.killBuffer.setLength(0); 455 while (count-- > 0) { 456 editor.killBuffer.appendCodePoint(buffer.del()); 457 } 458 editor.killBuffer.reverse(); 459 if (buffer.getCursor() > editor.getCursorBound()) { 460 buffer.moveLeft(); 461 } 462 } 463 }; 464 465 static EditorAction DELETE_BEGINNING = new EditorAction() { 466 @Override 467 void perform(Editor editor, EditorBuffer buffer) throws IOException { 468 editor.killBuffer.setLength(0); 469 while (editor.buffer.getCursor() > 0) { 470 editor.killBuffer.appendCodePoint(buffer.del()); 471 } 472 editor.killBuffer.reverse(); 473 } 474 }; 475 476 static EditorAction UNIX_LINE_DISCARD = new EditorAction() { 477 @Override 478 void perform(Editor editor, EditorBuffer buffer) throws IOException { 479 // Not really efficient 480 if (buffer.getCursor() > 0) { 481 editor.killBuffer.setLength(0); 482 while (buffer.getCursor() > 0) { 483 int c = buffer.del(); 484 editor.killBuffer.appendCodePoint(c); 485 } 486 editor.killBuffer.reverse(); 487 } 488 } 489 }; 490 491 static EditorAction DELETE_LINE = new EditorAction() { 492 @Override 493 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException { 494 buffer.moveRightBy(buffer.getSize() - buffer.getCursor()); 495 buffer.replace(""); 496 return null; 497 } 498 }; 499 500 static EditorAction PASTE_AFTER = new EditorAction() { 501 @Override 502 void perform(Editor editor, EditorBuffer buffer) throws IOException { 503 if (editor.killBuffer.length() > 0) { 504 for (int i = 0;i < editor.killBuffer.length();i++) { 505 char c = editor.killBuffer.charAt(i); 506 buffer.append(c); 507 } 508 } 509 } 510 }; 511 512 static EditorAction MOVE_END = new EditorAction() { 513 @Override 514 void perform(Editor editor, EditorBuffer buffer) throws IOException { 515 int cursor = editor.getCursorBound() - buffer.getCursor(); 516 if (cursor > 0) { 517 buffer.moveRightBy(cursor); 518 } 519 } 520 }; 521 522 static abstract class Copy extends EditorAction { 523 524 protected abstract int getFrom(EditorBuffer buffer); 525 526 protected abstract int getTo(EditorBuffer buffer); 527 528 @Override 529 void perform(Editor editor, EditorBuffer buffer) throws IOException { 530 int from = getFrom(buffer); 531 int to = getTo(buffer); 532 editor.killBuffer.setLength(0); 533 for (int i = from;i < to;i++) { 534 editor.killBuffer.append(editor.buffer.charAt(i)); 535 } 536 } 537 } 538 539 static EditorAction COPY = new Copy() { 540 @Override 541 protected int getFrom(EditorBuffer buffer) { 542 return 0; 543 } 544 @Override 545 protected int getTo(EditorBuffer buffer) { 546 return buffer.getSize(); 547 } 548 }; 549 550 static EditorAction COPY_END_OF_LINE = new Copy() { 551 @Override 552 protected int getFrom(EditorBuffer buffer) { 553 return buffer.getCursor(); 554 } 555 @Override 556 protected int getTo(EditorBuffer buffer) { 557 return buffer.getSize(); 558 } 559 }; 560 561 static EditorAction COPY_BEGINNING_OF_LINE = new Copy() { 562 @Override 563 protected int getFrom(EditorBuffer buffer) { 564 return 0; 565 } 566 @Override 567 protected int getTo(EditorBuffer buffer) { 568 return buffer.getCursor(); 569 } 570 }; 571 572 static EditorAction COPY_NEXT_WORD = new EditorAction() { 573 @Override 574 void perform(Editor editor, EditorBuffer buffer) throws IOException { 575 int size = editor.buffer.getSize(); 576 int cursor = editor.buffer.getCursor(); 577 editor.killBuffer.setLength(0); 578 while (cursor < size && editor.buffer.charAt(cursor) != ' ') { 579 editor.killBuffer.append(editor.buffer.charAt(cursor++)); 580 } 581 while (cursor < size && editor.buffer.charAt(cursor) == ' ') { 582 editor.killBuffer.append(editor.buffer.charAt(cursor++)); 583 } 584 } 585 }; 586 587 static EditorAction COPY_PREV_WORD = new EditorAction() { 588 @Override 589 void perform(Editor editor, EditorBuffer buffer) throws IOException { 590 int cursor = buffer.getCursor() - 1; 591 editor.killBuffer.setLength(0); 592 while (cursor > 0 && buffer.charAt(cursor) != ' ') { 593 editor.killBuffer.append(buffer.charAt(cursor--)); 594 } 595 while (cursor > 0 && editor.buffer.charAt(cursor) == ' ') { 596 editor.killBuffer.append(buffer.charAt(cursor--)); 597 } 598 editor.killBuffer.reverse(); 599 } 600 }; 601 602 static class ChangeChars extends EditorAction { 603 604 /** . */ 605 public final int count; 606 607 /** . */ 608 public final int c; 609 610 public ChangeChars(int count, int c) { 611 this.count = count; 612 this.c = c; 613 } 614 615 @Override 616 void perform(Editor editor, EditorBuffer buffer) throws IOException { 617 int a = Math.min(count, buffer.getSize() - buffer.getCursor()); 618 while (a-- > 0) { 619 buffer.moveRight((char)c); 620 } 621 buffer.moveLeft(); 622 } 623 } 624 625 static EditorAction DELETE_PREV_CHAR = new EditorAction() { 626 @Override 627 void perform(Editor editor, EditorBuffer buffer) throws IOException { 628 buffer.del(); 629 } 630 }; 631 632 static class DeleteNextChars extends EditorAction { 633 634 /** . */ 635 public final int count; 636 637 public DeleteNextChars(int count) { 638 this.count = count; 639 } 640 641 @Override 642 void perform(Editor editor, EditorBuffer buffer) throws IOException { 643 int tmp = count; 644 while (tmp > 0 && buffer.moveRight()) { 645 tmp--; 646 } 647 while (tmp++ < count) { 648 buffer.del(); 649 } 650 if (buffer.getCursor() > editor.getCursorBound()) { 651 buffer.moveLeft(); 652 } 653 } 654 } 655 656 static EditorAction DELETE_NEXT_CHAR = ((EditorAction)new DeleteNextChars(1)); 657 658 static EditorAction CHANGE_CASE = new EditorAction() { 659 @Override 660 void perform(Editor editor, EditorBuffer buffer) throws IOException { 661 if (buffer.getCursor() < buffer.getSize()) { 662 char c = buffer.charAt(buffer.getCursor()); 663 if (Character.isUpperCase(c)) { 664 c = Character.toLowerCase(c); 665 } 666 else if (Character.isLowerCase(c)) { 667 c = Character.toUpperCase(c); 668 } 669 buffer.moveRight(c); 670 if (buffer.getCursor() > editor.getCursorBound()) { 671 buffer.moveLeft(); 672 } 673 } 674 } 675 }; 676 677 static EditorAction TRANSPOSE_CHARS = new EditorAction() { 678 @Override 679 void perform(Editor editor, EditorBuffer buffer) throws IOException { 680 if (buffer.getSize() > 2) { 681 int pos = buffer.getCursor(); 682 if (pos > 0) { 683 if (pos < buffer.getSize()) { 684 if (buffer.moveLeft()) { 685 char a = buffer.charAt(pos - 1); 686 char b = buffer.charAt(pos); 687 buffer.moveRight(b); // Should be assertion 688 buffer.moveRight(a); // Should be assertion 689 // A bit not great : need to find a better way to do that... 690 if (editor.console.getMode() == Mode.VI_MOVE && buffer.getCursor() > editor.getCursorBound()) { 691 buffer.moveLeft(); 692 } 693 } 694 } else { 695 if (buffer.moveLeft() && buffer.moveLeft()) { 696 char a = buffer.charAt(pos - 2); 697 char b = buffer.charAt(pos - 1); 698 buffer.moveRight(b); // Should be assertion 699 buffer.moveRight(a); // Should be assertion 700 } 701 } 702 } 703 } 704 } 705 }; 706 707 static EditorAction INSERT_COMMENT = new EditorAction() { 708 @Override 709 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException { 710 EditorAction.MOVE_BEGINNING.perform(editor, buffer); 711 buffer.append("#"); 712 return EditorAction.ENTER.execute(editor, buffer, sequence, flush); 713 } 714 }; 715 716 static EditorAction CLS = new EditorAction() { 717 @Override 718 void perform(Editor editor, EditorBuffer buffer) throws IOException { 719 editor.console.driver.cls(); 720 StringBuilder sb = new StringBuilder(); 721 int index = 0; 722 List<String> lines = buffer.getLines(); 723 for (String line : lines) { 724 if (index == 0) { 725 String prompt = editor.console.shell.getPrompt(); 726 sb.append(prompt == null ? "" : prompt); 727 } else { 728 sb.append("\n> "); 729 } 730 sb.append(line); 731 index++; 732 } 733 editor.console.driver.write(sb.toString()); 734 editor.console.driver.flush(); 735 } 736 }; 737 738 static EditorAction ENTER = new EditorAction() { 739 @Override 740 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException { 741 editor.historyCursor = -1; 742 editor.historyBuffer = null; 743 String line = buffer.getLine(); 744 editor.lineParser.append(line); 745 if (editor.console.getMode() == Mode.VI_MOVE) { 746 editor.console.setMode(Mode.VI_INSERT); 747 } 748 if (editor.lineParser.crlf()) { 749 editor.console.driver.writeCRLF(); 750 editor.console.driver.flush(); 751 String request = editor.visitor.getRaw(); 752 if (request.length() > 0) { 753 editor.addToHistory(request); 754 } 755 return request; 756 } else { 757 buffer.append('\n'); 758 editor.console.driver.write("> "); 759 if (flush) { 760 buffer.flush(); 761 } 762 return null; 763 } 764 } 765 }; 766 767 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException { 768 perform(editor, buffer); 769 if (flush) { 770 buffer.flush(); 771 } 772 return null; 773 } 774 775 void perform(Editor editor, EditorBuffer buffer) throws IOException { 776 throw new UnsupportedOperationException("Implement the edition logic"); 777 } 778 779 public EditorAction then(final EditorAction action) { 780 return new EditorAction() { 781 @Override 782 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException { 783 EditorAction.this.execute(editor, buffer, sequence, flush); 784 return action.execute(editor, buffer, sequence, flush); 785 } 786 }; 787 } 788 789 public EditorAction repeat(final int count) { 790 return new EditorAction() { 791 @Override 792 void perform(Editor editor, EditorBuffer buffer) throws IOException { 793 for (int i = 0;i < count;i++) { 794 EditorAction.this.perform(editor, buffer); 795 } 796 } 797 }; 798 } 799 }