From 8c089928c09150a738ce70947eed09e1270f1b6c Mon Sep 17 00:00:00 2001 From: "yser.lee" Date: Thu, 17 May 2012 20:08:50 +0900 Subject: [PATCH] [Title] Add Dynamic Help View and Code Assist feature. [Type] Feature [Module] bada IDE [Priority] Normal [CQ#] [Redmine#] [Problem] [Cause] [Solution] [TestCase] --- .../c/hover/CDocBrowserInformationControl.java | 596 ++++++++ .../hover/CDocBrowserInformationControlInput.java | 66 + .../cdt/internal/ui/text/c/hover/CDocHover.java | 1479 +++++++++++++++++++- .../internal/ui/text/c/hover/CElementLinks.java | 100 ++ .../internal/ui/text/c/hover/CHoverMessages.java | 3 + .../ui/text/c/hover/CHoverMessages.properties | 3 + .../DOMCompletionProposalComputer.java | 534 ++++++- .../eclipse/cdt/ui/CUIPreferenceInitializer.java | 94 +- 8 files changed, 2837 insertions(+), 38 deletions(-) create mode 100644 org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocBrowserInformationControl.java create mode 100644 org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocBrowserInformationControlInput.java create mode 100644 org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CElementLinks.java diff --git a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocBrowserInformationControl.java b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocBrowserInformationControl.java new file mode 100644 index 0000000..7123832 --- /dev/null +++ b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocBrowserInformationControl.java @@ -0,0 +1,596 @@ +package org.eclipse.cdt.internal.ui.text.c.hover; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Iterator; + +import org.eclipse.cdt.ui.CUIPreferenceInitializer; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.internal.text.html.BrowserInformationControlInput; +import org.eclipse.jface.internal.text.html.HTML2TextReader; +import org.eclipse.jface.internal.text.html.HTMLPrinter; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.text.AbstractInformationControl; +import org.eclipse.jface.text.IDelayedInputChangeProvider; +import org.eclipse.jface.text.IInformationControlExtension2; +import org.eclipse.jface.text.IInputChangedListener; +import org.eclipse.jface.text.TextPresentation; +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTError; +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.browser.LocationListener; +import org.eclipse.swt.browser.OpenWindowListener; +import org.eclipse.swt.browser.ProgressAdapter; +import org.eclipse.swt.browser.ProgressEvent; +import org.eclipse.swt.browser.WindowEvent; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.graphics.TextLayout; +import org.eclipse.swt.graphics.TextStyle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Slider; + +/** + * Displays doxygen HTML information. + */ +public class CDocBrowserInformationControl extends AbstractInformationControl implements + IInformationControlExtension2, IDelayedInputChangeProvider { + + /** + * Tells whether the SWT Browser widget and hence this information + * control is available. + * + * @param parent the parent component used for checking or null if none + * @return true if this control is available + */ + public static boolean isAvailable(Composite parent) { + if (!fgAvailabilityChecked) { + try { + Browser browser= new Browser(parent, SWT.NONE); + browser.dispose(); + fgIsAvailable= true; + + Slider sliderV= new Slider(parent, SWT.VERTICAL); + Slider sliderH= new Slider(parent, SWT.HORIZONTAL); + int width= sliderV.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; + int height= sliderH.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; + fgScrollBarSize= new Point(width, height); + sliderV.dispose(); + sliderH.dispose(); + } catch (SWTError er) { + fgIsAvailable= false; + } finally { + fgAvailabilityChecked= true; + } + } + + return fgIsAvailable; + } + + /** + * Minimal size constraints. + * @since 3.2 + */ + private static final int MIN_WIDTH= 80; + + private static final int MIN_HEIGHT= 50; + + + /** + * Availability checking cache. + */ + private static boolean fgIsAvailable= false; + + private static boolean fgAvailabilityChecked= false; + + /** + * Cached scroll bar width and height + * @since 3.4 + */ + private static Point fgScrollBarSize; + + /** The control's browser widget */ + private Browser fBrowser; + + /** Tells whether the browser has content */ + private boolean fBrowserHasContent; + + /** Text layout used to approximate size of content when rendered in browser */ + private TextLayout fTextLayout; + + /** Bold text style */ + private TextStyle fBoldStyle; + + private BrowserInformationControlInput fInput; + + /** + * true iff the browser has completed loading of the last + * input set via {@link #setInformation(String)}. + * @since 3.4 + */ + private boolean fCompleted= false; + + /** + * The listener to be notified when a delayed location changing event happened. + * @since 3.4 + */ + private IInputChangedListener fDelayedInputChangeListener; + + /** + * The listeners to be notified when the input changed. + * @since 3.4 + */ + private ListenerList/**/fInputChangeListeners= new ListenerList(ListenerList.IDENTITY); + + /** + * The symbolic name of the font used for size computations, or null to use dialog font. + * @since 3.4 + */ + private final String fSymbolicFontName; + + + /** + * Creates a browser information control with the given shell as parent. + * + * @param parent the parent shell + * @param symbolicFontName the symbolic name of the font used for size computations + * @param resizable true if the control should be resizable + * @since 3.4 + */ + public CDocBrowserInformationControl(Shell parent, String symbolicFontName, boolean resizable) { + super(parent, resizable); + fSymbolicFontName= symbolicFontName; + create(); + } + + /** + * Creates a browser information control with the given shell as parent. + * + * @param parent the parent shell + * @param symbolicFontName the symbolic name of the font used for size computations + * @param statusFieldText the text to be used in the optional status field + * or null if the status field should be hidden + * @since 3.4 + */ + public CDocBrowserInformationControl(Shell parent, String symbolicFontName, String statusFieldText) { + super(parent, statusFieldText); + fSymbolicFontName= symbolicFontName; + create(); + } + + /** + * Creates a browser information control with the given shell as parent. + * + * @param parent the parent shell + * @param symbolicFontName the symbolic name of the font used for size computations + * @param toolBarManager the manager or null if toolbar is not desired + * @since 3.4 + */ + public CDocBrowserInformationControl(Shell parent, String symbolicFontName, ToolBarManager toolBarManager) { + super(parent, toolBarManager); + fSymbolicFontName= symbolicFontName; + create(); + } + + /* + * @see org.eclipse.jface.text.AbstractInformationControl#createContent(org.eclipse.swt.widgets.Composite) + */ + protected void createContent(Composite parent) { + fBrowser= new Browser(parent, SWT.NONE); + fBrowser.setJavascriptEnabled(false); + + Display display= getShell().getDisplay(); + fBrowser.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); + fBrowser.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + + fBrowser.addProgressListener(new ProgressAdapter() { + public void completed(ProgressEvent event) { + fCompleted= true; + } + }); + + fBrowser.addOpenWindowListener(new OpenWindowListener() { + public void open(WindowEvent event) { + event.required= true; // Cancel opening of new windows + } + }); + + // Replace browser's built-in context menu with none + fBrowser.setMenu(new Menu(getShell(), SWT.NONE)); + + createTextLayout(); + } + + /** + * {@inheritDoc} + * @deprecated use {@link #setInput(Object)} + */ + public void setInformation(final String content) { + setInput(new BrowserInformationControlInput(null) { + public String getHtml() { + return content; + } + + public String getInputName() { + return ""; //$NON-NLS-1$ + } + + public Object getInputElement() { + return content; + } + }); + } + + /** + * {@inheritDoc} This control can handle {@link String} and + * {@link BrowserInformationControlInput}. + */ + public void setInput(Object input) { + Assert.isLegal(input == null || input instanceof String || input instanceof BrowserInformationControlInput); + + if (input instanceof String) { + setInformation((String)input); + return; + } + + fInput= (BrowserInformationControlInput)input; + + String content= null; + if (fInput != null) + content= fInput.getHtml(); + + fBrowserHasContent= content != null && content.length() > 0; + + if (!fBrowserHasContent) + content= ""; //$NON-NLS-1$ + + boolean RTL= (getShell().getStyle() & SWT.RIGHT_TO_LEFT) != 0; + boolean resizable= isResizable(); + + // The default "overflow:auto" would not result in a predictable width for the client area + // and the re-wrapping would cause visual noise + String[] styles= null; + if (RTL && resizable) + styles= new String[] { "direction:rtl;", "overflow:scroll;", "word-wrap:break-word;" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + else if (RTL && !resizable) + styles= new String[] { "direction:rtl;", "overflow:hidden;", "word-wrap:break-word;" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + else if (!resizable) + //XXX: In IE, "word-wrap: break-word;" causes bogus wrapping even in non-broken words :-(see e.g. Javadoc of String). + // Re-check whether we really still need this now that the Javadoc Hover header already sets this style. + styles= new String[] { "overflow:hidden;"/*, "word-wrap: break-word;"*/}; //$NON-NLS-1$ + else + styles= new String[] { "overflow:scroll;" }; //$NON-NLS-1$ + + StringBuffer buffer= new StringBuffer(content); + HTMLPrinter.insertStyles(buffer, styles); + content= buffer.toString(); + + /* + * XXX: Should add some JavaScript here that shows something like + * "(continued...)" or "..." at the end of the visible area when the page overflowed + * with "overflow:hidden;". + */ + + fCompleted= false; + if (!content.contains(".html")) { + return; + } + String url = CUIPreferenceInitializer.doxygenDirectoryPath + "/" + content; + CElementLinks.isSet = 1; + fBrowser.setUrl(url); + + Object[] listeners= fInputChangeListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) + ((IInputChangedListener)listeners[i]).inputChanged(fInput); + } + + /* + * @see IInformationControl#setVisible(boolean) + */ + public void setVisible(boolean visible) { + Shell shell= getShell(); + if (shell.isVisible() == visible) + return; + + if (!visible) { + super.setVisible(false); + setInput(null); + return; + } + + /* + * The Browser widget flickers when made visible while it is not completely loaded. + * The fix is to delay the call to setVisible until either loading is completed + * (see ProgressListener in constructor), or a timeout has been reached. + */ + final Display display= shell.getDisplay(); + + // Make sure the display wakes from sleep after timeout: + display.timerExec(100, new Runnable() { + public void run() { + fCompleted= true; + } + }); + + while (!fCompleted) { + // Drive the event loop to process the events required to load the browser widget's contents: + if (!display.readAndDispatch()) { + display.sleep(); + } + } + + shell= getShell(); + if (shell == null || shell.isDisposed()) + return; + + /* + * Avoids flickering when replacing hovers, especially on Vista in ON_CLICK mode. + * Causes flickering on GTK. Carbon does not care. + */ + if ("win32".equals(SWT.getPlatform())) //$NON-NLS-1$ + shell.moveAbove(null); + + super.setVisible(true); + } + + /* + * @see org.eclipse.jface.text.AbstractInformationControl#setSize(int, int) + */ + public void setSize(int width, int height) { + fBrowser.setRedraw(false); // avoid flickering + try { + //super.setSize(width, height); + super.setSize(750, 300); // TODO Modify + } finally { + fBrowser.setRedraw(true); + } + } + + /** + * Creates and initializes the text layout used + * to compute the size hint. + * + * @since 3.2 + */ + private void createTextLayout() { + fTextLayout= new TextLayout(fBrowser.getDisplay()); + + // Initialize fonts + String symbolicFontName= fSymbolicFontName == null ? JFaceResources.DIALOG_FONT : fSymbolicFontName; + Font font= JFaceResources.getFont(symbolicFontName); + fTextLayout.setFont(font); + fTextLayout.setWidth(-1); + font= JFaceResources.getFontRegistry().getBold(symbolicFontName); + fBoldStyle= new TextStyle(font, null, null); + + // Compute and set tab width + fTextLayout.setText(" "); //$NON-NLS-1$ + int tabWidth= fTextLayout.getBounds().width; + fTextLayout.setTabs(new int[] { tabWidth }); + fTextLayout.setText(""); //$NON-NLS-1$ + } + + /* + * @see org.eclipse.jface.text.AbstractInformationControl#handleDispose() + * @since 3.6 + */ + protected void handleDispose() { + if (fTextLayout != null) { + fTextLayout.dispose(); + fTextLayout= null; + } + fBrowser= null; + + super.handleDispose(); + } + + /* + * @see IInformationControl#computeSizeHint() + */ + public Point computeSizeHint() { + Point sizeConstraints= getSizeConstraints(); + Rectangle trim= computeTrim(); + int height= trim.height; + + //FIXME: The HTML2TextReader does not render

like a browser. + // Instead of inserting an empty line, it just adds a single line break. + // Furthermore, the indentation of

elements is too small (e.g with a long @see line) + TextPresentation presentation= new TextPresentation(); + HTML2TextReader reader= new HTML2TextReader(new StringReader(fInput.getHtml()), presentation); + String text; + try { + text= reader.getString(); + } catch (IOException e) { + text= ""; //$NON-NLS-1$ + } + + fTextLayout.setText(text); + fTextLayout.setWidth(sizeConstraints == null ? SWT.DEFAULT : sizeConstraints.x - trim.width); + Iterator iter= presentation.getAllStyleRangeIterator(); + while (iter.hasNext()) { + StyleRange sr= (StyleRange)iter.next(); + if (sr.fontStyle == SWT.BOLD) + fTextLayout.setStyle(fBoldStyle, sr.start, sr.start + sr.length - 1); + } + + Rectangle bounds= fTextLayout.getBounds(); // does not return minimum width, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=217446 + int lineCount= fTextLayout.getLineCount(); + int textWidth= 0; + for (int i= 0; i < lineCount; i++) { + Rectangle rect= fTextLayout.getLineBounds(i); + int lineWidth= rect.x + rect.width; + if (i == 0) + lineWidth+= fInput.getLeadingImageWidth(); + textWidth= Math.max(textWidth, lineWidth); + } + bounds.width= textWidth; + fTextLayout.setText(""); //$NON-NLS-1$ + + int minWidth= bounds.width; + height= height + bounds.height; + + // Add some air to accommodate for different browser renderings + minWidth+= 15; + height+= 15; + + + // Apply max size constraints + if (sizeConstraints != null) { + if (sizeConstraints.x != SWT.DEFAULT) + minWidth= Math.min(sizeConstraints.x, minWidth + trim.width); + if (sizeConstraints.y != SWT.DEFAULT) + height= Math.min(sizeConstraints.y, height); + } + + // Ensure minimal size + int width= Math.max(MIN_WIDTH, minWidth); + height= Math.max(MIN_HEIGHT, height); + + return new Point(width, height); + } + + /* + * @see org.eclipse.jface.text.IInformationControlExtension3#computeTrim() + */ + public Rectangle computeTrim() { + Rectangle trim= super.computeTrim(); + if (isResizable()) { + boolean RTL= (getShell().getStyle() & SWT.RIGHT_TO_LEFT) != 0; + if (RTL) { + trim.x-= fgScrollBarSize.x; + } + trim.width+= fgScrollBarSize.x; + trim.height+= fgScrollBarSize.y; + } + return trim; + } + + /** + * Adds the listener to the collection of listeners who will be + * notified when the current location has changed or is about to change. + * + * @param listener the location listener + * @since 3.4 + */ + public void addLocationListener(LocationListener listener) { + fBrowser.addLocationListener(listener); + } + + /* + * @see IInformationControl#setForegroundColor(Color) + */ + public void setForegroundColor(Color foreground) { + super.setForegroundColor(foreground); + fBrowser.setForeground(foreground); + } + + /* + * @see IInformationControl#setBackgroundColor(Color) + */ + public void setBackgroundColor(Color background) { + super.setBackgroundColor(background); + fBrowser.setBackground(background); + } + + /* + * @see IInformationControlExtension#hasContents() + */ + public boolean hasContents() { + return fBrowserHasContent; + } + + /** + * Adds a listener for input changes to this input change provider. + * Has no effect if an identical listener is already registered. + * + * @param inputChangeListener the listener to add + * @since 3.4 + */ + public void addInputChangeListener(IInputChangedListener inputChangeListener) { + Assert.isNotNull(inputChangeListener); + fInputChangeListeners.add(inputChangeListener); + } + + /** + * Removes the given input change listener from this input change provider. + * Has no effect if an identical listener is not registered. + * + * @param inputChangeListener the listener to remove + * @since 3.4 + */ + public void removeInputChangeListener(IInputChangedListener inputChangeListener) { + fInputChangeListeners.remove(inputChangeListener); + } + + /* + * @see org.eclipse.jface.text.IDelayedInputChangeProvider#setDelayedInputChangeListener(org.eclipse.jface.text.IInputChangedListener) + * @since 3.4 + */ + public void setDelayedInputChangeListener(IInputChangedListener inputChangeListener) { + fDelayedInputChangeListener= inputChangeListener; + } + + /** + * Tells whether a delayed input change listener is registered. + * + * @return true iff a delayed input change + * listener is currently registered + * @since 3.4 + */ + public boolean hasDelayedInputChangeListener() { + return fDelayedInputChangeListener != null; + } + + /** + * Notifies listeners of a delayed input change. + * + * @param newInput the new input, or null to request cancellation + * @since 3.4 + */ + public void notifyDelayedInputChange(Object newInput) { + if (fDelayedInputChangeListener != null) + fDelayedInputChangeListener.inputChanged(newInput); + } + + /* + * @see java.lang.Object#toString() + * @since 3.4 + */ + public String toString() { + String style= (getShell().getStyle() & SWT.RESIZE) == 0 ? "fixed" : "resizeable"; //$NON-NLS-1$ //$NON-NLS-2$ + return super.toString() + " - style: " + style; //$NON-NLS-1$ + } + + /** + * @return the current browser input or null + */ + public BrowserInformationControlInput getInput() { + return fInput; + } + + /* + * @see org.eclipse.jface.text.IInformationControlExtension5#computeSizeConstraints(int, int) + */ + public Point computeSizeConstraints(int widthInChars, int heightInChars) { + if (fSymbolicFontName == null) + return null; + + GC gc= new GC(fBrowser); + Font font= fSymbolicFontName == null ? JFaceResources.getDialogFont() : JFaceResources.getFont(fSymbolicFontName); + gc.setFont(font); + int width= gc.getFontMetrics().getAverageCharWidth(); + int height= gc.getFontMetrics().getHeight(); + gc.dispose(); + + return new Point(widthInChars * width, heightInChars * height); + } +} diff --git a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocBrowserInformationControlInput.java b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocBrowserInformationControlInput.java new file mode 100644 index 0000000..3305a84 --- /dev/null +++ b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocBrowserInformationControlInput.java @@ -0,0 +1,66 @@ +package org.eclipse.cdt.internal.ui.text.c.hover; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.internal.text.html.BrowserInformationControlInput; + +/** + * Provides input for CDocBrowserInformationControl. + */ +public class CDocBrowserInformationControlInput extends BrowserInformationControlInput { + private final String fElement; + private final String fHtml; + private final int fLeadingImageWidth; + + /** + * Creates a new browser information control input. + * + * @param previous previous input, or null if none available + * @param element the element, or null if none available + * @param html HTML contents, must not be null + * @param leadingImageWidth the indent required for the element image + */ + public CDocBrowserInformationControlInput(CDocBrowserInformationControlInput previous, String element, String html, int leadingImageWidth) { + super(previous); + Assert.isNotNull(html); + fElement= element; + fHtml= html; + fLeadingImageWidth= leadingImageWidth; + } + + /* + * @see org.eclipse.jface.internal.text.html.BrowserInformationControlInput#getLeadingImageWidth() + * @since 3.4 + */ + public int getLeadingImageWidth() { + return fLeadingImageWidth; + } + + /** + * Returns the Java element. + * + * @return the element or null if none available + */ + public String getElement() { + return fElement; + } + + @Override + public String getHtml() { + // TODO Auto-generated method stub + return fHtml; + } + + @Override + public Object getInputElement() { + // TODO Auto-generated method stub + return fElement == null ? (Object) fHtml : fElement; + } + + @Override + public String getInputName() { + // TODO Auto-generated method stub + //return fElement == null ? "" : fElement.getElementName(); //$NON-NLS-1$ + return fElement == null ? "" : fElement; + } + +} diff --git a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocHover.java b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocHover.java index e912220..34dc834 100644 --- a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocHover.java +++ b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CDocHover.java @@ -11,33 +11,992 @@ *******************************************************************************/ package org.eclipse.cdt.internal.ui.text.c.hover; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Set; +import java.util.StringTokenizer; +import org.eclipse.cdt.core.dom.ast.DOMException; +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; +import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.core.model.CModelException; +import org.eclipse.cdt.core.model.ILanguage; +import org.eclipse.cdt.core.model.ITranslationUnit; +import org.eclipse.cdt.core.model.IWorkingCopy; +import org.eclipse.cdt.core.parser.KeywordSetKey; +import org.eclipse.cdt.core.parser.ParserFactory; +import org.eclipse.cdt.core.parser.ParserLanguage; +import org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable; +import org.eclipse.cdt.internal.corext.util.Strings; +import org.eclipse.cdt.internal.ui.CHelpProviderManager; +import org.eclipse.cdt.internal.ui.editor.ASTProvider; +import org.eclipse.cdt.internal.ui.editor.CEditorMessages; +import org.eclipse.cdt.internal.ui.text.CWordFinder; +import org.eclipse.cdt.internal.ui.text.HTMLPrinter; +import org.eclipse.cdt.ui.CUIPlugin; +import org.eclipse.cdt.ui.CUIPreferenceInitializer; +import org.eclipse.cdt.ui.IFunctionSummary; +import org.eclipse.cdt.ui.IFunctionSummary.IFunctionPrototypeSummary; +import org.eclipse.cdt.ui.IRequiredInclude; +import org.eclipse.cdt.ui.IWorkingCopyManager; +import org.eclipse.cdt.ui.PreferenceConstants; +import org.eclipse.cdt.ui.text.ICHelpInvocationContext; +import org.eclipse.cdt.ui.text.IHoverHelpInvocationContext; import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.internal.text.html.BrowserInformationControlInput; +import org.eclipse.jface.internal.text.html.BrowserInput; +import org.eclipse.jface.text.AbstractReusableInformationControlCreator; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DefaultInformationControl; +import org.eclipse.jface.text.IInformationControl; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.IInformationControlExtension4; +import org.eclipse.jface.text.IInputChangedListener; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Region; +import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.IWorkbenchSite; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.editors.text.EditorsUI; -import org.eclipse.cdt.core.model.ITranslationUnit; -import org.eclipse.cdt.ui.CUIPlugin; -import org.eclipse.cdt.ui.IFunctionSummary; -import org.eclipse.cdt.ui.IRequiredInclude; -import org.eclipse.cdt.ui.IFunctionSummary.IFunctionPrototypeSummary; -import org.eclipse.cdt.ui.text.ICHelpInvocationContext; -import org.eclipse.cdt.ui.text.IHoverHelpInvocationContext; +/** + * Provides doxygen hover. + */ +public class CDocHover extends AbstractCEditorTextHover { -import org.eclipse.cdt.internal.ui.CHelpProviderManager; -import org.eclipse.cdt.internal.ui.editor.CEditorMessages; -import org.eclipse.cdt.internal.ui.text.CWordFinder; -import org.eclipse.cdt.internal.ui.text.HTMLPrinter; + private static String doxygenDirectoryPath; -public class CDocHover extends AbstractCEditorTextHover { - /** * Constructor for DefaultCEditorTextHover */ public CDocHover() { + doxygenDirectoryPath = CUIPreferenceInitializer.doxygenDirectoryPath; + } + + private static class SingletonRule implements ISchedulingRule { + public static final ISchedulingRule INSTANCE = new SingletonRule(); + public boolean contains(ISchedulingRule rule) { + return rule == this; + } + public boolean isConflicting(ISchedulingRule rule) { + return rule == this; + } + } + + /** + * Computes the source location for a given identifier. + */ + private static class ComputeSourceRunnable implements ASTRunnable { + + private final IRegion fTextRegion; + private String fSource; + + /** + * If method is in source file, this is used. + */ + private final int methodInSource = 1001; + /** + * If method is in header file, this is used. + */ + private final int methodInHeader = 1002; + + public ComputeSourceRunnable(ITranslationUnit tUnit, IRegion textRegion) { + fTextRegion = textRegion; + fSource = null; + } + + /** + * Tokenizer + * + * @param string string to tokenize + * @return array of tokenized string + */ + private String[] tokenizeString(String string) { + StringTokenizer tokenizer = new StringTokenizer(string); + String tokenOfString[] = new String[tokenizer.countTokens()]; + int index = 0; + while (tokenizer.hasMoreTokens()) { + tokenOfString[index] = tokenizer.nextToken(); + index++; + } + return tokenOfString; + } + /** + * Tokenizer with delimiter + * + * @param string string to tokenize + * @param delimiter delimiter + * @return array of tokenized string + */ + private String[] tokenizeStringWithDelimiter(String string, String delimiter) { + StringTokenizer tokenizer = new StringTokenizer(string, delimiter); + String tokenOfString[] = new String[tokenizer.countTokens()]; + int index = 0; + while (tokenizer.hasMoreTokens()) { + tokenOfString[index] = tokenizer.nextToken(); + index++; + } + return tokenOfString; + } + /** + * Check HTML file existence. + * + * @param htmlFileName HTML file name + * @return true if it exist, false if it doesn't exist + * @throws IOException if an I/O error has occurred + */ + private boolean checkHTMLFileExistence(String htmlFileName) throws IOException { + File file = new File(doxygenDirectoryPath); + File fileList[] = file.listFiles(); + for (int i = 0; i < fileList.length; i++) { + if (htmlFileName.equals(fileList[i].getName())) { + return true; + } + } + + return false; + } + /** + * Find location of member variable of class. + * + * @param htmlLine 1 line of HTML file + * @param memberName method name + * @return location of member variable of class n HTML file + */ + private String searchMemberVariableLocationOfCalss(String htmlLine, String memberName) { + String tempString = null; + String htmlFile = null; + if (htmlLine.contains("class=\"memlist\"") == true) { + tempString = htmlLine.trim(); + tempString = htmlLine.substring( htmlLine.indexOf(">", htmlLine.indexOf("href")) + 1, htmlLine.length()); + if (tempString.indexOf(memberName) == 0) { + htmlFile = htmlLine.substring(htmlLine.indexOf("href") + 6, htmlLine.indexOf("\"", htmlLine.indexOf("href") + 6)); + return htmlFile; + } + } + return null; + } + /** + * Change string with enumeration brace. + * + * @param str string with enumeration brace + * @return changed string + */ + private String changeStringWithEnumerationBrace(String str) { + StringBuilder sb = new StringBuilder(str); + int index = 0; + int index2 = 0; + while (index != 4) { + index = sb.indexOf("enum {", index) + 5; + index2 = sb.indexOf("}", index); + if ((index2 != -1) && (index != -1)) { + sb.delete(index2, index2 + 1); + sb.delete(index, index + 1); + } + } + return sb.toString(); + } + /** + * Change string with void. + * + * @param str string with void + * @return changed string + */ + private String changeStringWithVoid(String str) { + StringBuilder sb = new StringBuilder(str); + int index = 0; + int index2 = 0; + while (index != -1) { + index = sb.indexOf("(void)", index); + index2 = index + 5; + if ((index2 != -1) && (index != -1)) { + sb.delete(index2, index2 + 1); + sb.delete(index, index + 1); + } + } + return sb.toString(); + } + /** + * Change string with template. + * + * @param str string with template + * @return changed string + */ + private String changeStringWithTemplate(String str) { + StringBuilder sb = new StringBuilder(str); + String type = str.substring(str.indexOf("<") + 1, str.indexOf(">")); + if (!type.contains(",")) { + str = str.replaceAll(type, ""); + return str; + } else if (type.contains(",")) { + sb.replace(sb.indexOf("<") + 1, sb.indexOf(",", sb.indexOf("<")), ""); + sb.replace(sb.indexOf(",", sb.indexOf("<")) + 1, sb.indexOf(">"), ""); + return sb.toString(); + } + return null; + } + /** + * Change string with asterisk. + * + * @param str string with asterisk + * @return changed string + */ + private String changeStringWithAsterisk(String str) { + StringBuilder sb = new StringBuilder(str); + int index = 0; + while (index != -1) { + index = sb.indexOf("*", index); + if (index != -1) { + sb.delete(index, index + 1); + sb.replace(index - 1, index, "* "); + } + } + + return sb.toString(); + } + /** + * Find location of method of class in HTML file. + * + * @param htmlLine 1 line of HTML file + * @param binding IBinding + * @param isWhere methodInSource if method is in source file, methodInHeader if method is in header file + * @param isMethodOfClass true if method of class, false if method of interface + * @return location of method of class in HTML file + * @throws DOMException + */ + private String searchMethodLocationOfClass(String htmlLine, IBinding binding, int isWhere, boolean isMethodOfClass) throws DOMException { + String htmlFile = null; + + String str[] = tokenizeString(binding.toString()); + if (str.length == 2) { + if (str[1].equals("CPPFIELD")) { + if ( (htmlFile = searchMemberVariableLocationOfCalss(htmlLine, str[0])) != null) { + return htmlFile; + } + } + } + + String bindingString = binding.toString(); + if (bindingString.contains("long int")) { + bindingString = bindingString.replaceAll("long int", "long"); + } + if (bindingString.contains("short int")) { + bindingString = bindingString.replaceAll("short int", "short"); + } + if (bindingString.contains("enum {")) { + bindingString = changeStringWithEnumerationBrace(bindingString); + } + if (bindingString.contains("enum ")) { + bindingString = bindingString.replaceAll("enum ", ""); + } + /* handling defined type start */ + if (bindingString.contains("wchar_t")) { + bindingString = bindingString.replaceAll("wchar_t", "mchar"); + } + if (bindingString.contains("Buffer")) { + bindingString = bindingString.replaceAll("Buffer", "DoubleBuffer"); + } + if (bindingString.contains("Buffer")) { + bindingString = bindingString.replaceAll("Buffer", "FloatBuffer"); + } + if (bindingString.contains("Buffer")) { + bindingString = bindingString.replaceAll("Buffer", "IntBuffer"); + } + if (bindingString.contains("Buffer")) { + bindingString = bindingString.replaceAll("Buffer", "LongBuffer"); + } + if (bindingString.contains("Buffer")) { + bindingString = bindingString.replaceAll("Buffer", "LongLongBuffer"); + } + if (bindingString.contains("Buffer")) { + bindingString = bindingString.replaceAll("Buffer", "McharBuffer"); + } + if (bindingString.contains("Buffer")) { + bindingString = bindingString.replaceAll("Buffer", "ShortBuffer"); + } + if (bindingString.contains("UUID_")) { + bindingString = bindingString.replaceAll("UUID_", "UUID"); + } + /* handling defined type end */ + if (bindingString.contains("(void)")) { + changeStringWithVoid(bindingString); + } + String ownerOfBinding = binding.getOwner().toString(); + String namespace = ownerOfBinding.substring(0, ownerOfBinding.lastIndexOf(":") + 1); + String methodName = tokenizeStringWithDelimiter(bindingString, "(),")[0]; + if (!methodName.equals("Compare")) { + if (!bindingString.contains("Osp::Base::Object")) { + if (bindingString.contains(namespace)) { + bindingString = bindingString.replaceAll(namespace, ""); + } + } + } else if (methodName.equals("Compare")) { + if (!bindingString.contains("Osp::Base::Object")) { + if (bindingString.contains(namespace)) { + bindingString = bindingString.replaceAll(namespace, ""); + } + } + } + if (ownerOfBinding.equals("Osp::Base::Utility::StringUtil CPPCLASSTYPE")) { + bindingString = bindingString.replaceAll("Osp::Base::", ""); + } + if (ownerOfBinding.equals("Osp::Ui::Controls::Frame CPPCLASSTYPE")) { + if (!bindingString.contains("AddControl")) { + bindingString = bindingString.replaceAll("Osp::Ui::", ""); + } + } + if (bindingString.contains("{")) { + bindingString = bindingString.substring(0, bindingString.indexOf("{") - 1); + bindingString = bindingString + " CPPMETHOD"; + if (bindingString.contains("<")) { + bindingString = changeStringWithTemplate(bindingString); + } + } + bindingString = bindingString.replaceAll(" ", ""); + + String bindingStringMethod[] = tokenizeStringWithDelimiter(bindingString, "(),"); + String htmlLineString = null; + int isWhereTemp = 0; + if (isWhere == methodInSource) { + isWhereTemp = 2; + } else if (isWhere == methodInHeader) { + isWhereTemp = 1; + } + boolean isSame = true; + + if (htmlLine.contains("class=\"memlist\"") == true) { + htmlLineString = htmlLine.trim(); + htmlLineString = htmlLine.substring( htmlLine.indexOf(">", htmlLine.indexOf("href")) + 1, htmlLine.length()); + if (htmlLineString.contains("const ")) { + htmlLineString = htmlLineString.replaceAll("const ", "const"); + } + if (htmlLineString.contains(" &")) { + htmlLineString = htmlLineString.replaceAll(" &", "& "); + } + if (htmlLineString.contains("*")) { + htmlLineString = changeStringWithAsterisk(htmlLineString); + } + if (htmlLineString.contains("< ")) { + htmlLineString = htmlLineString.replaceAll("< ", "<"); + } + if (htmlLineString.contains(" >")) { + htmlLineString = htmlLineString.replaceAll(" >", ">"); + } + if (htmlLineString.contains("<")) { + htmlLineString = changeStringWithTemplate(htmlLineString); + } + if (htmlLineString.contains("(") && htmlLineString.contains(")")) { /* if member of class is method*/ + String tokenOfMethod1 = htmlLineString.substring(0, htmlLineString.indexOf("(")); + String tokenOfMethod2 = htmlLineString.substring(htmlLineString.indexOf("("), htmlLineString.indexOf(")") + 1); + String tokenOfMethod3 = htmlLineString.substring(htmlLineString.indexOf(")") + 1, htmlLineString.length()); + /* handling defined type start */ + if (tokenOfMethod2.contains("AppId")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("AppId", "Osp::Base::String"); + } + if (tokenOfMethod2.contains("AppSecret")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("AppSecret", "Osp::Base::String"); + } + if (tokenOfMethod2.contains("BuddyCategoryId")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("BuddyCategoryId", "Osp::Base::String"); + } + if (tokenOfMethod2.contains("ChatMsgId")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("ChatMsgId", "int"); + } + if (tokenOfMethod2.contains("KeyData")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("KeyData", "char"); + } + if (tokenOfMethod2.contains("LandmarkId")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("LandmarkId", "long"); + } + if (tokenOfMethod2.contains("LoginId")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("LoginId", "Osp::Base::String"); + } + if (tokenOfMethod2.contains("NetAccountId")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("NetAccountId", "int"); + } + if (tokenOfMethod2.contains("RecordId")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("RecordId", "long long"); + } + if (tokenOfMethod2.contains("RecurrenceId")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("RecurrenceId", "Osp::Base::DateTime"); + } + if (tokenOfMethod2.contains("RequestId")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("RequestId", "long"); + } + if (tokenOfMethod2.contains("result")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("result", "unsigned long"); + } + if (tokenOfMethod2.contains("UserId")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("UserId", "Osp::Base::String"); + } + if (tokenOfMethod2.contains("xmlChar")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("xmlChar", "unsigned char"); + } + if (tokenOfMethod2.contains("byte")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("byte", "unsigned char"); + } + if (tokenOfMethod2.contains("unsigned ")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("unsigned ", "unsigned"); + } + if (tokenOfMethod2.contains("long long")) { + tokenOfMethod2 = tokenOfMethod2.replaceAll("long long", "longlong"); + } + /* handling defined type end */ + htmlLineString = tokenOfMethod1 + tokenOfMethod2 + tokenOfMethod3; + } + if (isMethodOfClass == false) { + htmlLineString = htmlLineString.replaceAll("=0", ""); + } + if (htmlLineString.indexOf(bindingStringMethod[0]) == 0) { /* method name in HTML file and method[0] variable are same */ + if (bindingStringMethod.length != isWhereTemp) { /* method has parameter variable */ + if ( (bindingStringMethod.length == isWhereTemp + 1) && (bindingStringMethod[1].equals("void")) && (htmlLineString.contains("(void")) ) { /* method has void parameter variable */ + htmlFile = htmlLine.substring(htmlLine.indexOf("href") + 6, htmlLine.indexOf("\"", htmlLine.indexOf("href") + 6)); + return htmlFile; + } + htmlLineString = htmlLineString.substring(htmlLineString.indexOf("(") + 1, htmlLineString.indexOf(")")); + String htmlLineStringMethod[] = tokenizeStringWithDelimiter(htmlLineString, ", "); + if ( ( (bindingStringMethod.length - isWhereTemp) * 2 ) == htmlLineStringMethod.length ) { /* the number of parameter variable of method in HTML file and the number of parameter variable of method are same */ + if (binding.toString().contains("enum {")) { + htmlFile = htmlLine.substring(htmlLine.indexOf("href") + 6, htmlLine.indexOf("\"", htmlLine.indexOf("href") + 6)); + return htmlFile; + } + if (binding.getOwner().toString().contains("CPP_CLASS_INSTANCE")) { + htmlFile = htmlLine.substring(htmlLine.indexOf("href") + 6, htmlLine.indexOf("\"", htmlLine.indexOf("href") + 6)); + return htmlFile; + } + for (int i = 0; i < bindingStringMethod.length - 1; i++) { + if ( !bindingStringMethod[i + 1].equals(htmlLineStringMethod[i * 2]) ) { + isSame = false; + break; + } + if (isSame == true) { + htmlFile = htmlLine.substring(htmlLine.indexOf("href") + 6, htmlLine.indexOf("\"", htmlLine.indexOf("href") + 6)); + return htmlFile; + } + } + } + } else if (bindingStringMethod.length == isWhereTemp) { /* method doesn't have parameter variable */ + htmlFile = htmlLine.substring(htmlLine.indexOf("href") + 6, htmlLine.indexOf("\"", htmlLine.indexOf("href") + 6)); + return htmlFile; + } + } + } + + return null; + } + /** + * Find location of method of class in HTML file, if mouseover event occurs in header file. + * + * @param htmlLine 1 line of HTML file + * @param binding IBinding + * @return location of method of class in HTML file + * @throws DOMException, IOExceptio + */ + private String searchMethod(String htmlLine, IBinding binding) throws DOMException, IOException { + String stringOfOwner = binding.getOwner().toString(); + String htmlFileName = null; + boolean isExist = false; + + if (htmlLine.contains("class=\"el\"") == true) { + if (htmlLine.contains(">" + stringOfOwner + "<")) { + int beginIndex = htmlLine.indexOf("\">", htmlLine.indexOf(">" + stringOfOwner + "<") + stringOfOwner.length() + 4); + int endIndex = htmlLine.indexOf("<", beginIndex); + String namespace = htmlLine.substring(beginIndex + 2, endIndex); + if (checkIsInterface(stringOfOwner) == false) { + htmlFileName = "class" + namespace.replaceAll("::", "_1_1") +"_1_1" + stringOfOwner + "-members.html"; + + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + + if (isExist == true) { + BufferedReader reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String tempString = null; + String htmlFileName2 = null; + while ( (tempString = reader.readLine()) != null) { + if ( (htmlFileName2 = searchMethodLocationOfClass(tempString, binding, methodInHeader, true)) != null) { + break; + } + } + reader.close(); + htmlFileName = htmlFileName2; + } + + return htmlFileName; + } else if (checkIsInterface(stringOfOwner) == true) { + htmlFileName = "interface" + namespace.replaceAll("::", "_1_1") +"_1_1" + stringOfOwner + "-members.html"; + + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + + if (isExist == true) { + BufferedReader reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String tempString = null; + String htmlFileName2 = null; + while ( (tempString = reader.readLine()) != null) { + if ( (htmlFileName2 = searchMethodLocationOfClass(tempString, binding, methodInHeader, false)) != null) { + break; + } + } + reader.close(); + htmlFileName = htmlFileName2; + } + + return htmlFileName; + } + } + } + + return null; + } + /** + * Check is owner a interface or a class. + * + * @param owner string of owner + * @return true if interface, false if class + */ + private boolean checkIsInterface(String owner) { + boolean isInterface = false; + if (owner.substring(0, 1).equals("I")) { + if (Character.isUpperCase(owner.charAt(1)) == true) { + isInterface = true; + } else if (Character.isUpperCase(owner.charAt(1)) == false) { + isInterface = false; + } + } else if (!owner.substring(0, 1).equals("I")) { + isInterface = false; + } + + return isInterface; + } + /** + * Check is owner a interface or a class. + * + * @param owner string array of owner + * @return true if interface, false if class + */ + private boolean checkIsInterface(String[] owner) { + boolean isInterface = false; + if (owner[owner.length - 1].substring(0, 1).equals("I")) { + if (Character.isUpperCase(owner[owner.length - 1].charAt(1)) == true) { + isInterface = true; + } else if (Character.isUpperCase(owner[owner.length - 1].charAt(1)) == false) { + isInterface = false; + } + } else if (!owner[owner.length - 1].substring(0, 1).equals("I")) { + isInterface = false; + } + + return isInterface; + } + /** + * Find location of enumerator in HTML file. + * + * @param htmlFileName name of HTML file + * @param binding IBinding + * @param isWhere methodInSource if enumerator is in source file, methodInHeader if enumerator is in header file + * @return location of enumerator in HTML file + * @throws IOException + */ + private String searchEnumerator(String htmlFileName, IBinding binding, int isWhere) throws IOException { + String enumeratorName = null; + if (isWhere == methodInSource) { + enumeratorName = tokenizeString(binding.toString())[0]; + } else if (isWhere == methodInHeader) { + enumeratorName = binding.getName(); + } + + boolean isExist = false; + + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + + if (isExist == true) { + BufferedReader reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String htmlLine = null; + String htmlFileName2 = null; + while ( (htmlLine = reader.readLine()) != null) { + if (htmlLine.contains(enumeratorName)) { + htmlLine = reader.readLine(); + htmlFileName2 = htmlLine.substring(htmlLine.indexOf("href") + 6, htmlLine.indexOf("\"", htmlLine.indexOf("href") + 6)); + break; + } + } + reader.close(); + htmlFileName = htmlFileName2; + } else if (isExist == false) { + return null; + } + + return htmlFileName; + } + /** + * Find location of enumeration in HTML file. + * + * @param htmlFileName name of HTML file + * @param binding IBinding + * @return location of enumeration in HTML file + * @throws IOException + */ + private String searchEnumeration(String htmlFileName, IBinding binding) throws IOException { + String enumerationName = null; + enumerationName = binding.getName(); + + boolean isExist = false; + + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + + if (isExist == true) { + BufferedReader reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String htmlLine = null; + String htmlFileName2 = null; + while ( (htmlLine = reader.readLine()) != null) { + if (htmlLine.contains(enumerationName)) { + htmlLine = reader.readLine(); + htmlFileName2 = htmlLine.substring(htmlLine.indexOf("href") + 6, htmlLine.indexOf("\"", htmlLine.indexOf("href") + 6)); + break; + } + } + reader.close(); + htmlFileName = htmlFileName2; + } else if (isExist == false) { + return null; + } + + return htmlFileName; + } + + /* + * @see org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable#runOnAST(org.eclipse.cdt.core.dom.ast.IASTTranslationUnit) + */ + public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) { + if (ast != null) { + try { + IASTName name= ast.getNodeSelector(null).findEnclosingName(fTextRegion.getOffset(), fTextRegion.getLength()); + if (name != null) { + IBinding binding= name.resolveBinding(); + + if (binding != null) { + String htmlFileName = null; + boolean isExist = false; + + if (binding.getOwner() == null) { + String stringOfBinding[] = tokenizeString(binding.toString()); + if (stringOfBinding.length == 2) { /* Case 1-1. using root namespace in source file */ + if (stringOfBinding[0].equals("Osp") && stringOfBinding[1].equals("CPPNAMESPACE")) { + htmlFileName = "namespaceOsp.html"; + isExist = checkHTMLFileExistence(htmlFileName); + } + } else if (stringOfBinding.length == 1) { /* Case 1-2. declaration root namespace in header or source file */ + if (stringOfBinding[0].equals("Osp")) { + htmlFileName = "namespaceOsp.html"; + isExist = checkHTMLFileExistence(htmlFileName); + } + } + } else if (binding.getOwner() != null) { + String stringOfOwner[] = tokenizeString(binding.getOwner().toString()); + if (stringOfOwner.length == 3) { + if (stringOfOwner[2].equals("CPPENUMERATION")) { + String stringOfBinding[] = tokenizeString(binding.toString()); + if (stringOfBinding[1].equals("CPPENUMERATOR")) { /* Case 2-0. enumerator in source file */ + char firstChar = stringOfBinding[0].charAt(0); + if (firstChar == 'A') { + htmlFileName = "namespacemembers_eval.html"; + } else { + for (int i = 66; i < 91; i++) { + if ((int)firstChar == i) { + htmlFileName = "namespacemembers_eval_0x" + Integer.toHexString(i + 32) + ".html"; + } + } + } + + String htmlFileName2 = null; + if ( (htmlFileName2 = searchEnumerator(htmlFileName, binding, methodInSource)) != null) { + isExist = true; + htmlFileName = htmlFileName2; + } + } + } + } else if (stringOfOwner.length == 2) { + if (stringOfOwner[1].equals("CPPNAMESPACE")) { + String stringOfBinding[] = tokenizeString(binding.toString()); + if (stringOfBinding[1].equals("CPPNAMESPACE")) { /* Case 2-1-1. using namespace in source file */ + htmlFileName = "namespace" + tokenizeString(binding.toString())[0].replaceAll("::", "_1_1") + ".html"; + isExist = checkHTMLFileExistence(htmlFileName); + } else if (stringOfBinding[1].equals("CPPCLASSTYPE")) { /* Case 2-1-2. class in source file */ + htmlFileName = "class" + stringOfBinding[0].replaceAll("::", "_1_1") + ".html"; + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + } else if (stringOfBinding[1].equals("CPP_CLASS_TEMPLATE")) { /* Case 2-1-3. class template in source file */ + htmlFileName = "class" + stringOfBinding[0].replaceAll("::", "_1_1") + ".html"; + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + } /*else if (stringOfBinding[1].equals("CPPENUMERATOR")) { Case 2-1-4. enumerator in source file + char firstChar = stringOfBinding[0].charAt(0); + if (firstChar == 'A') { + htmlFileName = "namespacemembers_eval.html"; + } else { + for (int i = 66; i < 91; i++) { + if ((int)firstChar == i) { + htmlFileName = "namespacemembers_eval_0x" + Integer.toHexString(i + 32) + ".html"; + } + } + } + + String htmlFileName2 = null; + if ( (htmlFileName2 = searchEnumerator(htmlFileName, binding, methodInSource)) != null) { + isExist = true; + htmlFileName = htmlFileName2; + } + }*/ else if (stringOfBinding.length == 3 && stringOfBinding[2].equals("CPPENUMERATION")) { /* Case 2-1-5. enumeration used as parameter variable in header file */ + char firstChar = stringOfBinding[1].charAt(0); + if (firstChar == 'A') { + htmlFileName = "namespacemembers_enum.html"; + } else { + for (int i = 66; i < 91; i++) { + if ((int)firstChar == i) { + htmlFileName = "namespacemembers_enum_0x" + Integer.toHexString(i + 32) + ".html"; + } + } + } + + String htmlFileName2 = null; + if ( (htmlFileName2 = searchEnumeration(htmlFileName, binding)) != null) { + isExist = true; + htmlFileName = htmlFileName2; + } + } else if (binding.toString().contains("CPPTYPEDEF") && binding.toString().contains("enum")) { /* Case 2-1-6. typedef enumeration used as parameter variable in header file */ + char firstChar = stringOfBinding[0].charAt(0); + if (firstChar == 'A') { + htmlFileName = "namespacemembers_enum.html"; + } else { + for (int i = 66; i < 91; i++) { + if ((int)firstChar == i) { + htmlFileName = "namespacemembers_enum_0x" + Integer.toHexString(i + 32) + ".html"; + } + } + } + + String htmlFileName2 = null; + if ( (htmlFileName2 = searchEnumeration(htmlFileName, binding)) != null) { + isExist = true; + htmlFileName = htmlFileName2; + } + } + } else if (stringOfOwner[1].equals("CPPCLASSTYPE")) { /* Case 2-2. constructor or member method in source file */ + String tokenOfStringOfOwner[] = tokenizeStringWithDelimiter(stringOfOwner[0], "::"); + if ( checkIsInterface(tokenOfStringOfOwner) == false ) { /* Case 2-2-1. constructor or member method of class in source file */ + htmlFileName = "class" + stringOfOwner[0].replaceAll("::", "_1_1") + "-members.html"; + + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + + if (isExist == true) { + BufferedReader reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String tempString = null; + String htmlFileName2 = null; + while ( (tempString = reader.readLine()) != null) { + if ( (htmlFileName2 = searchMethodLocationOfClass(tempString, binding, methodInSource, true)) != null) { + break; + } + } + reader.close(); + htmlFileName = htmlFileName2; + } + } else if ( checkIsInterface(tokenOfStringOfOwner) == true ) { /* Case 2-2-2. constructor or member method of interface in source file */ + htmlFileName = "interface" + stringOfOwner[0].replaceAll("::", "_1_1") + "-members.html"; + + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + + if (isExist == true) { + BufferedReader reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String tempString = null; + String htmlFileName2 = null; + while ( (tempString = reader.readLine()) != null) { + if ( (htmlFileName2 = searchMethodLocationOfClass(tempString, binding, methodInSource, false)) != null) { + break; + } + } + reader.close(); + htmlFileName = htmlFileName2; + } + } + } else if (stringOfOwner[1].equals("CPP_CLASS_INSTANCE")) { /* Case 2-3. member method with in source file */ + String tokenOfStringOfOwner[] = tokenizeStringWithDelimiter(stringOfOwner[0], "::"); + if ( checkIsInterface(tokenOfStringOfOwner) == false ) { /* Case 2-3-1. member method with of class in source file */ + String owner = stringOfOwner[0].substring(0, stringOfOwner[0].indexOf("<")); + htmlFileName = "class" + owner.replaceAll("::", "_1_1") + "-members.html"; + + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + + if (isExist == true) { + BufferedReader reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String tempString = null; + String htmlFileName2 = null; + while ( (tempString = reader.readLine()) != null) { + if ( (htmlFileName2 = searchMethodLocationOfClass(tempString, binding, methodInSource, true)) != null) { + break; + } + } + reader.close(); + htmlFileName = htmlFileName2; + } + } else if ( checkIsInterface(tokenOfStringOfOwner) == true ) { /* Case 2-3-2. member method with of interface in source file */ + String owner = stringOfOwner[0].substring(0, stringOfOwner[0].indexOf("<")); + htmlFileName = "interface" + owner.replaceAll("::", "_1_1") + "-members.html"; + + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + + if (isExist == true) { + BufferedReader reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String tempString = null; + String htmlFileName2 = null; + while ( (tempString = reader.readLine()) != null) { + if ( (htmlFileName2 = searchMethodLocationOfClass(tempString, binding, methodInSource, false)) != null) { + break; + } + } + reader.close(); + htmlFileName = htmlFileName2; + } + } + + } + } else if (stringOfOwner.length == 1) { + String stringOfClass[] = tokenizeStringWithDelimiter(binding.getClass().toString(), "."); + if ( stringOfClass[stringOfClass.length - 1].equals("CPPNamespace") ) { /* Case 3-1-1. declaration namespace in header or source file */ + htmlFileName = "namespace" + binding.toString().replaceAll("::", "_1_1") + ".html"; + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + } else if ( stringOfClass[stringOfClass.length - 1].equals("CPPClassType") ) { /* Case 3-1-2. class in header file */ + htmlFileName = "class" + binding.getOwner().toString().replaceAll("::", "_1_1") + "_1_1" + binding.toString() + ".html"; + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + } /*else if ( stringOfClass[stringOfClass.length - 1].equals("CPPEnumerator") ) { Case 3-1-3. enumerator in header file + char firstChar = binding.getName().charAt(0); + if (firstChar == 'A') { + htmlFileName = "namespacemembers_eval.html"; + } else { + for (int i = 66; i < 91; i++) { + if ((int)firstChar == i) { + htmlFileName = "namespacemembers_eval_0x" + Integer.toHexString(i + 32) + ".html"; + } + } + } + + String htmlFileName2 = null; + if ( (htmlFileName2 = searchEnumerator(htmlFileName, binding, methodInHeader)) != null) { + isExist = true; + htmlFileName = htmlFileName2; + } + }*/ else if ( stringOfClass[stringOfClass.length - 1].equals("CPPEnumeration") ) { /* Case 3-1-4. declared enumeration in header file */ + char firstChar = binding.getName().charAt(0); + if (firstChar == 'A') { + htmlFileName = "namespacemembers_enum.html"; + } else { + for (int i = 66; i < 91; i++) { + if ((int)firstChar == i) { + htmlFileName = "namespacemembers_enum_0x" + Integer.toHexString(i + 32) + ".html"; + } + } + } + + String htmlFileName2 = null; + if ( (htmlFileName2 = searchEnumeration(htmlFileName, binding)) != null) { + isExist = true; + htmlFileName = htmlFileName2; + } + } else if ( stringOfClass[stringOfClass.length - 1].equals("CPPTypedef") ) { /* Case 3-1-5. typedef declared enumeration in header file */ + char firstChar = binding.getName().charAt(0); + if (firstChar == 'A') { + htmlFileName = "namespacemembers_enum.html"; + } else { + for (int i = 66; i < 91; i++) { + if ((int)firstChar == i) { + htmlFileName = "namespacemembers_enum_0x" + Integer.toHexString(i + 32) + ".html"; + } + } + } + + String htmlFileName2 = null; + if ( (htmlFileName2 = searchEnumeration(htmlFileName, binding)) != null) { + isExist = true; + htmlFileName = htmlFileName2; + } + } else if ( stringOfClass[stringOfClass.length - 1].equals("CPPClassType") || stringOfClass[stringOfClass.length - 1].equals("CPPMethod") ) { /* Case 3-1-6. constructor or member method in header file */ + htmlFileName = "classes.html"; + + isExist = checkHTMLFileExistence(htmlFileName); /* check HTML file existence */ + + if (isExist == true) { + BufferedReader reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String tempString = null; + String htmlFileName2 = null; + while ( (tempString = reader.readLine()) != null) { + if ( (htmlFileName2 = searchMethod(tempString, binding)) != null) { + break; + } + } + reader.close(); + htmlFileName = htmlFileName2; + } + } + } else if (stringOfOwner.length == 0) { + String stringOfClass[] = tokenizeStringWithDelimiter(binding.getClass().toString(), "."); + if ( stringOfClass[stringOfClass.length - 1].equals("CPPEnumerator") ) { /* Case 4-1. enumerator in header file */ + char firstChar = binding.getName().charAt(0); + if (firstChar == 'A') { + htmlFileName = "namespacemembers_eval.html"; + } else { + for (int i = 66; i < 91; i++) { + if ((int)firstChar == i) { + htmlFileName = "namespacemembers_eval_0x" + Integer.toHexString(i + 32) + ".html"; + } + } + } + + String htmlFileName2 = null; + if ( (htmlFileName2 = searchEnumerator(htmlFileName, binding, methodInHeader)) != null) { + isExist = true; + htmlFileName = htmlFileName2; + } + } + } + } + + if (isExist == true) { + fSource = htmlFileName; + } + /* TODO Modify End */ + + if (fSource != null) { + return Status.OK_STATUS; + } + } + } + } catch (DOMException exc) { + return new Status(IStatus.ERROR, CUIPlugin.PLUGIN_ID, "Internal Error", exc); //$NON-NLS-1$ + } catch (Exception e) { + // + } + } + return Status.CANCEL_STATUS; + } + + /** + * @return the computed source or null, if no source could be computed + */ + public String getSource() { + return fSource; + } + } /* (non-Javadoc) @@ -120,6 +1079,125 @@ public class CDocHover extends AbstractCEditorTextHover { return null; } + + /* + * @see ITextHover#getHoverInfo(ITextViewer, IRegion) + */ + @Override + public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { + IEditorPart editor = getEditor(); + if (editor != null) { + IEditorInput input = editor.getEditorInput(); + IWorkingCopyManager manager = CUIPlugin.getDefault().getWorkingCopyManager(); + IWorkingCopy copy = manager.getWorkingCopy(input); + try { + if (copy == null || !copy.isConsistent()) { + return null; + } + } catch (CModelException exc) { + return null; + } + + String expression; + try { + expression = textViewer.getDocument().get(hoverRegion.getOffset(), hoverRegion.getLength()); + expression = expression.trim(); + if (expression.length() == 0) + return null; + + //Before trying a search lets make sure that the user is not hovering over a keyword + if (selectionIsKeyword(expression)) + return null; + + String source = null; + + // Try with the indexer + source= searchInIndex(copy, hoverRegion); + + if (source == null || source.trim().length() == 0) + return null; + + if (!source.contains(".html")) { + return null; + } + + // we are actually interested in the comments, too. + //source= removeLeadingComments(source); + + String delim= System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + + String[] sourceLines = Strings.convertIntoLines(source); + String firstLine = sourceLines[0]; + if (!Character.isWhitespace(firstLine.charAt(0))) + sourceLines[0] = ""; //$NON-NLS-1$ + Strings.trimIndentation(sourceLines, getTabWidth(), getTabWidth()); + + if (!Character.isWhitespace(firstLine.charAt(0))) + sourceLines[0] = firstLine; + + source = Strings.concatenate(sourceLines, delim); + + return internalGetHoverInfo(source, hoverRegion); + } catch (BadLocationException e) { + } + } + return null; + } + + private CDocBrowserInformationControlInput internalGetHoverInfo(String elements, IRegion hoverRegion) { + return getHoverInfo(elements, null, hoverRegion, null); + } + + private static CDocBrowserInformationControlInput getHoverInfo(String elements, String editorInputElement, IRegion hoverRegion, CDocBrowserInformationControlInput previousInput) { + int leadingImageWidth = 20; + return new CDocBrowserInformationControlInput(previousInput, elements, elements, leadingImageWidth); + } + + private static int getTabWidth() { + return 4; + } + + private String searchInIndex(final ITranslationUnit tUnit, IRegion textRegion) { + final ComputeSourceRunnable computer= new ComputeSourceRunnable(tUnit, textRegion); + Job job= new Job(CHoverMessages.CSourceHover_jobTitle) { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + return ASTProvider.getASTProvider().runOnAST(tUnit, ASTProvider.WAIT_ACTIVE_ONLY, monitor, computer); + } catch (Throwable t) { + CUIPlugin.log(t); + } + return Status.CANCEL_STATUS; + } + }; + // If the hover thread is interrupted this might have negative + // effects on the index - see http://bugs.eclipse.org/219834 + // Therefore we schedule a job to decouple the parsing from this thread. + job.setPriority(Job.DECORATE); + job.setSystem(true); + job.setRule(SingletonRule.INSTANCE); + job.schedule(); + try { + job.join(); + } catch (InterruptedException exc) { + job.cancel(); + return null; + } + return computer.getSource(); + } + + + /** + * Test whether the given name is a known keyword. + * + * @param name + * @return true if the name is a known keyword or false if the + * name is not considered a keyword + */ + private boolean selectionIsKeyword(String name) { + Set keywords = ParserFactory.getKeywordSet(KeywordSetKey.KEYWORDS, ParserLanguage.CPP); + return keywords.contains(name); + } /* (non-Javadoc) * @see org.eclipse.jface.text.ITextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int) @@ -138,5 +1216,378 @@ public class CDocHover extends AbstractCEditorTextHover { } return null; } - + + /* + * @see org.eclipse.jface.text.ITextHoverExtension2#getInformationPresenterControlCreator() + * @since 3.1 + */ + public IInformationControlCreator getInformationPresenterControlCreator() { + if (fPresenterControlCreator == null) + fPresenterControlCreator = new PresenterControlCreator(getSite()); + return fPresenterControlCreator; + } + + private IWorkbenchSite getSite() { + IEditorPart editor= getEditor(); + if (editor != null) + return editor.getSite(); + + return null; + } + + /* + * @see org.eclipse.jface.text.ITextHoverExtension#getHoverControlCreator() + * @since 3.0 + */ + @Override + public IInformationControlCreator getHoverControlCreator() { + if (fHoverControlCreator == null) + fHoverControlCreator = new HoverControlCreator(getInformationPresenterControlCreator()); + return fHoverControlCreator; + } + + private static void addLinkListener(final CDocBrowserInformationControl control) { + control.addLocationListener(CElementLinks.createLocationListener(new CElementLinks.ILinkHandler() { + /* (non-Javadoc) + * @see org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks.ILinkHandler#handleInlineJavadocLink(org.eclipse.jdt.core.IJavaElement) + */ + public void handleInlineCDocLink(String linkTarget) { + CDocBrowserInformationControlInput hoverInfo = getHoverInfo(linkTarget, null, null, (CDocBrowserInformationControlInput) control.getInput()); + if (control.hasDelayedInputChangeListener()) { + control.notifyDelayedInputChange(hoverInfo); + } else { + control.setInput(hoverInfo); + } + } + + public void handleTextSet() { + } + })); + } + + /** + * Get element name of back or forward action. + * + * @param htmlName HTML name + * @return element name + */ + private static String getBackForwardElementName(String htmlName) { + String elementName = ""; + try { + if (htmlName.equals("namespaceOsp.html")) { + elementName = "Osp"; + } else if (htmlName.equals("privlevel.html")) { + elementName = "Privilege Level"; + } else if (htmlName.equals("privgroup.html")) { + elementName = "Privilege Group"; + } else if (htmlName.contains("_1_1")) { + if (!htmlName.contains("#")) { + elementName = htmlName.substring(htmlName.lastIndexOf("_1_1") + 4, htmlName.lastIndexOf(".html")); + } else if (htmlName.contains("#_details")) { + elementName = "Detailed Description"; + } else if (htmlName.contains("#")) { + String htmlFileName = htmlName.substring(0, htmlName.lastIndexOf("#")); + + File file = new File(doxygenDirectoryPath); + File fileList[] = file.listFiles(); + BufferedReader reader = null; + for (int i = 0; i < fileList.length; i++) { + if (htmlFileName.equals(fileList[i].getName())) { + reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String tempString = null; + while ( (tempString = reader.readLine()) != null) { + if (tempString.contains(htmlName)) { + int startIndex = tempString.indexOf(">", tempString.indexOf(htmlName)) + 1; + int endIndex = tempString.indexOf("", startIndex); + elementName = tempString.substring(startIndex, endIndex); + break; + } + } + } + } + reader.close(); + } + } else if (!htmlName.contains("_1_1")) { + elementName = htmlName.substring(0, htmlName.indexOf(".html")); + if (!htmlName.contains("#")) { + File file = new File(doxygenDirectoryPath); + File fileList[] = file.listFiles(); + BufferedReader reader = null; + for (int i = 0; i < fileList.length; i++) { + reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlName)); + String tempString = null; + while ( (tempString = reader.readLine()) != null) { + if (tempString.contains("h1")) { + int startIndex = tempString.indexOf("h1") + 3; + int endIndex = tempString.indexOf("<", startIndex); + elementName = tempString.substring(startIndex, endIndex); + break; + } + } + } + reader.close(); + } else if (htmlName.contains("#")) { + String htmlFileName = htmlName.substring(0, htmlName.lastIndexOf("#")); + String htmlName2 = htmlName.substring(htmlName.indexOf("#"), htmlName.length()); + boolean isSearchFail = false; + + File file = new File(doxygenDirectoryPath); + File fileList[] = file.listFiles(); + BufferedReader reader = null; + for (int i = 0; i < fileList.length; i++) { + if (htmlFileName.equals(fileList[i].getName())) { + reader = new BufferedReader(new FileReader(doxygenDirectoryPath + "/" + htmlFileName)); + String tempString = null; + while ( (tempString = reader.readLine()) != null) { + if (tempString.contains(htmlName)) { + int startIndex = tempString.indexOf(">", tempString.indexOf(htmlName)) + 1; + int endIndex = tempString.indexOf("", startIndex); + elementName = tempString.substring(startIndex, endIndex); + isSearchFail = true; + break; + } + if (isSearchFail == false) { + if (tempString.contains(htmlName2)) { + int startIndex = tempString.indexOf(">", tempString.indexOf(htmlName2)) + 1; + int endIndex = tempString.indexOf("", startIndex); + elementName = tempString.substring(startIndex, endIndex); + break; + } + } + } + } + } + reader.close(); + + if (elementName.contains("")) { + elementName = elementName.replaceAll("", ""); + } + if (elementName.contains("")) { + elementName = elementName.replaceAll("", ""); + } + } + } + } catch (IOException e) { + // + } catch (Exception e) { + // + } + return elementName; + } + + /** + * Action to go back to the previous input in the hover control. + * + * @since 3.4 + */ + private static final class BackAction extends Action { + private final CDocBrowserInformationControl fInfoControl; + + public BackAction(CDocBrowserInformationControl infoControl) { + fInfoControl= infoControl; + setText(CHoverMessages.CDocHover_back); + ISharedImages images = PlatformUI.getWorkbench().getSharedImages(); + setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_BACK)); + setDisabledImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_BACK_DISABLED)); + + update(); + } + + public void run() { + BrowserInformationControlInput previous = (BrowserInformationControlInput) fInfoControl.getInput().getPrevious(); + if (previous != null) { + fInfoControl.setInput(previous); + } + } + + public void update() { + BrowserInformationControlInput current = fInfoControl.getInput(); + + if (current != null && current.getPrevious() != null) { + BrowserInput previous = current.getPrevious(); + setToolTipText("Back to " + getBackForwardElementName(previous.toString())); + setEnabled(true); + } else { + setToolTipText(CHoverMessages.CDocHover_back); + setEnabled(false); + } + } + } + + /** + * Action to go forward to the next input in the hover control. + * + * @since 3.4 + */ + private static final class ForwardAction extends Action { + private final CDocBrowserInformationControl fInfoControl; + + public ForwardAction(CDocBrowserInformationControl infoControl) { + fInfoControl = infoControl; + setText(CHoverMessages.CDocHover_forward); + ISharedImages images = PlatformUI.getWorkbench().getSharedImages(); + setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_FORWARD)); + setDisabledImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_FORWARD_DISABLED)); + + update(); + } + + public void run() { + BrowserInformationControlInput next = (BrowserInformationControlInput) fInfoControl.getInput().getNext(); + if (next != null) { + fInfoControl.setInput(next); + } + } + + public void update() { + BrowserInformationControlInput current = fInfoControl.getInput(); + + if (current != null && current.getNext() != null) { + setToolTipText("Forward to " + getBackForwardElementName(current.getNext().toString())); + setEnabled(true); + } else { + setToolTipText(CHoverMessages.CDocHover_forward); + setEnabled(false); + } + } + } + + /** + * Presenter control creator. + * + * @since 3.3 + */ + public static final class PresenterControlCreator extends AbstractReusableInformationControlCreator { + + private IWorkbenchSite fSite; + + /** + * Creates a new PresenterControlCreator. + * + * @param site the site or null if none + * @since 3.6 + */ + public PresenterControlCreator(IWorkbenchSite site) { + fSite = site; + } + + /* + * @see org.eclipse.jdt.internal.ui.text.java.hover.AbstractReusableInformationControlCreator#doCreateInformationControl(org.eclipse.swt.widgets.Shell) + */ + public IInformationControl doCreateInformationControl(Shell parent) { + if (CDocBrowserInformationControl.isAvailable(parent)) { + ToolBarManager tbm = new ToolBarManager(SWT.FLAT); + String font = PreferenceConstants.EDITOR_TEXT_FONT; + CDocBrowserInformationControl iControl = new CDocBrowserInformationControl(parent, font, tbm); + + final BackAction backAction = new BackAction(iControl); + backAction.setEnabled(false); + tbm.add(backAction); + final ForwardAction forwardAction = new ForwardAction(iControl); + tbm.add(forwardAction); + forwardAction.setEnabled(false); + + IInputChangedListener inputChangeListener= new IInputChangedListener() { + public void inputChanged(Object newInput) { + backAction.update(); + forwardAction.update(); + } + }; + iControl.addInputChangeListener(inputChangeListener); + + tbm.update(true); + + addLinkListener(iControl); + return iControl; + + } else { + return new DefaultInformationControl(parent, true); + } + } + } + + /** + * Hover control creator. + * + * @since 3.3 + */ + public static final class HoverControlCreator extends AbstractReusableInformationControlCreator { + /** + * The information presenter control creator. + * @since 3.4 + */ + private final IInformationControlCreator fInformationPresenterControlCreator; + /** + * true to use the additional info affordance, false to use the hover affordance. + */ + private final boolean fAdditionalInfoAffordance; + + /** + * @param informationPresenterControlCreator control creator for enriched hover + * @since 3.4 + */ + public HoverControlCreator(IInformationControlCreator informationPresenterControlCreator) { + this(informationPresenterControlCreator, false); + } + + /** + * @param informationPresenterControlCreator control creator for enriched hover + * @param additionalInfoAffordance true to use the additional info affordance, false to use the hover affordance + * @since 3.4 + */ + public HoverControlCreator(IInformationControlCreator informationPresenterControlCreator, boolean additionalInfoAffordance) { + fInformationPresenterControlCreator = informationPresenterControlCreator; + fAdditionalInfoAffordance = additionalInfoAffordance; + } + + /* + * @see org.eclipse.jdt.internal.ui.text.java.hover.AbstractReusableInformationControlCreator#doCreateInformationControl(org.eclipse.swt.widgets.Shell) + */ + public IInformationControl doCreateInformationControl(Shell parent) { + String tooltipAffordanceString = fAdditionalInfoAffordance ? "showTextHoverAffordance" : EditorsUI.getTooltipAffordanceString(); + if (CDocBrowserInformationControl.isAvailable(parent)) { + String font= PreferenceConstants.EDITOR_TEXT_FONT; + CDocBrowserInformationControl iControl = new CDocBrowserInformationControl(parent, font, tooltipAffordanceString) { + /* + * @see org.eclipse.jface.text.IInformationControlExtension5#getInformationPresenterControlCreator() + */ + public IInformationControlCreator getInformationPresenterControlCreator() { + return fInformationPresenterControlCreator; + } + }; + addLinkListener(iControl); + return iControl; + } else { + return new DefaultInformationControl(parent, tooltipAffordanceString); + } + } + + /* + * @see org.eclipse.jdt.internal.ui.text.java.hover.AbstractReusableInformationControlCreator#canReuse(org.eclipse.jface.text.IInformationControl) + */ + public boolean canReuse(IInformationControl control) { + if (!super.canReuse(control)) + return false; + + if (control instanceof IInformationControlExtension4) { + String tooltipAffordanceString = fAdditionalInfoAffordance ? "showTextHoverAffordance" : EditorsUI.getTooltipAffordanceString(); + ((IInformationControlExtension4)control).setStatusText(tooltipAffordanceString); + } + + return true; + } + } + + /** + * The hover control creator. + * + * @since 3.2 + */ + private IInformationControlCreator fHoverControlCreator; + /** + * The presentation control creator. + * + * @since 3.2 + */ + private IInformationControlCreator fPresenterControlCreator; } diff --git a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CElementLinks.java b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CElementLinks.java new file mode 100644 index 0000000..4430dc1 --- /dev/null +++ b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CElementLinks.java @@ -0,0 +1,100 @@ +package org.eclipse.cdt.internal.ui.text.c.hover; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; + +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.browser.LocationAdapter; +import org.eclipse.swt.browser.LocationEvent; +import org.eclipse.swt.browser.LocationListener; + +/** + * Links inside doxygen hovers. + */ +public class CElementLinks { + /** + * A handler is asked to handle links to targets. + */ + public interface ILinkHandler { + + /** + * Handle normal kind of link to given target. + * + * @param target the target to show + */ + void handleInlineCDocLink(String target); + + /** + * Informs the handler that the text of the browser was set. + */ + void handleTextSet(); + } + + public static final String CDOC_SCHEME= "file"; //$NON-NLS-1$ + public static int isSet = 0; + + private CElementLinks() { + // static only + } + + /** + * Creates a location listener which uses the given handler + * to handle java element links. + * + * The location listener can be attached to a {@link Browser} + * + * @param handler the handler to use to handle links + * @return a new {@link LocationListener} + */ + public static LocationListener createLocationListener(final ILinkHandler handler) { + return new LocationAdapter() { + public void changing(LocationEvent event) { + String loc = event.location; + if (CElementLinks.isSet == 1) { + CElementLinks.isSet = 0; + return; + } + + if ("about:blank".equals(loc)) { //$NON-NLS-1$ + /* + * Using the Browser.setText API triggers a location change to "about:blank". + * XXX: remove this code once https://bugs.eclipse.org/bugs/show_bug.cgi?id=130314 is fixed + */ + //input set with setText + handler.handleTextSet(); + return; + } + + event.doit= false; + + if (loc.startsWith("about:")) { //$NON-NLS-1$ + // Relative links should be handled via head > base tag. + // If no base is available, links just won't work. + return; + } + + URI uri; + try { + uri= new URI(loc); + } catch (URISyntaxException e) { + // try it with a file (workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=237903 ): + File file= new File(loc); + if (!file.exists()) { + //JavaPlugin.log(e); + return; + } + uri = file.toURI(); + loc = uri.toASCIIString(); + } + + String scheme = uri.getScheme(); + if (CElementLinks.CDOC_SCHEME.equals(scheme)) { + String uriString = uri.toString(); + handler.handleInlineCDocLink(uriString.substring(uriString.lastIndexOf("/") + 1, uriString.length())); + event.doit= true; + } + } + }; + } +} diff --git a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CHoverMessages.java b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CHoverMessages.java index 9838be3..6f63c01 100644 --- a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CHoverMessages.java +++ b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CHoverMessages.java @@ -33,6 +33,9 @@ public final class CHoverMessages extends NLS { public static String CSourceHover_jobTitle; + public static String CDocHover_back; + public static String CDocHover_forward; + static { NLS.initializeMessages(CHoverMessages.class.getName(), CHoverMessages.class); } diff --git a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CHoverMessages.properties b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CHoverMessages.properties index cb14e7a..b44e269 100644 --- a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CHoverMessages.properties +++ b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CHoverMessages.properties @@ -26,3 +26,6 @@ CMacroExpansionControl_title_original=Original CMacroExpansionInput_jobTitle= Computing Macro Expansion CSourceHover_jobTitle= Computing Source + +CDocHover_back=Back +CDocHover_forward=Forward diff --git a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/DOMCompletionProposalComputer.java b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/DOMCompletionProposalComputer.java index 969caba..ced0020 100644 --- a/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/DOMCompletionProposalComputer.java +++ b/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/DOMCompletionProposalComputer.java @@ -17,25 +17,24 @@ package org.eclipse.cdt.internal.ui.text.contentassist; import java.util.ArrayList; import java.util.List; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.ITypedRegion; -import org.eclipse.jface.text.TextUtilities; -import org.eclipse.jface.text.contentassist.ICompletionProposal; -import org.eclipse.swt.graphics.Image; - +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.IPositionConverter; +import org.eclipse.cdt.core.dom.IName; import org.eclipse.cdt.core.dom.ast.ASTTypeUtil; import org.eclipse.cdt.core.dom.ast.DOMException; import org.eclipse.cdt.core.dom.ast.IASTCompletionContext; import org.eclipse.cdt.core.dom.ast.IASTCompletionNode; import org.eclipse.cdt.core.dom.ast.IASTDeclarator; +import org.eclipse.cdt.core.dom.ast.IASTFileLocation; +import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition; import org.eclipse.cdt.core.dom.ast.IASTFunctionStyleMacroParameter; import org.eclipse.cdt.core.dom.ast.IASTIdExpression; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTNamedTypeSpecifier; +import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorFunctionStyleMacroDefinition; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition; +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.ICompositeType; import org.eclipse.cdt.core.dom.ast.IEnumeration; @@ -43,7 +42,9 @@ import org.eclipse.cdt.core.dom.ast.IEnumerator; import org.eclipse.cdt.core.dom.ast.IField; import org.eclipse.cdt.core.dom.ast.IFunction; import org.eclipse.cdt.core.dom.ast.IFunctionType; +import org.eclipse.cdt.core.dom.ast.IMacroBinding; import org.eclipse.cdt.core.dom.ast.IParameter; +import org.eclipse.cdt.core.dom.ast.IProblemBinding; import org.eclipse.cdt.core.dom.ast.IScope; import org.eclipse.cdt.core.dom.ast.IType; import org.eclipse.cdt.core.dom.ast.ITypedef; @@ -62,13 +63,18 @@ import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionTemplate; import org.eclipse.cdt.core.dom.ast.cpp.ICPPMember; import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod; import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPSpecialization; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateDefinition; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateParameter; import org.eclipse.cdt.core.dom.ast.cpp.ICPPUsingDeclaration; +import org.eclipse.cdt.core.dom.ast.gnu.c.ICASTKnRFunctionDeclarator; +import org.eclipse.cdt.core.index.IIndex; +import org.eclipse.cdt.core.index.IIndexName; +import org.eclipse.cdt.core.model.ITranslationUnit; +import org.eclipse.cdt.core.model.IWorkingCopy; import org.eclipse.cdt.core.parser.ast.ASTAccessVisibility; import org.eclipse.cdt.core.parser.util.CharArrayUtils; import org.eclipse.cdt.core.parser.util.IContentAssistMatcher; -import org.eclipse.cdt.ui.CUIPlugin; -import org.eclipse.cdt.ui.text.ICPartitions; - import org.eclipse.cdt.internal.core.dom.parser.c.CBuiltinParameter; import org.eclipse.cdt.internal.core.dom.parser.c.CBuiltinVariable; import org.eclipse.cdt.internal.core.dom.parser.c.CImplicitFunction; @@ -80,8 +86,36 @@ import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPImplicitMethod; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPImplicitTypedef; import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.AccessContext; import org.eclipse.cdt.internal.core.parser.util.ContentAssistMatcherFactory; - +import org.eclipse.cdt.internal.ui.text.CHeuristicScanner; +import org.eclipse.cdt.internal.ui.util.EditorUtility; import org.eclipse.cdt.internal.ui.viewsupport.CElementImageProvider; +import org.eclipse.cdt.ui.CUIPlugin; +import org.eclipse.cdt.ui.IWorkingCopyManager; +import org.eclipse.cdt.ui.text.ICPartitions; +import org.eclipse.core.filebuffers.FileBuffers; +import org.eclipse.core.filebuffers.ITextFileBuffer; +import org.eclipse.core.filebuffers.ITextFileBufferManager; +import org.eclipse.core.filebuffers.LocationKind; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextUtilities; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; /** * Searches the DOM (both the AST and the index) for completion proposals. @@ -89,20 +123,42 @@ import org.eclipse.cdt.internal.ui.viewsupport.CElementImageProvider; * @author Bryan Wilkinson */ public class DOMCompletionProposalComputer extends ParsingBasedProposalComputer { + private final IProgressMonitor fMonitor; + private ITranslationUnit fTU; /** * Default constructor is required (executable extension). */ public DOMCompletionProposalComputer() { + fMonitor = new NullProgressMonitor(); + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IWorkingCopy copy = null; + if (window != null) { + IWorkbenchPage page = window.getActivePage(); + if (page != null) { + IEditorPart editor = page.getActiveEditor(); + if (editor != null) { + IEditorInput input = editor.getEditorInput(); + IWorkingCopyManager manager = CUIPlugin.getDefault().getWorkingCopyManager(); + copy = manager.getWorkingCopy(input); + fTU = copy; + } + } + } } - + @Override protected List computeCompletionProposals( CContentAssistInvocationContext context, - IASTCompletionNode completionNode, String prefix) { + IASTCompletionNode completionNode, String prefix) throws CoreException { List proposals = new ArrayList(); - + + int indices[] = new int[200]; + for (int i = 0; i < indices.length; i++) { + indices[i] = -1; + } + if (inPreprocessorDirective(context)) { if (!inPreprocessorKeyword(context)) { // add only macros @@ -116,6 +172,9 @@ public class DOMCompletionProposalComputer extends ParsingBasedProposalComputer addMacroProposals(context, prefix, proposals); } } else { + IASTTranslationUnit ast = completionNode.getTranslationUnit(); + String source = null; + boolean handleMacros= false; IASTName[] names = completionNode.getNames(); @@ -123,7 +182,7 @@ public class DOMCompletionProposalComputer extends ParsingBasedProposalComputer if (name.getTranslationUnit() == null) // The node isn't properly hooked up, must have backtracked out of this node continue; - + IASTCompletionContext astContext = name.getCompletionContext(); if (astContext == null) { continue; @@ -132,14 +191,25 @@ public class DOMCompletionProposalComputer extends ParsingBasedProposalComputer // handle macros only if there is a prefix handleMacros = prefix.length() > 0; } - + IBinding[] bindings = astContext.findBindings(name, !context.isContextInformationStyle()); - + if (bindings != null) { + int index = 0; + int index2 = 0; AccessContext accessibilityContext = new AccessContext(name); for (IBinding binding : bindings) { - if (accessibilityContext.isAccessible(binding)) + if (accessibilityContext.isAccessible(binding)) { handleBinding(binding, context, prefix, astContext, proposals); + + source = computeSourceForBinding(ast, binding); + if (source != null) { + if ( !source.contains("@class") && !source.contains("@interface") && source.contains("@internal") ) { + indices[index2++] = index; + } + index++; + } + } } } } @@ -147,11 +217,435 @@ public class DOMCompletionProposalComputer extends ParsingBasedProposalComputer if (handleMacros) addMacroProposals(context, prefix, proposals); } - + + for (int i = indices.length - 1; i > -1; i--) { + if (indices[i] != -1) { + proposals.remove(indices[i]); + } + } + return proposals; } /** + * Find a definition or declaration for the given binding and returns the source for it. + * Definitions are preferred over declarations. In case of multiple definitions or declarations, + * and the first name which yields source is taken. + * + * @param ast the AST of the translation unit + * @param binding the binding + * @return a source string or null, if no source could be computed + * @throws CoreException if the source file could not be loaded or if there was a + * problem with the index + */ + private String computeSourceForBinding(IASTTranslationUnit ast, IBinding binding) throws CoreException { + IName[] names = findDefsOrDecls(ast, binding); + + // in case the binding is a non-explicit specialization we need + // to consider the original binding (bug 281396) + if (names.length == 0 && binding instanceof ICPPSpecialization) { + binding= ((ICPPSpecialization) binding).getSpecializedBinding(); + if (!(binding instanceof IProblemBinding)) { + names= findDefsOrDecls(ast, binding); + } + } + if (names.length > 0) { + for (IName name : names) { + String source= computeSourceForName(name, binding); + if (source != null) { + return source; + } + } + } + return null; + } + /** + * Search for definitions or declarations for the given binding. + * @param ast the AST of the translation unit + * @param binding the binding + * @return an array of definitions or declarations, never null + * @throws CoreException + */ + private IName[] findDefsOrDecls(IASTTranslationUnit ast, IBinding binding) throws CoreException { + IName[] names= findDefinitions(ast, binding); + if (names.length == 0) { + names= findDeclarations(ast, binding); + } + return names; + } + /** + * Search for definitions for the given binding. + * + * @param ast the AST of the translation unit + * @param binding the binding + * @return an array of definitions, never null + * @throws CoreException + */ + private IName[] findDefinitions(IASTTranslationUnit ast, IBinding binding) throws CoreException { + IName[] declNames= ast.getDefinitionsInAST(binding); + if (declNames.length == 0 && ast.getIndex() != null) { + // search definitions in index + declNames = ast.getIndex().findNames(binding, IIndex.FIND_DEFINITIONS | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES); + } + return declNames; + } + /** + * Search for declarations for the given binding. + * + * @param ast the AST of the translation unit + * @param binding the binding + * @return an array of declarations, never null + * @throws CoreException + */ + private IName[] findDeclarations(IASTTranslationUnit ast, IBinding binding) throws CoreException { + IName[] declNames= ast.getDeclarationsInAST(binding); + if (declNames.length == 0 && ast.getIndex() != null) { + // search declarations in index + declNames= ast.getIndex().findNames(binding, IIndex.FIND_DECLARATIONS | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES); + } + return declNames; + } + /** + * Get the source for the given name from the underlying file. + * + * @param name the name to get the source for + * @param binding the binding of the name + * @return the source string or null, if the source could not be computed + * @throws CoreException if the file could not be loaded + */ + private String computeSourceForName(IName name, IBinding binding) throws CoreException { + IASTFileLocation fileLocation= name.getFileLocation(); + if (fileLocation == null) { + return null; + } + int nodeOffset= fileLocation.getNodeOffset(); + int nodeLength= fileLocation.getNodeLength(); + + String fileName= fileLocation.getFileName(); + //if (DEBUG) System.out.println("[CSourceHover] Computing source for " + name + " in " + fileName); //$NON-NLS-1$//$NON-NLS-2$ + IPath location= Path.fromOSString(fileName); + LocationKind locationKind= LocationKind.LOCATION; + if (name instanceof IASTName && !name.isReference()) { + IASTName astName= (IASTName)name; + if (astName.getTranslationUnit().getFilePath().equals(fileName) && fTU.getResource() != null) { + // reuse editor buffer for names local to the translation unit + location= fTU.getResource().getFullPath(); + locationKind= LocationKind.IFILE; + } + } else { + // try to resolve path to a resource for proper encoding (bug 221029) + IFile file= EditorUtility.getWorkspaceFileAtLocation(location, fTU); + if (file != null) { + location= file.getFullPath(); + locationKind= LocationKind.IFILE; + if (name instanceof IIndexName) { + // need to adjust index offsets to current offsets + // in case file has been modified since last index time + IIndexName indexName= (IIndexName) name; + long timestamp= indexName.getFile().getTimestamp(); + IPositionConverter converter= CCorePlugin.getPositionTrackerManager().findPositionConverter(file, timestamp); + if (converter != null) { + IRegion currentLocation= converter.historicToActual(new Region(nodeOffset, nodeLength)); + nodeOffset= currentLocation.getOffset(); + nodeLength= currentLocation.getLength(); + } + } + } + } + ITextFileBufferManager mgr= FileBuffers.getTextFileBufferManager(); + mgr.connect(location, locationKind, fMonitor); + ITextFileBuffer buffer= mgr.getTextFileBuffer(location, locationKind); + try { + IRegion nameRegion= new Region(nodeOffset, nodeLength); + final int nameOffset= nameRegion.getOffset(); + final int sourceStart; + final int sourceEnd; + IDocument doc= buffer.getDocument(); + if (nameOffset >= doc.getLength() || nodeLength <= 0) { + return null; + } + if (binding instanceof IMacroBinding) { + ITypedRegion partition= TextUtilities.getPartition(doc, ICPartitions.C_PARTITIONING, nameOffset, false); + if (ICPartitions.C_PREPROCESSOR.equals(partition.getType())) { + int directiveStart= partition.getOffset(); + int commentStart= searchCommentBackward(doc, directiveStart, -1); + if (commentStart >= 0) { + sourceStart= commentStart; + } else { + sourceStart= directiveStart; + } + sourceEnd= directiveStart + partition.getLength(); + } else { + return null; + } + } else { + // expand source range to include preceding comment, if any + boolean isKnR= isKnRSource(name); + sourceStart= computeSourceStart(doc, nameOffset, binding, isKnR); + if (sourceStart == CHeuristicScanner.NOT_FOUND) { + return null; + } + sourceEnd= computeSourceEnd(doc, nameOffset + nameRegion.getLength(), binding, name.isDefinition(), isKnR); + } + String source= buffer.getDocument().get(sourceStart, sourceEnd - sourceStart); + return source; + + } catch (BadLocationException exc) { + // ignore - should not happen anyway + //if (DEBUG) exc.printStackTrace(); + } finally { + mgr.disconnect(location, LocationKind.LOCATION, fMonitor); + } + return null; + } + /** + * Searches the start of the comment preceding the given source offset. + * Continuous line comments are considered as one comment until a block + * comment is reached or a non-comment partition. + * + * @param doc the document + * @param start the start of the backward search + * @param bound search boundary (exclusive) + * @return the comment start offset or -1, if no suitable comment was found + * @throws BadLocationException + */ + private static int searchCommentBackward(IDocument doc, int start, int bound) throws BadLocationException { + int firstLine= doc.getLineOfOffset(start); + if (firstLine == 0) { + return 0; + } + ITypedRegion partition= TextUtilities.getPartition(doc, ICPartitions.C_PARTITIONING, start, true); + int currentOffset= Math.max(doc.getLineOffset(firstLine - 1), partition.getOffset() - 1); + int commentOffset= -1; + while (currentOffset > bound) { + partition= TextUtilities.getPartition(doc, ICPartitions.C_PARTITIONING, currentOffset, true); + currentOffset= partition.getOffset() - 1; + if (ICPartitions.C_MULTI_LINE_COMMENT.equals(partition.getType()) + || ICPartitions.C_MULTI_LINE_DOC_COMMENT.equals(partition.getType())) { + final int partitionOffset= partition.getOffset(); + final int startLine= doc.getLineOfOffset(partitionOffset); + final int lineOffset= doc.getLineOffset(startLine); + if (partitionOffset == lineOffset || + doc.get(lineOffset, partitionOffset - lineOffset).trim().length() == 0) { + return lineOffset; + } + return commentOffset; + } else if (ICPartitions.C_SINGLE_LINE_COMMENT.equals(partition.getType()) + || ICPartitions.C_SINGLE_LINE_DOC_COMMENT.equals(partition.getType())) { + final int partitionOffset= partition.getOffset(); + final int startLine= doc.getLineOfOffset(partitionOffset); + final int lineOffset= doc.getLineOffset(startLine); + if (partitionOffset == lineOffset || + doc.get(lineOffset, partitionOffset - lineOffset).trim().length() == 0) { + commentOffset= lineOffset; + continue; + } + return commentOffset; + } else if (IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) { + if (doc.get(partition.getOffset(), partition.getLength()).trim().length() == 0) { + continue; + } + if (commentOffset >= 0) { + break; + } + } else { + break; + } + } + return commentOffset; + } + /** + * Determine if the name is part of a KnR function definition. + * @param name + * @return true if the name is part of a KnR function + */ + private boolean isKnRSource(IName name) { + if (name instanceof IASTName) { + IASTNode node= (IASTNode)name; + while (node.getParent() != null) { + if (node instanceof ICASTKnRFunctionDeclarator) { + return node.getParent() instanceof IASTFunctionDefinition; + } + node= node.getParent(); + } + } + return false; + } + private int computeSourceStart(IDocument doc, int nameOffset, IBinding binding, boolean isKnR) throws BadLocationException { + int sourceStart= nameOffset; + CHeuristicScanner scanner= new CHeuristicScanner(doc); + if (binding instanceof IParameter) { + if (isKnR) { + sourceStart= scanner.scanBackward(nameOffset, CHeuristicScanner.UNBOUND, new char[] { ')', ';' }); + } else { + sourceStart= scanner.scanBackward(nameOffset, CHeuristicScanner.UNBOUND, new char[] { '>', '(', ',' }); + if (sourceStart > 0 && doc.getChar(sourceStart) == '>') { + sourceStart= scanner.findOpeningPeer(sourceStart - 1, '<', '>'); + if (sourceStart > 0) { + sourceStart= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { '(', ',' }); + } + } + } + if (sourceStart == CHeuristicScanner.NOT_FOUND) { + return sourceStart; + } + sourceStart= scanner.findNonWhitespaceForward(sourceStart + 1, nameOffset); + if (sourceStart == CHeuristicScanner.NOT_FOUND) { + sourceStart = nameOffset; + } + } else if (binding instanceof ICPPTemplateParameter) { + sourceStart= scanner.scanBackward(nameOffset, CHeuristicScanner.UNBOUND, new char[] { '>', '<', ',' }); + if (sourceStart > 0 && doc.getChar(sourceStart) == '>') { + sourceStart= scanner.findOpeningPeer(sourceStart - 1, '<', '>'); + if (sourceStart > 0) { + sourceStart= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { '<', ',' }); + } + } + if (sourceStart == CHeuristicScanner.NOT_FOUND) { + return sourceStart; + } + sourceStart= scanner.findNonWhitespaceForward(sourceStart + 1, nameOffset); + if (sourceStart == CHeuristicScanner.NOT_FOUND) { + sourceStart = nameOffset; + } + } else if (binding instanceof IEnumerator) { + sourceStart= scanner.scanBackward(nameOffset, CHeuristicScanner.UNBOUND, new char[] { '{', ',' }); + if (sourceStart == CHeuristicScanner.NOT_FOUND) { + return sourceStart; + } + sourceStart= scanner.findNonWhitespaceForward(sourceStart + 1, nameOffset); + if (sourceStart == CHeuristicScanner.NOT_FOUND) { + sourceStart = nameOffset; + } + } else { + final boolean expectClosingBrace; + IType type= null; + if (binding instanceof ITypedef) { + type= ((ITypedef)binding).getType(); + } else if (binding instanceof IVariable) { + type= ((IVariable)binding).getType(); + } + expectClosingBrace= type instanceof ICompositeType || type instanceof IEnumeration; + final int nameLine= doc.getLineOfOffset(nameOffset); + sourceStart= nameOffset; + int commentBound; + if (isKnR) { + commentBound= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { ')', ';' }); + } else { + commentBound= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { '{', '}', ';' }); + } + while (expectClosingBrace && commentBound > 0 && doc.getChar(commentBound) == '}') { + int openingBrace= scanner.findOpeningPeer(commentBound - 1, '{', '}'); + if (openingBrace != CHeuristicScanner.NOT_FOUND) { + sourceStart= openingBrace - 1; + } + if (isKnR) { + commentBound= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { ')', ';' }); + } else { + commentBound= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { '{', '}', ';' }); + } + } + if (commentBound == CHeuristicScanner.NOT_FOUND) { + commentBound= -1; // unbound + } + sourceStart= Math.min(sourceStart, doc.getLineOffset(nameLine)); + int commentStart= searchCommentBackward(doc, sourceStart, commentBound); + if (commentStart >= 0) { + sourceStart= commentStart; + } else { + int nextNonWS= scanner.findNonWhitespaceForward(commentBound+1, sourceStart); + if (nextNonWS != CHeuristicScanner.NOT_FOUND) { + int nextNonWSLine= doc.getLineOfOffset(nextNonWS); + int lineOffset= doc.getLineOffset(nextNonWSLine); + if (doc.get(lineOffset, nextNonWS - lineOffset).trim().length() == 0) { + sourceStart= doc.getLineOffset(nextNonWSLine); + } + } + } + } + return sourceStart; + } + private int computeSourceEnd(IDocument doc, int start, IBinding binding, boolean isDefinition, boolean isKnR) throws BadLocationException { + int sourceEnd= start; + CHeuristicScanner scanner= new CHeuristicScanner(doc); + // expand forward to the end of the definition/declaration + boolean searchBrace= false; + boolean searchSemi= false; + boolean searchComma= false; + if (binding instanceof ICompositeType || binding instanceof IEnumeration) { + searchBrace= true; + } else if (binding instanceof ICPPTemplateDefinition) { + searchBrace= true; + } else if (binding instanceof IFunction && isDefinition) { + searchBrace= true; + } else if (binding instanceof IParameter) { + if (isKnR) { + searchSemi= true; + } else { + searchComma= true; + } + } else if (binding instanceof IEnumerator || binding instanceof ICPPTemplateParameter) { + searchComma= true; + } else if (binding instanceof IVariable || binding instanceof ITypedef) { + searchSemi= true; + } else if (!isDefinition) { + searchSemi= true; + } + if (searchBrace) { + int brace= scanner.scanForward(start, CHeuristicScanner.UNBOUND, '{'); + if (brace != CHeuristicScanner.NOT_FOUND) { + sourceEnd= scanner.findClosingPeer(brace + 1, '{', '}'); + if (sourceEnd == CHeuristicScanner.NOT_FOUND) { + sourceEnd= doc.getLength(); + } + } + // expand region to include whole line + IRegion lineRegion= doc.getLineInformationOfOffset(sourceEnd); + sourceEnd= lineRegion.getOffset() + lineRegion.getLength(); + } else if (searchSemi) { + int semi= scanner.scanForward(start, CHeuristicScanner.UNBOUND, ';'); + if (semi != CHeuristicScanner.NOT_FOUND) { + sourceEnd= semi+1; + } + // expand region to include whole line + IRegion lineRegion= doc.getLineInformationOfOffset(sourceEnd); + sourceEnd= lineRegion.getOffset() + lineRegion.getLength(); + } else if (searchComma) { + int bound; + if (binding instanceof IParameter) { + bound= scanner.findClosingPeer(start, '(', ')'); + } else if (binding instanceof ICPPTemplateParameter) { + bound= scanner.findClosingPeer(start, '<', '>'); + } else if (binding instanceof IEnumerator) { + bound= scanner.findClosingPeer(start, '{', '}'); + } else { + bound = CHeuristicScanner.NOT_FOUND; + } + if (bound == CHeuristicScanner.NOT_FOUND) { + bound= Math.min(doc.getLength(), start + 100); + } + int comma= scanner.scanForward(start, bound, ','); + if (comma == CHeuristicScanner.NOT_FOUND) { + // last argument + sourceEnd= bound; + } else { + sourceEnd= comma; + // expand region to include whole line if rest is comment + IRegion lineRegion= doc.getLineInformationOfOffset(sourceEnd); + int lineEnd= lineRegion.getOffset() + lineRegion.getLength(); + int nextNonWS= scanner.findNonWhitespaceForwardInAnyPartition(sourceEnd + 1, lineEnd); + if (nextNonWS != CHeuristicScanner.NOT_FOUND) { + String contentType= TextUtilities.getContentType(doc, ICPartitions.C_PARTITIONING, nextNonWS, false); + if (ICPartitions.C_MULTI_LINE_COMMENT.equals(contentType) || ICPartitions.C_SINGLE_LINE_COMMENT.equals(contentType)) { + sourceEnd= lineEnd; + } + } + } + } + return sourceEnd; + } + + /** * Test whether the invocation offset is inside or before the preprocessor directive keyword. * * @param context the invocation context diff --git a/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/CUIPreferenceInitializer.java b/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/CUIPreferenceInitializer.java index 60d6458..b97837d 100644 --- a/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/CUIPreferenceInitializer.java +++ b/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/CUIPreferenceInitializer.java @@ -10,6 +10,14 @@ *******************************************************************************/ package org.eclipse.cdt.ui; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + import org.eclipse.cdt.internal.ui.cview.CView; import org.eclipse.cdt.internal.ui.editor.SemanticHighlightings; import org.eclipse.cdt.internal.ui.preferences.BuildConsolePreferencePage; @@ -18,6 +26,7 @@ import org.eclipse.cdt.internal.ui.preferences.CPluginPreferencePage; import org.eclipse.cdt.internal.ui.preferences.CodeAssistPreferencePage; import org.eclipse.cdt.internal.ui.preferences.WorkInProgressPreferencePage; +import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.ui.editors.text.EditorsUI; @@ -30,7 +39,13 @@ import org.eclipse.ui.texteditor.AbstractTextEditor; * @noextend This class is not intended to be subclassed by clients. */ public class CUIPreferenceInitializer extends AbstractPreferenceInitializer { - + + private static boolean isFirst = true; + private static final String path = "plugins"; + private static final String fileName = "com.osp.cppapireference.help"; + public static String doxygenJarFilePath; + public static String doxygenDirectoryPath; + /* (non-Javadoc) * @see org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer#initializeDefaultPreferences() */ @@ -38,7 +53,7 @@ public class CUIPreferenceInitializer extends AbstractPreferenceInitializer { public void initializeDefaultPreferences() { final IPreferenceStore store = CUIPlugin.getDefault().getPreferenceStore(); - PreferenceConstants.initializeDefaultValues(store); + PreferenceConstants.initializeDefaultValues(store); CPluginPreferencePage.initDefaults(store); BuildConsolePreferencePage.initDefaults(store); CView.initDefaults(store); @@ -52,14 +67,84 @@ public class CUIPreferenceInitializer extends AbstractPreferenceInitializer { // All of those settings are now in the workbench "All TextEditor" preference Page. // Later we should remove this calls, after CDT-3.0 EditorsUI.useAnnotationsPreferencePage(store); - EditorsUI.useQuickDiffPreferencePage(store); + EditorsUI.useQuickDiffPreferencePage(store); useTextEditorPreferencePage(store); + + if (isFirst == true) { + setPath(); + extractJarFile(); + } + } + + /** + * Set doxygenJarFilePath and doxygenDirectoryPath. + */ + private void setPath() { + isFirst = false; + String eclipseRootPath = Platform.getInstallLocation().getURL().getPath(); + eclipseRootPath = eclipseRootPath.substring(1, eclipseRootPath.length()); + + String eclipsePluginPath = eclipseRootPath + path; + String doxygenJarFileName = null; + File file = new File(eclipsePluginPath); + File fileList[] = file.listFiles(); + for (int i = 0; i < fileList.length; i++) { + if (fileList[i].getName().contains(fileName)) { + doxygenJarFileName = fileList[i].getName(); + } + } + + doxygenJarFilePath = eclipseRootPath + path + "/" + doxygenJarFileName; + doxygenDirectoryPath = eclipseRootPath + path + "/" + fileName; + } + + /** + * Extract jar file. + */ + private void extractJarFile() { + try { + JarFile jarFile = new JarFile(doxygenJarFilePath); + File destinationDir = new File(doxygenDirectoryPath); + if (!destinationDir.exists()) { + destinationDir.mkdirs(); + } else if (destinationDir.exists()) { + return; + } + for (Enumeration entries = jarFile.entries(); entries.hasMoreElements(); ) { + JarEntry entry = entries.nextElement(); + if (entry.isDirectory()) { + if (entry.getName().toUpperCase().indexOf("META-INF") != -1) { + continue; + } + File newDirectory = new File(destinationDir, entry.getName()); + newDirectory.mkdirs(); + } else { + if (entry.getName().toUpperCase().indexOf("META-INF") != -1) { + continue; + } + File output = new File(destinationDir, entry.getName()); + InputStream inputStream = jarFile.getInputStream(entry); + BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output)); + byte[] buf = new byte[4096]; + int read = -1; + while ( (read = inputStream.read(buf)) != -1 ) { + outputStream.write(buf, 0, read); + } + outputStream.flush(); + inputStream.close(); + outputStream.close(); + } + } + } catch (Exception e) { + // + } } /* - * Reset to default, those constants that are no longer maintained in CUIPlugin store. + * reset to default, those constants are no longer maintain int CUIPlugin store. */ public static void useTextEditorPreferencePage(IPreferenceStore store) { + store.setToDefault(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE); store.setToDefault(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR); @@ -88,4 +173,5 @@ public class CUIPreferenceInitializer extends AbstractPreferenceInitializer { store.setToDefault(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_DISABLE_OVERWRITE_MODE); store.setToDefault(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SMART_HOME_END); } + } -- 2.7.4