Change to openFileInTextEditor to fix ticket issue #650.
[phpeclipse.git] / net.sourceforge.phpeclipse.phpmanual / src / net / sourceforge / phpeclipse / phpmanual / views / PHPManualView.java
1 package net.sourceforge.phpeclipse.phpmanual.views;
2
3 import java.io.BufferedReader;
4 import java.io.FileNotFoundException;
5 import java.io.FileReader;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.net.URL;
9 import java.util.ArrayList;
10 import java.util.regex.Matcher;
11 import java.util.zip.ZipEntry;
12 import java.util.zip.ZipFile;
13
14 import net.sourceforge.phpdt.internal.ui.text.JavaWordFinder;
15 import net.sourceforge.phpdt.internal.ui.viewsupport.ISelectionListenerWithAST;
16 import net.sourceforge.phpdt.internal.ui.viewsupport.SelectionListenerWithASTManager;
17 import net.sourceforge.phpdt.phphelp.PHPHelpPlugin;
18 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
19 import net.sourceforge.phpeclipse.phpeditor.PHPEditor;
20 import net.sourceforge.phpeclipse.phpmanual.PHPManualUIPlugin;
21
22 import org.eclipse.core.runtime.Path;
23 import org.eclipse.core.runtime.Platform;
24 import org.eclipse.jface.text.IDocument;
25 import org.eclipse.jface.text.IRegion;
26 import org.eclipse.jface.text.ITextSelection;
27 import org.eclipse.jface.viewers.ISelection;
28 import org.eclipse.swt.SWT;
29 import org.eclipse.swt.widgets.Composite;
30 import org.eclipse.swt.widgets.Display;
31 import org.eclipse.swt.browser.Browser;
32 import org.eclipse.ui.IEditorPart;
33 import org.eclipse.ui.INullSelectionListener;
34 import org.eclipse.ui.ISelectionListener;
35 import org.eclipse.ui.IWorkbenchPart;
36 import org.eclipse.ui.part.ViewPart;
37 import org.htmlparser.Node;
38 import org.htmlparser.Parser;
39 import org.htmlparser.tags.Div;
40 import org.htmlparser.util.ParserException;
41 import org.htmlparser.visitors.TagFindingVisitor;
42 import org.osgi.framework.Bundle;
43
44 /**
45  * This ViewPart is the implementation of the idea of having the 
46  * PHP Manual easily accessible while coding. It shows the
47  * under-cursor function's reference inside a browser.
48  * <p>
49  * The view listens to selection changes both in the (1)workbench, to
50  * know when the user changes between the instances of the PHPEditor
51  * or when a new instance is created; and in the (2)PHPEditor, to know
52  * when the user changes the cursor position. This explains the need
53  * to implement both ISelectionListener and ISelectionListenerWithAST.
54  * <p>
55  * Up to now, the ViewPart show reference pages from HTML stored in the
56  * doc.zip file from the net.sourceforge.phpeclipse.phphelp plugin. It
57  * also depends on net.sourceforge.phpeclipse.phpmanual.htmlparser to
58  * parse these HTML files.
59  * <p>
60  * @author scorphus
61  */
62 public class PHPManualView extends ViewPart implements INullSelectionListener, ISelectionListenerWithAST {
63
64         /**
65          * The ViewPart's browser
66          */
67         private Browser browser;
68
69         /**
70          * A reference to store last active editor to know when we've
71          * got a new instance of the PHPEditor
72          */
73         private PHPEditor lastEditor;
74
75         /**
76          * String that stores the last selected word
77          */
78         private String lastOccurrence = null;
79
80         /**
81          * The path to the doc.zip file containing the PHP Manual
82          * in HTML format
83          */
84         private final Path docPath = new Path("doc.zip"); 
85
86         /**
87          * The constructor.
88          */
89         public PHPManualView() {
90         }
91
92         /**
93          * This method initializes the ViewPart. It instantiates components
94          * and add listeners
95          * 
96          * @param parent The parent control
97          */
98         public void createPartControl(Composite parent) {
99                 browser = new Browser(parent, SWT.NONE);
100                 parent.pack();
101                 if ((lastEditor = getJavaEditor()) != null) {
102                         SelectionListenerWithASTManager.getDefault().addListener(lastEditor, this);
103                 }
104                 getSite().getWorkbenchWindow().getSelectionService()
105                                 .addPostSelectionListener(PHPeclipsePlugin.EDITOR_ID, this);
106         }
107
108         /**
109          * Cleanup to remove the selection listener
110          */
111         public void dispose() {
112                 getSite().getWorkbenchWindow().getSelectionService()
113                                 .removePostSelectionListener(PHPeclipsePlugin.EDITOR_ID, this);
114         }
115
116         /**
117          * Passing the focus request to the viewer's control.
118          */
119         public void setFocus() {
120                 browser.setFocus();
121         }
122
123         /**
124          * Treats selection changes from the PHPEditor
125          */
126         public void selectionChanged(IEditorPart part, ITextSelection selection) {
127                 IDocument document = ((PHPEditor)part).getViewer().getDocument();
128                 int offset = selection.getOffset();
129                 IRegion iRegion = JavaWordFinder.findWord(document, offset);
130                 if (document != null && iRegion != null) {
131                         try {
132                                 final String wordStr = document.get(iRegion.getOffset(),
133                                                 iRegion.getLength());
134                                 if (!wordStr.equalsIgnoreCase(lastOccurrence)) {
135                                         showReference(wordStr);                         
136                                         lastOccurrence = wordStr;
137                                 }
138                         } catch (Exception e) {
139                                 e.printStackTrace();
140                         }
141                 }
142         }
143
144         /**
145          * Treats selection changes from the workbench. When part is new
146          * instance of PHPEditor it gets a listener attached
147          */
148         public void selectionChanged(IWorkbenchPart part, ISelection selection) {
149                 if (part != null && !((PHPEditor)part).equals(lastEditor)) {
150                         SelectionListenerWithASTManager.getDefault().addListener((PHPEditor)part, this);
151                         lastEditor = (PHPEditor)part;
152                 } else {
153                         System.out.println(part);
154                 }
155         }
156
157         /**
158          * Updates the browser with the reference page for a given function
159          * 
160          * @param funcName Function name
161          */
162         private void showReference(final String funcName) {
163                 new Thread(new Runnable() {
164                         public void run() {
165                                 Display.getDefault().asyncExec(new Runnable() {
166                                         public void run() {
167                                                 String html = getHtmlSource(funcName);
168                                                 browser.setText(html);
169                                         }
170                                 });
171                         }
172                 }).start();
173         }
174
175         /**
176          * Filters the function's reference page extracting only parts of it
177          * 
178          * @param source HTML source of the reference page
179          * @return HTML source of reference page
180          */
181         private String filterHtmlSource(String source) {
182                 try {
183                         Parser parser = new Parser(source);
184                         String [] tagsToBeFound = {"DIV"};
185                         ArrayList classList = new ArrayList(8);
186                         classList.add("refnamediv");
187                         classList.add("refsect1 description");
188                         classList.add("refsect1 parameters");
189                         classList.add("refsect1 returnvalues");
190                         classList.add("refsect1 examples");
191                         classList.add("refsect1 seealso");
192                         classList.add("refsect1 u");
193                         TagFindingVisitor visitor = new TagFindingVisitor(tagsToBeFound);
194                         parser.visitAllNodesWith(visitor);
195                         Node [] allPTags = visitor.getTags(0);
196                         StringBuffer output = new StringBuffer();
197                         for (int i = 0; i < allPTags.length; i++) {
198                                 String tagClass = ((Div)allPTags[i]).getAttribute("class");
199                                 if (classList.contains(tagClass)) {
200                                         output.append(allPTags[i].toHtml());
201                                 }
202                         }
203                         return output.toString().replaceAll("—", "-");
204                         //.replace("<h3 class=\"title\">Description</h3>", " ");
205                 } catch (ParserException e) {
206                         e.printStackTrace();
207                 }
208                 return "";
209         }
210
211         /**
212          * Reads the template that defines the style of the reference page
213          * shown inside the view's browser
214          * 
215          * @return HTML source of the template
216          */
217         public String getRefPageTemplate() {
218                 Bundle bundle = Platform.getBundle(PHPManualUIPlugin.PLUGIN_ID);
219                 URL fileURL = Platform.find(bundle, new Path("templates"));
220                 StringBuffer contents = new StringBuffer();
221                 BufferedReader input = null;
222                 try {
223                         URL resolve = Platform.resolve(fileURL);
224                         input = new BufferedReader(new FileReader(resolve.getPath()+"/refpage.html"));
225                         String line = null;
226                         while ((line = input.readLine()) != null){
227                                 contents.append(line);
228                         }
229                 }
230                 catch (FileNotFoundException e) {
231                         e.printStackTrace();
232                 } catch (IOException e) {
233                         e.printStackTrace();
234                 }
235                 finally {
236                         try {
237                                 if (input!= null) {
238                                         input.close();
239                                 }
240                         }
241                         catch (IOException ex) {
242                                 ex.printStackTrace();
243                         }
244                 }
245                 return contents.toString();
246         }
247
248         /**
249          * Replaces each substring of source string that matches the
250          * given pattern string with the given replace string
251          * 
252          * @param source The source string
253          * @param pattern The pattern string
254          * @param replace The replace string
255          * @return The resulting String
256          */
257         public static String replace(String source, String pattern, String replace) {
258                 if (source != null) {
259                         final int len = pattern.length();
260                         StringBuffer sb = new StringBuffer();
261                         int found = -1;
262                         int start = 0;
263                         while ((found = source.indexOf(pattern, start)) != -1) {
264                                 sb.append(source.substring(start, found));
265                                 sb.append(replace);
266                                 start = found + len;
267                         }
268                         sb.append(source.substring(start));
269                         return sb.toString();
270                 } else {
271                         return "";
272                 }
273         }
274
275         /**
276          * Looks for the function's reference page inside the doc.zip file and
277          * returns a filtered HTML source of it embedded in the template
278          * 
279          * @param funcName
280          *            Function name
281          * @return HTML source of reference page
282          */
283         public String getHtmlSource(String funcName) {
284                 Bundle bundle = Platform.getBundle(PHPHelpPlugin.PLUGIN_ID);
285                 URL fileURL = Platform.find(bundle, docPath);
286                 byte[] b = null;
287                 try {
288                         URL resolve = Platform.resolve(fileURL);
289                         ZipFile docFile = new ZipFile(resolve.getPath());
290                         ZipEntry entry = docFile.getEntry("doc/function."+funcName.replace('_', '-')+".html");
291                         InputStream ref = docFile.getInputStream(entry);
292                         b = new byte[(int)entry.getSize()];
293                         ref.read(b, 0, (int)entry.getSize());
294                         if (b != null) {
295                                 String reference = filterHtmlSource(new String(b));
296                                 String refPageTpl = getRefPageTemplate();
297                                 refPageTpl = refPageTpl.replaceAll("%title%", funcName);
298                                 refPageTpl = replace(refPageTpl, "%reference%", reference);
299                                 return refPageTpl;
300                         }
301                 } catch (IOException e) {
302                         return "<html></html>";
303                 } catch (Exception e) {
304                         return null;
305                 }
306                 return "<html></html>";
307         }
308
309         /**
310          * Returns the currently active java editor, or <code>null</code> if it
311          * cannot be determined.
312          * 
313          * @return the currently active java editor, or <code>null</code>
314          */
315         private PHPEditor getJavaEditor() {
316                 try {
317                         IEditorPart part = PHPeclipsePlugin.getActivePage().getActiveEditor();
318                         if (part instanceof PHPEditor)
319                                 return (PHPEditor) part;
320                         else
321                                 return null;
322                 } catch (Exception e) {
323                         return null;
324                 }
325         }
326
327 }