1 19 20 package org.netbeans.editor.ext; 21 22 import java.awt.EventQueue ; 23 import java.awt.event.ActionListener ; 24 import java.awt.event.ActionEvent ; 25 import java.beans.PropertyChangeListener ; 26 import java.beans.PropertyChangeEvent ; 27 import javax.swing.Timer ; 28 import javax.swing.SwingUtilities ; 29 import javax.swing.text.JTextComponent ; 30 import javax.swing.event.CaretListener ; 31 import javax.swing.event.CaretEvent ; 32 import javax.swing.event.DocumentListener ; 33 import javax.swing.event.DocumentEvent ; 34 import org.netbeans.editor.BaseDocument; 35 import org.netbeans.editor.Settings; 36 import org.netbeans.editor.SettingsChangeListener; 37 import org.netbeans.editor.SettingsChangeEvent; 38 import org.netbeans.editor.SettingsUtil; 39 import org.netbeans.editor.Utilities; 40 import org.netbeans.editor.WeakTimerListener; 41 import javax.swing.text.BadLocationException ; 42 import javax.swing.event.ListSelectionListener ; 43 import org.openide.util.NbBundle; 44 import org.openide.util.RequestProcessor; 45 46 52 53 public class Completion 54 implements PropertyChangeListener , SettingsChangeListener, ActionListener { 55 56 57 58 59 protected ExtEditorUI extEditorUI; 60 61 62 private CompletionQuery query; 63 64 68 private CompletionQuery.Result lastResult; 69 70 private boolean keyPressed = false; 71 72 73 private CompletionView view; 74 75 78 private ExtCompletionPane pane; 79 80 private JavaDocPane javaDocPane; 81 82 private JDCPopupPanel jdcPopupPanel; 83 84 private boolean autoPopup; 85 86 private int autoPopupDelay; 87 88 private int refreshDelay; 89 90 private boolean instantSubstitution; 91 92 Timer timer; 93 Timer docChangeTimer; 94 95 private DocumentListener docL; 96 private CaretListener caretL; 97 98 private PropertyChangeListener docChangeL; 99 100 private int caretPos=-1; 101 102 private static RequestProcessor serializingRequestProcessor; 104 105 private static final String PROP_DEBUG_COMPLETION = "editor.debug.completion"; private static final boolean DEBUG_COMPLETION = Boolean.getBoolean(PROP_DEBUG_COMPLETION); 108 109 private CancelableRunnable cancellable = new CancelableRunnable() { 114 public void run() {} 115 }; 116 public boolean provokedByAutoPopup; 117 118 public Completion(ExtEditorUI extEditorUI) { 119 this.extEditorUI = extEditorUI; 120 121 timer = new Timer (0, new WeakTimerListener(this)); timer.setRepeats(false); 124 125 docChangeTimer = new Timer (0, new WeakTimerListener(new ActionListener (){ 126 public void actionPerformed(ActionEvent e){ 127 refreshImpl( false ); } 129 })); 130 docChangeTimer.setRepeats(false); 131 132 class CompletionDocumentListener implements DocumentListener { 134 135 private void processTimer(){ 136 docChangeTimer.stop(); 137 setKeyPressed(true); 138 invalidateLastResult(); 139 docChangeTimer.setInitialDelay(refreshDelay); 140 docChangeTimer.setDelay(refreshDelay); 141 docChangeTimer.start(); 142 } 143 144 public void insertUpdate(DocumentEvent evt) { 145 trace("ENTRY insertUpdate"); processTimer(); 147 } 148 149 public void removeUpdate(DocumentEvent evt) { 150 trace("ENTRY removeUpdate"); processTimer(); 152 } 153 154 public void changedUpdate(DocumentEvent evt) { 155 } 156 }; 157 docL = new CompletionDocumentListener(); 158 159 160 class CompletionCaretListener implements CaretListener { 161 public void caretUpdate( CaretEvent e ) { 162 trace("ENTRY caretUpdate"); if (!isPaneVisible()){ 164 cancelRequestImpl(); 166 }else{ 167 refreshImpl( true ); 169 } 170 } 171 }; 172 caretL = new CompletionCaretListener(); 173 174 Settings.addSettingsChangeListener(this); 175 176 synchronized (extEditorUI.getComponentLock()) { 177 JTextComponent component = extEditorUI.getComponent(); 179 if (component != null) { 180 propertyChange(new PropertyChangeEvent (extEditorUI, 181 ExtEditorUI.COMPONENT_PROPERTY, null, component)); 182 } 183 184 extEditorUI.addPropertyChangeListener(this); 185 } 186 } 187 188 public void settingsChange(SettingsChangeEvent evt) { 189 Class kitClass = Utilities.getKitClass(extEditorUI.getComponent()); 190 191 if (kitClass != null) { 192 autoPopup = SettingsUtil.getBoolean(kitClass, 193 ExtSettingsNames.COMPLETION_AUTO_POPUP, 194 ExtSettingsDefaults.defaultCompletionAutoPopup); 195 196 autoPopupDelay = SettingsUtil.getInteger(kitClass, 197 ExtSettingsNames.COMPLETION_AUTO_POPUP_DELAY, 198 ExtSettingsDefaults.defaultCompletionAutoPopupDelay); 199 200 refreshDelay = SettingsUtil.getInteger(kitClass, 201 ExtSettingsNames.COMPLETION_REFRESH_DELAY, 202 ExtSettingsDefaults.defaultCompletionRefreshDelay); 203 204 instantSubstitution = SettingsUtil.getBoolean(kitClass, 205 ExtSettingsNames.COMPLETION_INSTANT_SUBSTITUTION, 206 ExtSettingsDefaults.defaultCompletionInstantSubstitution); 207 208 } 209 } 210 211 public void propertyChange(PropertyChangeEvent evt) { 212 String propName = evt.getPropertyName(); 213 214 if (ExtEditorUI.COMPONENT_PROPERTY.equals(propName)) { 215 JTextComponent component = (JTextComponent )evt.getNewValue(); 216 if (component != null) { 218 settingsChange(null); 219 220 BaseDocument doc = Utilities.getDocument(component); 221 if (doc != null) { 222 doc.addDocumentListener(docL); 223 } 224 225 component.addCaretListener( caretL ); 226 } else { 228 setPaneVisible(false); 229 230 component = (JTextComponent )evt.getOldValue(); 231 232 BaseDocument doc = Utilities.getDocument(component); 233 if (doc != null) { 234 doc.removeDocumentListener(docL); 235 } 236 237 if( component != null ) { 238 component.removeCaretListener( caretL ); 239 } 240 } 241 242 } else if ("document".equals(propName)) { if (evt.getOldValue() instanceof BaseDocument) { 244 ((BaseDocument)evt.getOldValue()).removeDocumentListener(docL); 245 } 246 if (evt.getNewValue() instanceof BaseDocument) { 247 ((BaseDocument)evt.getNewValue()).addDocumentListener(docL); 248 } 249 250 } 251 252 } 253 254 public CompletionPane getPane() { 255 return (CompletionPane) getExtPane(); 256 } 257 258 public ExtCompletionPane getExtPane() { 259 if (pane == null){ 260 pane = new ScrollCompletionPane(extEditorUI); 261 } 262 return pane; 263 } 264 265 protected CompletionView createView() { 266 return new ListCompletionView(); 267 } 268 269 public final CompletionView getView() { 270 if (view == null) { 271 view = createView(); 272 } 273 return view; 274 } 275 276 protected CompletionQuery createQuery() { 277 return null; 278 } 279 280 public final CompletionQuery getQuery() { 281 if (query == null) { 282 query = createQuery(); 283 } 284 return query; 285 } 286 287 public JavaDocPane getJavaDocPane(){ 288 if (javaDocPane == null){ 289 javaDocPane = new ScrollJavaDocPane(extEditorUI); 290 } 291 return javaDocPane; 292 } 293 294 298 public final JDCPopupPanel getJDCPopupPanelIfExists() { 299 return jdcPopupPanel; 300 } 301 302 306 public JDCPopupPanel getJDCPopupPanel(){ 307 if (jdcPopupPanel == null){ 308 jdcPopupPanel = new JDCPopupPanel(extEditorUI, getExtPane(), this); 309 } 310 return jdcPopupPanel; 311 } 312 313 316 public synchronized final CompletionQuery.Result getLastResult() { 317 return lastResult; 318 } 319 320 323 public synchronized final void invalidateLastResult() { 324 currentTask().cancel(); 325 lastResult = null; 326 caretPos = -1; 327 } 328 329 private synchronized void setKeyPressed(boolean value) { 330 keyPressed = value; 331 } 332 333 private synchronized boolean isKeyPressed() { 334 return keyPressed; 335 } 336 337 public synchronized Object getSelectedValue() { 338 if (lastResult != null) { 339 int index = getView().getSelectedIndex(); 340 if (index >= 0 && index<lastResult.getData().size()) { 341 return lastResult.getData().get(index); 342 } 343 } 344 return null; 345 } 346 347 348 public boolean isAutoPopupEnabled() { 349 return autoPopup; 350 } 351 352 357 public boolean isPaneVisible() { 358 return (pane != null && pane.isVisible()); 359 } 360 361 365 public void setPaneVisible(boolean visible) { 366 trace("ENTRY setPaneVisible " + visible); if (visible) { 368 if (extEditorUI.getComponent() != null) { 369 popupImpl(false); 370 } 371 } else { 372 if (pane != null) { 373 cancelRequestImpl(); 374 invalidateLastResult(); 375 getJDCPopupPanel().setCompletionVisible(false); 376 caretPos=-1; 377 } 378 } 379 } 380 381 public void completionCancel(){ 382 trace("ENTRY completionCancel"); if (pane != null){ 384 cancelRequestImpl(); 385 invalidateLastResult(); 386 caretPos=-1; 387 } 388 } 389 390 395 public void refresh(boolean postRequest) { 396 trace("ENTRY refresh " + postRequest); refreshImpl(postRequest); 398 } 399 400 private synchronized void refreshImpl(final boolean postRequest) { 401 402 if (isPaneVisible() == false) return; 404 405 class RefreshTask implements Runnable { 406 private final boolean batch; 407 RefreshTask(boolean batch) { 408 this.batch = batch; 409 } 410 public void run() { 411 if (isPaneVisible()) { 412 timer.stop(); 413 if (batch) { 414 timer.setInitialDelay(refreshDelay); 415 timer.setDelay(refreshDelay); 416 timer.start(); 417 } else { 418 actionPerformed(null); 419 } 420 } 421 } 422 }; 423 424 SwingUtilities.invokeLater(new RefreshTask(postRequest)); 425 } 426 427 436 public void popup(boolean postRequest) { 437 trace("ENTRY popup " + postRequest); popupImpl(postRequest); 439 } 440 441 private synchronized void popupImpl( boolean postRequest) { 442 if (isPaneVisible()) { 443 refreshImpl(postRequest); 444 } else { 445 timer.stop(); 446 if (postRequest) { 447 timer.setInitialDelay(autoPopupDelay); 448 timer.setDelay(autoPopupDelay); 449 timer.start(); 450 } else { 451 actionPerformed(null); 452 } 453 } 454 } 455 456 459 public void cancelRequest() { 460 trace("ENTRY cancelRequest"); cancelRequestImpl(); 462 } 463 464 private synchronized void cancelRequestImpl() { 465 timer.stop(); 466 } 467 468 474 public synchronized void actionPerformed(ActionEvent evt) { 475 476 if (jdcPopupPanel == null) extEditorUI.getCompletionJavaDoc(); 478 final JTextComponent component = extEditorUI.getComponent(); 479 BaseDocument doc = Utilities.getDocument(component); 480 481 if (component != null && doc != null) { 482 483 provokedByAutoPopup = evt != null; 484 485 try{ 486 if((caretPos!=-1) && (Utilities.getRowStart(component,component.getCaret().getDot()) != 487 Utilities.getRowStart(component,caretPos)) && ((component.getCaret().getDot()-caretPos)>0) ){ 488 getJDCPopupPanel().setCompletionVisible(false); 489 caretPos=-1; 490 return; 491 } 492 }catch(BadLocationException ble){ 493 } 494 495 caretPos = component.getCaret().getDot(); 496 497 class PendingTask extends CancelableRunnable { 499 public void run() { 500 if (cancelled()) return; 501 SwingUtilities.invokeLater(new Runnable () { 502 public void run() { 503 if (cancelled()) return; 504 performWait(); 505 } 506 }); 507 } 508 }; 509 510 class QueryTask extends CancelableRunnable { 512 private final CancelableRunnable wait; 513 private final boolean isPaneVisible; 514 public QueryTask(CancelableRunnable wait, boolean isPaneVisible) { 515 this.wait = wait; 516 this.isPaneVisible = isPaneVisible; 517 } 518 public void run() { 519 if (cancelled()) return; 520 try { 521 performQuery(component); 522 } catch ( ThreadDeath td ) { 523 throw td; 524 } catch (Throwable exc){ 525 exc.printStackTrace(); 526 }finally { 527 wait.cancel(); 528 if (cancelled()) return; 529 SwingUtilities.invokeLater( new Runnable () { 530 public void run() { 531 if (cancelled()) return; 532 CompletionQuery.Result res = lastResult; 533 if (res != null) { 534 if (instantSubstitution && res.getData().size() == 1 && 535 !isPaneVisible && instantSubstitution(caretPos)){ 536 setPaneVisible(false); 537 return; 538 } 539 } 540 541 performResults(); 542 } 543 }); 544 } 545 } 546 void cancel() { 547 super.cancel(); 548 wait.cancel(); 549 } 550 }; 551 552 554 currentTask().cancel(); 555 556 RequestProcessor rp; 557 boolean reentrantProvider = getQuery() instanceof CompletionQuery.SupportsSpeculativeInvocation; 558 if (reentrantProvider) { 559 rp = RequestProcessor.getDefault(); 560 } else { 561 rp = getSerialiazingRequestProcessor(); 562 } 563 564 CancelableRunnable wait = new PendingTask(); 565 CancelableRunnable task = new QueryTask(wait, getPane().isVisible()); 566 currentTask(task); 567 if (provokedByAutoPopup == false) { 568 RequestProcessor.getDefault().post(wait, 100); 569 } 570 rp.post(task); 571 } 572 } 573 574 577 private void performWait() { 578 getPane().setTitle(NbBundle.getBundle(org.netbeans.editor.BaseKit.class).getString("ext.Completion.wait")); 579 getView().setResult((CompletionQuery.Result)null); 580 if (isPaneVisible()) { 581 getJDCPopupPanel().refresh(); 582 } else { 583 getJDCPopupPanel().setCompletionVisible(true); 584 } 585 } 586 587 590 private void performQuery(final JTextComponent target) { 591 592 BaseDocument doc = Utilities.getDocument(target); 593 long start = System.currentTimeMillis(); 594 try { 595 lastResult = getQuery().query( target, caretPos, doc.getSyntaxSupport()); 596 } finally { 597 trace("performQuery took " + (System.currentTimeMillis() - start) + "ms"); setKeyPressed(false); 599 } 600 } 601 602 605 protected void performResults() { 606 CompletionQuery.Result res = lastResult; 608 if (res != null) { 609 610 if (instantSubstitution && res.getData().size() == 1 && 611 !isPaneVisible() && instantSubstitution(caretPos)) return; 612 613 getPane().setTitle(res.getTitle()); 614 getView().setResult(res); 615 if (isPaneVisible()) { 616 getJDCPopupPanel().refresh(); 617 } else { 618 getJDCPopupPanel().setCompletionVisible(true); 619 } 620 } else { 621 getJDCPopupPanel().setCompletionVisible(false); 622 623 if (!isKeyPressed()) { 624 caretPos=-1; 625 } else { 626 setKeyPressed(false); 627 } 628 } 629 } 630 631 635 public boolean instantSubstitution(int caretPos){ 636 trace("ENTRY instantSubstitution " + caretPos); return instantSubstitutionImpl(caretPos); 638 } 639 640 private synchronized boolean instantSubstitutionImpl(int caretPos){ 641 if (getLastResult() == null) return false; 642 JTextComponent comp = extEditorUI.getComponent(); 643 try{ 644 if (comp != null) { 645 int[] block = Utilities.getIdentifierBlock(comp,caretPos); 646 if (block == null || block[1] == caretPos) 647 return getLastResult().substituteText(0, false); 648 } 649 }catch(BadLocationException ble){ 650 } 651 return false; 652 } 653 654 655 662 public synchronized boolean substituteText( boolean shift ) { 663 trace("ENTRY substituteText " + shift); if (lastResult != null) { 665 int index = getView().getSelectedIndex(); 666 if (index >= 0) { 667 lastResult.substituteText(index, shift ); 668 } 669 return true; 670 } else { 671 return false; 672 } 673 } 674 675 public synchronized boolean substituteSimpleText() { 676 return false; 677 } 678 679 686 public synchronized boolean substituteCommonText() { 687 trace("ENTRY substituteCommonText"); if (lastResult != null) { 689 int index = getView().getSelectedIndex(); 690 if (index >= 0) { 691 lastResult.substituteCommonText(index); 692 } 693 return true; 694 } else { 695 return false; 696 } 697 } 698 699 700 702 703 706 private void currentTask(CancelableRunnable task) { 707 cancellable = task; 708 } 709 710 713 private CancelableRunnable currentTask() { 714 return cancellable; 715 } 716 717 721 abstract class CancelableRunnable implements Runnable { 722 private boolean cancelled = false; 723 724 boolean cancelled() { 725 return cancelled; 726 } 727 728 void cancel() { 729 cancelled = true; 730 } 731 } 732 733 736 private synchronized RequestProcessor getSerialiazingRequestProcessor() { 737 if (serializingRequestProcessor == null) { 738 serializingRequestProcessor = new RequestProcessor("editor.completion", 1); } 740 return serializingRequestProcessor; 741 } 742 743 745 private static void trace(String msg) { 746 if (DEBUG_COMPLETION && Boolean.getBoolean(PROP_DEBUG_COMPLETION)) { 747 synchronized (System.err) { 748 System.err.println(msg); 749 } 750 } 751 } 752 } 753 | Popular Tags |