Added PHPUnitEditor and corresponding PHPPreferencePage
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / PHPEditor.java
1 package net.sourceforge.phpeclipse.phpeditor;
2
3 /**********************************************************************
4 Copyright (c) 2000, 2002 IBM Corp. and others.
5 All rights reserved. This program and the accompanying materials
6 are made available under the terms of the Common Public License v1.0
7 which accompanies this distribution, and is available at
8 http://www.eclipse.org/legal/cpl-v10.html
9
10 Contributors:
11     IBM Corporation - Initial implementation
12     Klaus Hartlage - www.eclipseproject.de
13 **********************************************************************/
14 import java.util.ArrayList;
15 import java.util.List;
16
17 import net.sourceforge.phpdt.internal.ui.actions.CompositeActionGroup;
18 import net.sourceforge.phpdt.internal.ui.text.HTMLTextPresenter;
19 import net.sourceforge.phpdt.internal.ui.text.JavaColorManager;
20 import net.sourceforge.phpdt.internal.ui.text.PHPPairMatcher;
21 import net.sourceforge.phpdt.internal.ui.viewsupport.IViewPartInputProvider;
22 import net.sourceforge.phpdt.ui.actions.GenerateActionGroup;
23 import net.sourceforge.phpdt.ui.actions.GotoMatchingBracketAction;
24 import net.sourceforge.phpdt.ui.text.JavaTextTools;
25 import net.sourceforge.phpeclipse.IPreferenceConstants;
26 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
27 import net.sourceforge.phpeclipse.phpeditor.php.IPHPPartitionScannerConstants;
28 import net.sourceforge.phpeclipse.phpeditor.php.PHPCodeScanner;
29
30 import org.eclipse.core.resources.IResource;
31 import org.eclipse.core.runtime.CoreException;
32 import org.eclipse.core.runtime.IProgressMonitor;
33 import org.eclipse.jface.action.Action;
34 import org.eclipse.jface.action.IAction;
35 import org.eclipse.jface.action.MenuManager;
36 import org.eclipse.jface.preference.IPreferenceStore;
37 import org.eclipse.jface.preference.PreferenceConverter;
38 import org.eclipse.jface.text.BadLocationException;
39 import org.eclipse.jface.text.DefaultInformationControl;
40 import org.eclipse.jface.text.IDocument;
41 import org.eclipse.jface.text.IInformationControl;
42 import org.eclipse.jface.text.IInformationControlCreator;
43 import org.eclipse.jface.text.IRegion;
44 import org.eclipse.jface.text.ITextOperationTarget;
45 import org.eclipse.jface.text.ITextViewer;
46 import org.eclipse.jface.text.ITextViewerExtension3;
47 import org.eclipse.jface.text.ITypedRegion;
48 import org.eclipse.jface.text.Region;
49 import org.eclipse.jface.text.information.InformationPresenter;
50 import org.eclipse.jface.text.source.AnnotationRulerColumn;
51 import org.eclipse.jface.text.source.CompositeRuler;
52 import org.eclipse.jface.text.source.ISourceViewer;
53 import org.eclipse.jface.text.source.IVerticalRuler;
54 import org.eclipse.jface.text.source.IVerticalRulerColumn;
55 import org.eclipse.jface.text.source.LineNumberRulerColumn;
56 import org.eclipse.jface.util.IPropertyChangeListener;
57 import org.eclipse.jface.util.PropertyChangeEvent;
58 import org.eclipse.swt.SWT;
59 import org.eclipse.swt.custom.BidiSegmentEvent;
60 import org.eclipse.swt.custom.BidiSegmentListener;
61 import org.eclipse.swt.custom.StyledText;
62 import org.eclipse.swt.graphics.Point;
63 import org.eclipse.swt.graphics.RGB;
64 import org.eclipse.swt.widgets.Composite;
65 import org.eclipse.swt.widgets.Shell;
66 import org.eclipse.ui.IEditorInput;
67 import org.eclipse.ui.actions.ActionContext;
68 import org.eclipse.ui.actions.ActionGroup;
69 import org.eclipse.ui.texteditor.ContentAssistAction;
70 import org.eclipse.ui.texteditor.DefaultRangeIndicator;
71 import org.eclipse.ui.texteditor.IDocumentProvider;
72 import org.eclipse.ui.texteditor.IEditorStatusLine;
73 import org.eclipse.ui.texteditor.ITextEditorActionConstants;
74 import org.eclipse.ui.texteditor.StatusTextEditor;
75 import org.eclipse.ui.texteditor.TextOperationAction;
76 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
77 /**
78  * PHP specific text editor.
79  */
80 public class PHPEditor
81   extends StatusTextEditor
82   implements IViewPartInputProvider { // extends TextEditor {
83
84   // protected PHPActionGroup fActionGroups;
85   /** The outline page */
86   private PHPContentOutlinePage fOutlinePage;
87
88   //  protected PHPSyntaxParserThread fValidationThread = null;
89
90   // private IPreferenceStore fPHPPrefStore;
91
92   /** The editor's bracket matcher */
93   private PHPPairMatcher fBracketMatcher;
94   /** The line number ruler column */
95   private LineNumberRulerColumn fLineNumberRulerColumn;
96
97   protected CompositeActionGroup fActionGroups;
98   /** The standard action groups added to the menu */
99   private GenerateActionGroup fGenerateActionGroup;
100   private CompositeActionGroup fContextMenuGroup;
101
102   /** The information presenter. */
103   private InformationPresenter fInformationPresenter;
104
105   /**
106    * Default constructor.
107    */
108   public PHPEditor() {
109     super();
110     JavaTextTools textTools = PHPeclipsePlugin.getDefault().getJavaTextTools();
111     setSourceViewerConfiguration(
112       new PHPSourceViewerConfiguration(textTools, this));
113     setRangeIndicator(new DefaultRangeIndicator());
114     setPreferenceStore(PHPeclipsePlugin.getDefault().getPreferenceStore());
115
116     //    if (PreferenceConstants.getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SYNC_OUTLINE_ON_CURSOR_MOVE))
117     //      fUpdater= new OutlinePageSelectionUpdater();
118
119     initializeEditor();
120   }
121   //
122   //    /**
123   //     * @see IMember#getCompilationUnit()
124   //     */
125   //    public ICompilationUnit getCompilationUnit() {
126   //            return this; 
127   //    }
128   //    /**
129   //     * @see org.phpeclipse.phpdt.internal.compiler.env.ICompilationUnit#getContents()
130   //     */
131   //    public char[] getContents() {
132   //            IDocument doc = this.getDocumentProvider().getDocument(this.getEditorInput());
133   //    
134   //            return doc.get().toCharArray();
135   //    }
136
137   /*
138    * @see net.sourceforge.phpdt.internal.ui.viewsupport.IViewPartInputProvider#getViewPartInput()
139    */
140   public Object getViewPartInput() {
141     return getEditorInput().getAdapter(IResource.class);
142   }
143
144   /*
145    * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.
146    * widgets.Composite)
147    */
148   public void createPartControl(Composite parent) {
149     super.createPartControl(parent);
150
151     IInformationControlCreator informationControlCreator =
152       new IInformationControlCreator() {
153       public IInformationControl createInformationControl(Shell parent) {
154         boolean cutDown = false;
155         int style = cutDown ? SWT.NONE : (SWT.V_SCROLL | SWT.H_SCROLL);
156         return new DefaultInformationControl(
157           parent,
158           SWT.RESIZE,
159           style,
160           new HTMLTextPresenter(cutDown));
161       }
162     };
163
164     fInformationPresenter = new InformationPresenter(informationControlCreator);
165     fInformationPresenter.setSizeConstraints(60, 10, true, true);
166     fInformationPresenter.install(getSourceViewer());
167   }
168
169   /**
170    * Returns this document's complete text.
171    *
172    * @return the document's complete text
173    */
174   public String get() {
175     IDocument doc =
176       this.getDocumentProvider().getDocument(this.getEditorInput());
177     return doc.get();
178   }
179
180   /**
181    *  Returns the standard action group of this editor.
182    */
183   protected ActionGroup getActionGroup() {
184     return fActionGroups;
185   }
186
187   public PHPContentOutlinePage getfOutlinePage() {
188     return fOutlinePage;
189   }
190
191   /** The <code>PHPEditor</code> implementation of this 
192    * <code>AbstractTextEditor</code> method extend the 
193    * actions to add those specific to the receiver
194    */
195   protected void createActions() {
196     super.createActions();
197
198     Action action;
199     //    setAction(
200     //      "ContentAssistProposal",
201     //      new TextOperationAction(
202     //        PHPEditorMessages.getResourceBundle(),
203     //        "ContentAssistProposal.",
204     //        this,
205     //        ISourceViewer.CONTENTASSIST_PROPOSALS));
206     action = new ContentAssistAction(PHPEditorMessages.getResourceBundle(), "ContentAssistProposal.", this); //$NON-NLS-1$
207     action.setActionDefinitionId(
208       PHPEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
209     setAction("ContentAssistProposal", action); //$NON-NLS-1$
210
211     setAction(
212       "ContentAssistTip",
213       new TextOperationAction(
214         PHPEditorMessages.getResourceBundle(),
215         "ContentAssistTip.",
216         this,
217         ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION));
218
219     action =
220       new TextOperationAction(
221         PHPEditorMessages.getResourceBundle(),
222         "Comment.",
223         this,
224         ITextOperationTarget.PREFIX);
225     action.setActionDefinitionId(PHPEditorActionDefinitionIds.COMMENT);
226     setAction("Comment", action);
227
228     action =
229       new TextOperationAction(
230         PHPEditorMessages.getResourceBundle(),
231         "Uncomment.",
232         this,
233         ITextOperationTarget.STRIP_PREFIX);
234     action.setActionDefinitionId(PHPEditorActionDefinitionIds.UNCOMMENT);
235     setAction("Uncomment", action);
236
237     action = new TextOperationAction(PHPEditorMessages.getResourceBundle(), "Format.", this, ISourceViewer.FORMAT); //$NON-NLS-1$
238     action.setActionDefinitionId(PHPEditorActionDefinitionIds.FORMAT);
239     setAction("Format", action); //$NON-NLS-1$
240
241     markAsStateDependentAction("ContentAssistProposal", true); //$NON-NLS-1$
242     markAsStateDependentAction("Comment", true); //$NON-NLS-1$
243     markAsStateDependentAction("Uncomment", true); //$NON-NLS-1$
244     markAsStateDependentAction("Format", true); //$NON-NLS-1$
245
246     action = new GotoMatchingBracketAction(this);
247     action.setActionDefinitionId(
248       PHPEditorActionDefinitionIds.GOTO_MATCHING_BRACKET);
249     setAction(GotoMatchingBracketAction.GOTO_MATCHING_BRACKET, action);
250
251     fGenerateActionGroup =
252       new GenerateActionGroup(this, ITextEditorActionConstants.GROUP_EDIT);
253
254     fActionGroups =
255       new CompositeActionGroup(new ActionGroup[] { fGenerateActionGroup });
256
257     // We have to keep the context menu group separate to have better control over positioning
258     fContextMenuGroup =
259       new CompositeActionGroup(new ActionGroup[] { fGenerateActionGroup });
260     //      rg, 
261     //      new LocalHistoryActionGroup(this, ITextEditorActionConstants.GROUP_EDIT)});
262
263     //    if (fValidationThread == null) {
264     //      fValidationThread =
265     //        new PHPSyntaxParserThread(this, getSourceViewer());
266     //      //Thread defaults
267     //
268     //      fValidationThread.start();
269     //    }
270     //
271     //    fValidationThread.setText(getSourceViewer().getTextWidget().getText());
272   }
273
274   /** The <code>PHPEditor</code> implementation of this 
275    * <code>AbstractTextEditor</code> method performs any extra 
276    * disposal actions required by the php editor.
277    */
278   public void dispose() {
279     PHPEditorEnvironment.disconnect(this);
280     if (fOutlinePage != null)
281       fOutlinePage.setInput(null);
282
283     if (fActionGroups != null)
284       fActionGroups.dispose();
285
286     super.dispose();
287   }
288
289   /** The <code>PHPEditor</code> implementation of this 
290    * <code>AbstractTextEditor</code> method performs any extra 
291    * revert behavior required by the php editor.
292    */
293   public void doRevertToSaved() {
294     super.doRevertToSaved();
295     if (fOutlinePage != null)
296       fOutlinePage.update();
297   }
298
299   /** The <code>PHPEditor</code> implementation of this 
300    * <code>AbstractTextEditor</code> method performs any extra 
301    * save behavior required by the php editor.
302    */
303   public void doSave(IProgressMonitor monitor) {
304     super.doSave(monitor);
305     // compile or not, according to the user preferences
306     IPreferenceStore store = getPreferenceStore(); // fPHPPrefStore;
307     if (store.getBoolean(PHPeclipsePlugin.PHP_PARSE_ON_SAVE)) {
308       IAction a = PHPParserAction.getInstance();
309       if (a != null)
310         a.run();
311     }
312     if (fOutlinePage != null)
313       fOutlinePage.update();
314   }
315
316   /** The <code>PHPEditor</code> implementation of this 
317    * <code>AbstractTextEditor</code> method performs any extra 
318    * save as behavior required by the php editor.
319    */
320   public void doSaveAs() {
321     super.doSaveAs();
322     if (fOutlinePage != null)
323       fOutlinePage.update();
324   }
325
326   /** The <code>PHPEditor</code> implementation of this 
327    * <code>AbstractTextEditor</code> method performs sets the 
328    * input of the outline page after AbstractTextEditor has set input.
329    */
330   protected void doSetInput(IEditorInput input) throws CoreException {
331     super.doSetInput(input);
332     if (fOutlinePage != null)
333       fOutlinePage.setInput(input);
334   }
335
336   /*
337    * @see org.phpeclipse.phpdt.internal.ui.viewsupport.IViewPartInputProvider#getViewPartInput()
338    */
339   //  public Object getViewPartInput() {
340   //    return getEditorInput().getAdapter(IFile.class);
341   //  }
342
343   /** The <code>PHPEditor</code> implementation of this 
344    * <code>AbstractTextEditor</code> method adds any 
345    * PHPEditor specific entries.
346    */
347   public void editorContextMenuAboutToShow(MenuManager menu) {
348     super.editorContextMenuAboutToShow(menu);
349
350     addAction(menu, ITextEditorActionConstants.GROUP_EDIT, "Format"); //$NON-NLS-1$
351
352     ActionContext context =
353       new ActionContext(getSelectionProvider().getSelection());
354     fContextMenuGroup.setContext(context);
355     fContextMenuGroup.fillContextMenu(menu);
356     fContextMenuGroup.setContext(null);
357   }
358
359   protected void updateStateDependentActions() {
360     super.updateStateDependentActions();
361     fGenerateActionGroup.editorStateChanged();
362   }
363
364   /** The <code>PHPEditor</code> implementation of this 
365    * <code>AbstractTextEditor</code> method performs gets
366    * the java content outline page if request is for a an 
367    * outline page.
368    */
369   public Object getAdapter(Class required) {
370     if (IContentOutlinePage.class.equals(required)) {
371       if (fOutlinePage == null) {
372         fOutlinePage = new PHPContentOutlinePage(getDocumentProvider(), this);
373         if (getEditorInput() != null)
374           fOutlinePage.setInput(getEditorInput());
375       }
376       return fOutlinePage;
377     }
378     return super.getAdapter(required);
379   }
380
381   //  public void openContextHelp() {
382   //    IDocument doc = this.getDocumentProvider().getDocument(this.getEditorInput());
383   //    ITextSelection selection = (ITextSelection) this.getSelectionProvider().getSelection();
384   //    int pos = selection.getOffset();
385   //    String word = getFunctionName(doc, pos);
386   //    openContextHelp(word);
387   //  }
388   //
389   //  private void openContextHelp(String word) {
390   //    open(word);
391   //  }
392   //
393   //  public static void open(String word) {
394   //    IHelp help = WorkbenchHelp.getHelpSupport();
395   //    if (help != null) {
396   //      IHelpResource helpResource = new PHPFunctionHelpResource(word);
397   //      WorkbenchHelp.getHelpSupport().displayHelpResource(helpResource);
398   //    } else {
399   //      //   showMessage(shell, dialogTitle, ActionMessages.getString("Open help not available"), false); //$NON-NLS-1$
400   //    }
401   //  }
402
403   //    private String getFunctionName(IDocument doc, int pos) {
404   //            Point word = PHPWordExtractor.findWord(doc, pos);
405   //            if (word != null) {
406   //                    try {
407   //                            return doc.get(word.x, word.y).replace('_', '-');
408   //                    } catch (BadLocationException e) {
409   //                    }
410   //            }
411   //            return "";
412   //    }
413
414   /*
415      * @see AbstractTextEditor#handlePreferenceStoreChanged(PropertyChangeEvent)
416      */
417   protected void handlePreferenceStoreChanged(PropertyChangeEvent event) {
418
419     try {
420
421       ISourceViewer sourceViewer = getSourceViewer();
422       if (sourceViewer == null)
423         return;
424
425       String property = event.getProperty();
426
427       //      if (JavaSourceViewerConfiguration.PREFERENCE_TAB_WIDTH.equals(property)) {
428       //        Object value= event.getNewValue();
429       //        if (value instanceof Integer) {
430       //          sourceViewer.getTextWidget().setTabs(((Integer) value).intValue());
431       //        } else if (value instanceof String) {
432       //          sourceViewer.getTextWidget().setTabs(Integer.parseInt((String) value));
433       //        }
434       //        return;
435       //      }
436
437       if (IPreferenceConstants.LINE_NUMBER_RULER.equals(property)) {
438         if (isLineNumberRulerVisible())
439           showLineNumberRuler();
440         else
441           hideLineNumberRuler();
442         return;
443       }
444
445       if (fLineNumberRulerColumn != null
446         && (IPreferenceConstants.LINE_NUMBER_COLOR.equals(property)
447           || PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT.equals(property)
448           || PREFERENCE_COLOR_BACKGROUND.equals(property))) {
449
450         initializeLineNumberRulerColumn(fLineNumberRulerColumn);
451       }
452
453     } finally {
454       super.handlePreferenceStoreChanged(event);
455     }
456   }
457   /**
458    * Shows the line number ruler column.
459    */
460   private void showLineNumberRuler() {
461     IVerticalRuler v = getVerticalRuler();
462     if (v instanceof CompositeRuler) {
463       CompositeRuler c = (CompositeRuler) v;
464       c.addDecorator(1, createLineNumberRulerColumn());
465     }
466   }
467
468   /**
469   * Return whether the line number ruler column should be 
470   * visible according to the preference store settings.
471   * @return <code>true</code> if the line numbers should be visible
472   */
473   private boolean isLineNumberRulerVisible() {
474     // IPreferenceStore store= getPreferenceStore();
475     return getPreferenceStore().getBoolean(
476       IPreferenceConstants.LINE_NUMBER_RULER);
477   }
478   /**
479    * Hides the line number ruler column.
480    */
481   private void hideLineNumberRuler() {
482     IVerticalRuler v = getVerticalRuler();
483     if (v instanceof CompositeRuler) {
484       CompositeRuler c = (CompositeRuler) v;
485       try {
486         c.removeDecorator(1);
487       } catch (Throwable e) {
488       }
489     }
490   }
491
492   /**
493   * Initializes the given line number ruler column from the preference store.
494   * @param rulerColumn the ruler column to be initialized
495   */
496   protected void initializeLineNumberRulerColumn(LineNumberRulerColumn rulerColumn) {
497     JavaTextTools textTools = PHPeclipsePlugin.getDefault().getJavaTextTools();
498     // PHPColorProvider manager = PHPEditorEnvironment.getPHPColorProvider();
499     JavaColorManager manager = textTools.getColorManager();
500
501     IPreferenceStore store = getPreferenceStore();
502     if (store != null) {
503
504       RGB rgb = null;
505       // foreground color
506       if (store.contains(IPreferenceConstants.LINE_NUMBER_COLOR)) {
507         if (store.isDefault(IPreferenceConstants.LINE_NUMBER_COLOR))
508           rgb =
509             PreferenceConverter.getDefaultColor(
510               store,
511               IPreferenceConstants.LINE_NUMBER_COLOR);
512         else
513           rgb =
514             PreferenceConverter.getColor(
515               store,
516               IPreferenceConstants.LINE_NUMBER_COLOR);
517       }
518       rulerColumn.setForeground(manager.getColor(rgb));
519
520       rgb = null;
521       // background color
522       if (!store
523         .getBoolean(
524           IPreferenceConstants.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)) {
525         if (store.contains(IPreferenceConstants.PREFERENCE_COLOR_BACKGROUND)) {
526           if (store
527             .isDefault(IPreferenceConstants.PREFERENCE_COLOR_BACKGROUND))
528             rgb =
529               PreferenceConverter.getDefaultColor(
530                 store,
531                 IPreferenceConstants.PREFERENCE_COLOR_BACKGROUND);
532           else
533             rgb =
534               PreferenceConverter.getColor(
535                 store,
536                 IPreferenceConstants.PREFERENCE_COLOR_BACKGROUND);
537         }
538         rulerColumn.setBackground(manager.getColor(rgb));
539       }
540
541     }
542   }
543
544   /**
545    * Creates a new line number ruler column that is appropriately initialized.
546    */
547   protected IVerticalRulerColumn createLineNumberRulerColumn() {
548     fLineNumberRulerColumn = new LineNumberRulerColumn();
549     initializeLineNumberRulerColumn(fLineNumberRulerColumn);
550     return fLineNumberRulerColumn;
551   }
552
553   /*
554    * @see AbstractTextEditor#createVerticalRuler()
555    */
556   protected IVerticalRuler createVerticalRuler() {
557     CompositeRuler ruler = new CompositeRuler();
558     ruler.addDecorator(0, new AnnotationRulerColumn(VERTICAL_RULER_WIDTH));
559     if (isLineNumberRulerVisible())
560       ruler.addDecorator(1, createLineNumberRulerColumn());
561     return ruler;
562   }
563
564   /* (non-Javadoc)
565    * Method declared on TextEditor
566    */
567   protected void initializeEditor() {
568     IPreferenceStore store = PHPeclipsePlugin.getDefault().getPreferenceStore();
569     PHPEditorEnvironment.connect(this);
570
571     setEditorContextMenuId("#PHPEditorContext"); //$NON-NLS-1$
572     setRulerContextMenuId("#PHPRulerContext"); //$NON-NLS-1$
573     // setDocumentProvider(PHPeclipsePlugin.getCompilationUnitDocumentProvider());
574
575     store.addPropertyChangeListener(new IPropertyChangeListener() {
576       public void propertyChange(PropertyChangeEvent event) {
577         PHPCodeScanner scanner = PHPEditorEnvironment.getPHPCodeScanner();
578         if (scanner != null) {
579           scanner.updateToken(PHPEditorEnvironment.getPHPColorProvider());
580         }
581         if (getSourceViewer() != null) {
582           getSourceViewer().invalidateTextPresentation();
583         }
584
585         String property = event.getProperty();
586         if (IPreferenceConstants.LINE_NUMBER_RULER.equals(property)) {
587           if (isLineNumberRulerVisible())
588             showLineNumberRuler();
589           else
590             hideLineNumberRuler();
591           return;
592         }
593       }
594     });
595   }
596
597   private static IRegion getSignedSelection(ITextViewer viewer) {
598
599     StyledText text = viewer.getTextWidget();
600     int caretOffset = text.getCaretOffset();
601     Point selection = text.getSelection();
602
603     // caret left
604     int offset, length;
605     if (caretOffset == selection.x) {
606       offset = selection.y;
607       length = selection.x - selection.y;
608
609       // caret right
610     } else {
611       offset = selection.x;
612       length = selection.y - selection.x;
613     }
614
615     return new Region(offset, length);
616   }
617
618   private final static char[] BRACKETS = { '{', '}', '(', ')', '[', ']' };
619
620   private static boolean isBracket(char character) {
621     for (int i = 0; i != BRACKETS.length; ++i)
622       if (character == BRACKETS[i])
623         return true;
624     return false;
625   }
626
627   private static boolean isSurroundedByBrackets(
628     IDocument document,
629     int offset) {
630     if (offset == 0 || offset == document.getLength())
631       return false;
632
633     try {
634       return isBracket(document.getChar(offset - 1))
635         && isBracket(document.getChar(offset));
636
637     } catch (BadLocationException e) {
638       return false;
639     }
640   }
641   /**
642     * Jumps to the matching bracket.
643     */
644   public void gotoMatchingBracket() {
645
646     if (fBracketMatcher == null)
647       fBracketMatcher = new PHPPairMatcher(BRACKETS);
648
649     ISourceViewer sourceViewer = getSourceViewer();
650     IDocument document = sourceViewer.getDocument();
651     if (document == null)
652       return;
653
654     IRegion selection = getSignedSelection(sourceViewer);
655
656     int selectionLength = Math.abs(selection.getLength());
657     if (selectionLength > 1) {
658       setStatusLineErrorMessage(PHPEditorMessages.getString("GotoMatchingBracket.error.invalidSelection")); //$NON-NLS-1$               
659       sourceViewer.getTextWidget().getDisplay().beep();
660       return;
661     }
662
663     // #26314
664     int sourceCaretOffset = selection.getOffset() + selection.getLength();
665     if (isSurroundedByBrackets(document, sourceCaretOffset))
666       sourceCaretOffset -= selection.getLength();
667
668     IRegion region = fBracketMatcher.match(document, sourceCaretOffset);
669     if (region == null) {
670       setStatusLineErrorMessage(PHPEditorMessages.getString("GotoMatchingBracket.error.noMatchingBracket")); //$NON-NLS-1$              
671       sourceViewer.getTextWidget().getDisplay().beep();
672       return;
673     }
674
675     int offset = region.getOffset();
676     int length = region.getLength();
677
678     if (length < 1)
679       return;
680
681     int anchor = fBracketMatcher.getAnchor();
682     int targetOffset =
683       (PHPPairMatcher.RIGHT == anchor) ? offset : offset + length - 1;
684
685     boolean visible = false;
686     if (sourceViewer instanceof ITextViewerExtension3) {
687       ITextViewerExtension3 extension = (ITextViewerExtension3) sourceViewer;
688       visible = (extension.modelOffset2WidgetOffset(targetOffset) > -1);
689     } else {
690       IRegion visibleRegion = sourceViewer.getVisibleRegion();
691       visible =
692         (targetOffset >= visibleRegion.getOffset()
693           && targetOffset < visibleRegion.getOffset() + visibleRegion.getLength());
694     }
695
696     if (!visible) {
697       setStatusLineErrorMessage(PHPEditorMessages.getString("GotoMatchingBracket.error.bracketOutsideSelectedElement")); //$NON-NLS-1$          
698       sourceViewer.getTextWidget().getDisplay().beep();
699       return;
700     }
701
702     if (selection.getLength() < 0)
703       targetOffset -= selection.getLength();
704
705     sourceViewer.setSelectedRange(targetOffset, selection.getLength());
706     sourceViewer.revealRange(targetOffset, selection.getLength());
707   }
708   /**
709      * Ses the given message as error message to this editor's status line.
710      * @param msg message to be set
711      */
712   protected void setStatusLineErrorMessage(String msg) {
713     IEditorStatusLine statusLine =
714       (IEditorStatusLine) getAdapter(IEditorStatusLine.class);
715     if (statusLine != null)
716       statusLine.setMessage(true, msg, null);
717   }
718
719   /**
720      * Returns a segmentation of the line of the given document appropriate for bidi rendering.
721      * The default implementation returns only the string literals of a php code line as segments.
722      * 
723      * @param document the document
724      * @param lineOffset the offset of the line
725      * @return the line's bidi segmentation
726      * @throws BadLocationException in case lineOffset is not valid in document
727      */
728     public static int[] getBidiLineSegments(IDocument document, int lineOffset) throws BadLocationException {
729         
730       IRegion line= document.getLineInformationOfOffset(lineOffset);
731       ITypedRegion[] linePartitioning= document.computePartitioning(lineOffset, line.getLength());
732                 
733       List segmentation= new ArrayList();
734       for (int i= 0; i < linePartitioning.length; i++) {
735         if (IPHPPartitionScannerConstants.PHP_STRING.equals(linePartitioning[i].getType()))
736           segmentation.add(linePartitioning[i]);
737       }
738                 
739                 
740       if (segmentation.size() == 0) 
741         return null;
742                         
743       int size= segmentation.size();
744       int[] segments= new int[size * 2 + 1];
745                 
746       int j= 0;
747       for (int i= 0; i < size; i++) {
748         ITypedRegion segment= (ITypedRegion) segmentation.get(i);
749                         
750         if (i == 0)
751           segments[j++]= 0;
752                                 
753         int offset= segment.getOffset() - lineOffset;
754         if (offset > segments[j - 1])
755           segments[j++]= offset;
756                                 
757         if (offset + segment.getLength() >= line.getLength())
758           break;
759                                 
760         segments[j++]= offset + segment.getLength();
761       }
762                 
763       if (j < segments.length) {
764         int[] result= new int[j];
765         System.arraycopy(segments, 0, result, 0, j);
766         segments= result;
767       }
768                 
769       return segments;
770     }
771   /**
772      * Returns a segmentation of the given line appropriate for bidi rendering. The default
773      * implementation returns only the string literals of a php code line as segments.
774      * 
775      * @param lineOffset the offset of the line
776      * @param line the content of the line
777      * @return the line's bidi segmentation
778      */
779   protected int[] getBidiLineSegments(int lineOffset, String line) {
780     IDocumentProvider provider = getDocumentProvider();
781     if (provider != null && line != null && line.length() > 0) {
782       IDocument document = provider.getDocument(getEditorInput());
783       if (document != null)
784         try {
785           return getBidiLineSegments(document, lineOffset);
786         } catch (BadLocationException x) {
787           // ignore
788         }
789     }
790     return null;
791   }
792
793   /*
794    * @see AbstractTextEditor#createSourceViewer(Composite, IVerticalRuler, int)
795    */
796   protected final ISourceViewer createSourceViewer(
797     Composite parent,
798     IVerticalRuler ruler,
799     int styles) {
800     ISourceViewer viewer = createJavaSourceViewer(parent, ruler, styles);
801     StyledText text = viewer.getTextWidget();
802     text.addBidiSegmentListener(new BidiSegmentListener() {
803       public void lineGetSegments(BidiSegmentEvent event) {
804         event.segments = getBidiLineSegments(event.lineOffset, event.lineText);
805       }
806     });
807     //   JavaUIHelp.setHelp(this, text, IJavaHelpContextIds.JAVA_EDITOR);
808     return viewer;
809   }
810
811   /*
812    * @see AbstractTextEditor#createSourceViewer(Composite, IVerticalRuler, int)
813    */
814   protected ISourceViewer createJavaSourceViewer(
815     Composite parent,
816     IVerticalRuler ruler,
817     int styles) {
818     return super.createSourceViewer(parent, ruler, styles);
819   }
820 }