1 19 20 package org.netbeans.modules.languages.studio; 21 22 import java.lang.UnsupportedOperationException ; 23 import javax.swing.JTree ; 24 import javax.swing.SwingUtilities ; 25 import javax.swing.event.TreeSelectionEvent ; 26 import javax.swing.event.TreeSelectionListener ; 27 import javax.swing.tree.DefaultMutableTreeNode ; 28 import javax.swing.tree.DefaultTreeCellRenderer ; 29 import javax.swing.tree.TreeNode ; 30 import javax.swing.tree.TreePath ; 31 import org.netbeans.api.languages.ASTToken; 32 import org.netbeans.api.lexer.Token; 33 import org.netbeans.api.lexer.TokenHierarchy; 34 import org.netbeans.api.lexer.TokenSequence; 35 import org.netbeans.api.lexer.TokenSequence; 36 import org.openide.ErrorManager; 37 import org.openide.cookies.EditorCookie; 38 import org.openide.nodes.Node; 39 import org.openide.util.NbBundle; 40 import org.openide.util.RequestProcessor; 41 import org.openide.windows.TopComponent; 42 import org.openide.windows.WindowManager; 43 import java.util.Enumeration ; 44 import javax.swing.event.DocumentEvent ; 45 import javax.swing.event.DocumentListener ; 46 import javax.swing.text.AbstractDocument ; 47 import javax.swing.event.CaretEvent ; 48 import javax.swing.event.CaretListener ; 49 import java.awt.BorderLayout ; 50 import java.awt.Color ; 51 import java.awt.Component ; 52 import java.awt.event.FocusEvent ; 53 import java.awt.event.FocusListener ; 54 import java.beans.PropertyChangeEvent ; 55 import java.beans.PropertyChangeListener ; 56 import java.io.Serializable ; 57 import java.lang.ref.WeakReference ; 58 import java.util.ConcurrentModificationException ; 59 import javax.swing.JEditorPane ; 60 import javax.swing.JScrollPane ; 61 import javax.swing.tree.DefaultTreeModel ; 62 63 64 67 final class TokensBrowserTopComponent extends TopComponent { 68 69 private static final String PREFERRED_ID = "TokensBrowserTopComponent"; 70 private static final long serialVersionUID = 1L; 71 private static TokensBrowserTopComponent instance; 72 73 private JTree tree; 74 private Listener listener; 75 private HighlighterSupport highlighting = new HighlighterSupport (Color.yellow); 76 private boolean listen = true; 77 private CaretListener caretListener; 78 private JEditorPane lastPane; 79 private DocumentListener documentListener; 80 private AbstractDocument lastDocument; 81 82 83 private TokensBrowserTopComponent () { 84 initComponents (); 85 setLayout (new BorderLayout ()); 86 tree = new JTree (); 87 tree.setCellRenderer (new Renderer ()); 88 tree.addTreeSelectionListener (new TreeSelectionListener () { 89 public void valueChanged (TreeSelectionEvent e) { 90 if (!listen) return; 91 mark (); 92 } 93 }); 94 tree.addFocusListener (new FocusListener () { 95 public void focusGained (FocusEvent e) { 96 mark (); 97 } 98 public void focusLost (FocusEvent e) { 99 mark (); 100 } 101 }); 102 tree.setRootVisible (false); 103 add (new JScrollPane (tree), BorderLayout.CENTER); 104 setName (NbBundle.getMessage (TokensBrowserTopComponent.class, "CTL_TokensBrowserTopComponent")); 105 setToolTipText (NbBundle.getMessage (TokensBrowserTopComponent.class, "HINT_TokensBrowserTopComponent")); 106 } 108 109 114 private void initComponents() { 116 117 org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); 118 this.setLayout(layout); 119 layout.setHorizontalGroup( 120 layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) 121 .add(0, 400, Short.MAX_VALUE) 122 ); 123 layout.setVerticalGroup( 124 layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) 125 .add(0, 300, Short.MAX_VALUE) 126 ); 127 } 129 130 133 138 public static synchronized TokensBrowserTopComponent getDefault () { 139 if (instance == null) { 140 instance = new TokensBrowserTopComponent (); 141 } 142 return instance; 143 } 144 145 148 public static synchronized TokensBrowserTopComponent findInstance () { 149 TopComponent win = WindowManager.getDefault ().findTopComponent (PREFERRED_ID); 150 if (win == null) { 151 ErrorManager.getDefault ().log (ErrorManager.WARNING, "Cannot find TokensBrowser component. It will not be located properly in the window system."); 152 return getDefault (); 153 } 154 if (win instanceof TokensBrowserTopComponent) { 155 return (TokensBrowserTopComponent)win; 156 } 157 ErrorManager.getDefault ().log (ErrorManager.WARNING, "There seem to be multiple components with the '" + PREFERRED_ID + "' ID. That is a potential source of errors and unexpected behavior."); 158 return getDefault (); 159 } 160 161 public int getPersistenceType () { 162 return TopComponent.PERSISTENCE_ALWAYS; 163 } 164 165 protected void componentShowing () { 166 refresh (); 167 if (listener == null) 168 listener = new Listener (this); 169 } 170 171 protected void componentHidden () { 172 if (listener != null) { 173 listener.remove (); 174 listener = null; 175 } 176 if (lastPane != null) 177 lastPane.removeCaretListener (caretListener); 178 lastPane = null; 179 if (lastDocument != null) 180 lastDocument.removeDocumentListener (documentListener); 181 lastDocument = null; 182 highlighting.removeHighlight (); 183 } 184 185 186 public Object writeReplace () { 187 return new ResolvableHelper (); 188 } 189 190 protected String preferredID () { 191 return PREFERRED_ID; 192 } 193 194 private void mark () { 195 Node [] ns = TopComponent.getRegistry ().getActivatedNodes (); 196 if (ns.length == 1 && tree.isFocusOwner ()) { 197 EditorCookie editorCookie = (EditorCookie) ns [0].getLookup (). 198 lookup (EditorCookie.class); 199 if (editorCookie != null) { 200 THNode t = (THNode) tree.getLastSelectedPathComponent (); 201 if (t == null) return; 202 Token token = t.getToken (); 203 if (token == null) return; 204 ASTToken stoken = ASTToken.create ( 205 t.getMimeType (), 206 token.id ().name (), 207 token.text ().toString (), 208 t.getOffset () 209 ); 210 if (t != null) { 211 highlighting.highlight ( 212 editorCookie.getDocument (), 213 stoken 214 ); 215 return; 216 } 217 } 218 } 219 highlighting.removeHighlight (); 220 } 221 222 private JEditorPane getCurrentEditor () { 223 Node [] ns = TopComponent.getRegistry ().getActivatedNodes (); 224 if (ns.length != 1) return null; 225 EditorCookie editorCookie = (EditorCookie) ns [0].getLookup (). 226 lookup (EditorCookie.class); 227 if (editorCookie == null) return null; 228 if (editorCookie.getOpenedPanes () == null) return null; 229 if (editorCookie.getOpenedPanes ().length < 1) return null; 230 return editorCookie.getOpenedPanes () [0]; 231 } 232 233 private AbstractDocument getCurrentDocument () { 234 Node [] ns = TopComponent.getRegistry ().getActivatedNodes (); 235 if (ns.length != 1) return null; 236 EditorCookie editorCookie = (EditorCookie) ns [0].getLookup (). 237 lookup (EditorCookie.class); 238 if (editorCookie == null) return null; 239 if (editorCookie.getOpenedPanes () == null) return null; 240 if (editorCookie.getOpenedPanes ().length < 1) return null; 241 JEditorPane pane = editorCookie.getOpenedPanes () [0]; 242 243 if (caretListener == null) 244 caretListener = new CListener (); 245 if (lastPane != null && lastPane != pane) { 246 lastPane.removeCaretListener (caretListener); 247 lastPane = null; 248 } 249 if (lastPane == null) { 250 pane.addCaretListener (caretListener); 251 lastPane = pane; 252 } 253 254 AbstractDocument doc = (AbstractDocument ) editorCookie.getDocument (); 255 if (documentListener == null) 256 documentListener = new CDocumentListener (); 257 if (lastDocument != null && lastDocument != doc) { 258 lastDocument.removeDocumentListener (documentListener); 259 lastDocument = null; 260 } 261 if (lastDocument == null) { 262 doc.addDocumentListener (documentListener); 263 lastDocument = doc; 264 } 265 return doc; 266 } 267 268 private RequestProcessor.Task task; 269 270 private void refreshLater () { 271 if (task != null) task.cancel (); 272 task = RequestProcessor.getDefault ().post ( 273 new Runnable () { 274 public void run () { 275 refresh (); 276 task = null; 277 } 278 }, 279 1000 280 ); 281 } 282 283 private void refresh () { 284 SwingUtilities.invokeLater(new Runnable () { 285 public void run() { 286 AbstractDocument doc = getCurrentDocument (); 287 TokenSequence ts = null; 288 if (doc != null) 289 try { 290 doc.readLock (); 291 TokenHierarchy tokenHierarchy = TokenHierarchy.get (doc); 292 if (tokenHierarchy == null) return; 293 ts = tokenHierarchy.tokenSequence (); 294 } finally { 295 doc.readUnlock (); 296 } 297 if (ts == null) 298 tree.setModel (new DefaultTreeModel (new DefaultMutableTreeNode ())); 299 else 300 tree.setModel (new DefaultTreeModel (new TSNode (null, ts, null, 0, 0))); 301 JEditorPane editor = getCurrentEditor (); 302 if (editor != null) { 303 int position = getCurrentEditor ().getCaret ().getDot (); 304 selectPath (position); 305 } 306 } 307 }); 308 } 309 310 private void selectPath (int offset) { 311 Object root = tree.getModel ().getRoot (); 312 if (!(root instanceof TSNode)) return; 313 listen = false; 314 TSNode n = (TSNode) root; 315 TreePath path = new TreePath (n); 316 path = findPath (path, offset); 317 tree.setSelectionPath (path); 318 tree.scrollPathToVisible (path); 319 listen = true; 320 } 321 322 private TreePath findPath (TreePath path, int offset) { 323 THNode parent = (THNode) path.getLastPathComponent (); 324 Enumeration en = parent.children (); 325 while (en.hasMoreElements ()) { 326 THNode n = (THNode) en.nextElement (); 327 if (n.getOffset () + n.getToken ().length () > offset) { 328 if (offset < n.getOffset ()) 329 return path; 330 if (n.isLeaf ()) 331 return new MPath (path, n); 332 return findPath (new MPath (path, n), offset); 333 } 334 } 335 return path; 336 } 337 338 339 341 static interface THNode extends TreeNode { 342 Token getToken (); 343 String getMimeType (); 344 int getOffset (); 345 int getIndex (); 346 } 347 348 static class MPath extends TreePath { 349 MPath (TreePath path, Object e) { 350 super (path, e); 351 } 352 } 353 354 static class TSNode implements THNode { 355 356 private TSNode parent; 357 private TokenSequence ts; 358 private Token token; 359 private int offset; 360 private int index; 361 362 TSNode (TSNode parent, TokenSequence ts, Token token, int offset, int index) { 363 this.parent = parent; 364 this.ts = ts; 365 this.token = token; 366 this.offset = offset; 367 this.index = index; 368 } 369 370 public TreeNode getChildAt (int index) { 371 ts.moveIndex (index); 372 ts.moveNext (); 373 TokenSequence ts2 = ts.embedded (); 374 if (ts2 != null) 375 return new TSNode (this, ts2, ts.token (), ts.offset (), ts.index ()); 376 return new TNode (this, ts.token (), getMimeType (), index, ts.offset ()); 377 } 378 379 public int getChildCount () { 380 return ts.tokenCount (); 381 } 382 383 public TreeNode getParent () { 384 return parent; 385 } 386 387 public String getMimeType () { 388 return ts.language ().mimeType (); 389 } 390 391 public int getIndex (TreeNode node) { 392 return ((THNode) node).getIndex (); 393 } 394 395 public boolean getAllowsChildren () { 396 return true; 397 } 398 399 public boolean isLeaf () { 400 return false; 401 } 402 403 public Enumeration children () { 404 return new Enumeration () { 405 private int i = 0; 406 407 public boolean hasMoreElements () { 408 return i < getChildCount (); 409 } 410 411 public Object nextElement() { 412 return getChildAt (i++); 413 } 414 }; 415 } 416 417 public Token getToken () { 418 return token; 419 } 420 421 public int getOffset () { 422 return offset; 423 } 424 425 public int getIndex () { 426 return index; 427 } 428 } 429 430 static class TNode implements THNode { 431 432 private TSNode parent; 433 private Token token; 434 private String mimeType; 435 private int index; 436 private int offset; 437 438 TNode (TSNode parent, Token token, String mimeType, int index, int offset) { 439 this.parent = parent; 440 this.token = token; 441 this.mimeType = mimeType; 442 this.index = index; 443 this.offset = offset; 444 } 445 446 public TreeNode getChildAt (int index) { 447 throw new UnsupportedOperationException (); 448 } 449 450 public int getChildCount () { 451 throw new UnsupportedOperationException (); 452 } 453 454 public TreeNode getParent() { 455 return parent; 456 } 457 458 public int getIndex (TreeNode node) { 459 throw new UnsupportedOperationException (); 460 } 461 462 public boolean getAllowsChildren () { 463 return false; 464 } 465 466 public boolean isLeaf () { 467 return true; 468 } 469 470 public Enumeration children () { 471 throw new UnsupportedOperationException (); 472 } 473 474 public Token getToken () { 475 return token; 476 } 477 478 public String getMimeType () { 479 return mimeType; 480 } 481 482 public int getOffset () { 483 return offset; 484 } 485 486 public int getIndex () { 487 return index; 488 } 489 } 490 491 class CDocumentListener implements DocumentListener { 492 public void insertUpdate (DocumentEvent e) { 493 refreshLater (); 494 } 495 496 public void removeUpdate (DocumentEvent e) { 497 refreshLater (); 498 } 499 500 public void changedUpdate (DocumentEvent e) { 501 refreshLater (); 502 } 503 } 504 505 class CListener implements CaretListener { 506 public void caretUpdate (CaretEvent e) { 507 int position = e.getDot (); 508 try { 509 selectPath (position); 510 } catch (ConcurrentModificationException ex) { 511 } 512 } 513 } 514 515 private static class Renderer extends DefaultTreeCellRenderer { 516 517 private String e (CharSequence t) { 518 StringBuilder sb = new StringBuilder (); 519 int i, k = t.length (); 520 for (i = 0; i < k; i++) { 521 if (t.charAt (i) == '\t') 522 sb.append ("\\t"); 523 else 524 if (t.charAt (i) == '\r') 525 sb.append ("\\r"); 526 else 527 if (t.charAt (i) == '\n') 528 sb.append ("\\n"); 529 else 530 sb.append (t.charAt (i)); 531 } 532 return sb.toString (); 533 } 534 535 public Component getTreeCellRendererComponent ( 536 JTree tree, 537 Object value, 538 boolean sel, 539 boolean expanded, 540 boolean leaf, 541 int row, 542 boolean hasFocus 543 ) { 544 if (!(value instanceof THNode)) 545 return super.getTreeCellRendererComponent ( 546 tree, value, sel, expanded, leaf, row, hasFocus 547 ); 548 THNode node = (THNode) value; 549 Token token = node.getToken (); 550 if (token == null) 551 return super.getTreeCellRendererComponent ( 552 tree, value, sel, expanded, leaf, row, hasFocus 553 ); 554 StringBuilder sb = new StringBuilder (). 555 append ('<'). 556 append (node.getOffset ()). 557 append (",\""). 558 append (token.id ().name ()). 559 append (",\""). 560 append (e (token.text ())). 561 append ("\">"); 562 return super.getTreeCellRendererComponent ( 563 tree, sb.toString (), sel, expanded, leaf, row, hasFocus 564 ); 565 } 566 } 567 568 final static class ResolvableHelper implements Serializable { 569 private static final long serialVersionUID = 1L; 570 public Object readResolve () { 571 return TokensBrowserTopComponent.getDefault (); 572 } 573 } 574 575 private static class Listener implements PropertyChangeListener { 576 577 private WeakReference component; 578 579 580 Listener (TokensBrowserTopComponent c) { 581 component = new WeakReference (c); 582 TopComponent.getRegistry ().addPropertyChangeListener (this); 583 } 584 585 TokensBrowserTopComponent getComponent () { 586 TokensBrowserTopComponent c = (TokensBrowserTopComponent) component.get (); 587 if (c != null) return c; 588 remove (); 589 return null; 590 } 591 592 void remove () { 593 TopComponent.getRegistry ().removePropertyChangeListener (this); 594 } 595 596 public void propertyChange (PropertyChangeEvent evt) { 597 TokensBrowserTopComponent c = getComponent (); 598 if (c == null) return; 599 c.refresh (); 600 } 601 } 602 } 603 | Popular Tags |