package org.rubypeople.rdt.internal.debug.ui.actions; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IBreakpointManager; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.ui.actions.IToggleBreakpointsTargetExtension; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; import org.jruby.ast.RootNode; import org.jruby.parser.RubyParserResult; import org.rubypeople.rdt.core.IMember; import org.rubypeople.rdt.core.IMethod; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.IRubyScript; import org.rubypeople.rdt.core.ISourceRange; import org.rubypeople.rdt.core.IType; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.debug.core.IRubyBreakpoint; import org.rubypeople.rdt.debug.core.IRubyLineBreakpoint; import org.rubypeople.rdt.debug.core.IRubyMethodBreakpoint; import org.rubypeople.rdt.debug.core.RdtDebugModel; import org.rubypeople.rdt.internal.core.parser.RubyParser; import org.rubypeople.rdt.internal.debug.ui.BreakpointUtils; import org.rubypeople.rdt.internal.debug.ui.DebugWorkingCopyManager; import org.rubypeople.rdt.internal.debug.ui.RdtDebugUiPlugin; import org.rubypeople.rdt.internal.ui.rubyeditor.IRubyScriptEditorInput; import org.rubypeople.rdt.ui.IWorkingCopyManager; import org.rubypeople.rdt.ui.RubyUI; public class ToggleBreakpointAdapter implements IToggleBreakpointsTargetExtension { public boolean canToggleBreakpoints(IWorkbenchPart part, ISelection selection) { if (isRemote(part, selection)) { return false; } return canToggleLineBreakpoints(part, selection); } public void toggleBreakpoints(IWorkbenchPart part, ISelection selection) throws CoreException { ISelection sel = translateToMembers(part, selection); if (sel instanceof IStructuredSelection) { IMember member = (IMember) ((IStructuredSelection) sel).getFirstElement(); int mtype = member.getElementType(); if (mtype == IRubyElement.FIELD || mtype == IRubyElement.METHOD) { // remove line breakpoint if present first if (selection instanceof ITextSelection) { ITextSelection ts = (ITextSelection) selection; IType declaringType = member.getDeclaringType(); IResource resource = BreakpointUtils.getBreakpointResource(declaringType); IRubyLineBreakpoint breakpoint = RdtDebugModel.lineBreakpointExists(resource, null, ts .getStartLine()); if (breakpoint != null) { breakpoint.delete(); return; } RootNode unit = parseRubyScript(getTextEditor(part)); ValidBreakpointLocationLocator loc = new ValidBreakpointLocationLocator(unit, ts.getStartLine(), true); unit.accept(loc); if (loc.getLocationType() == ValidBreakpointLocationLocator.LOCATION_METHOD) { toggleMethodBreakpoints(part, sel); } else if (loc.getLocationType() == ValidBreakpointLocationLocator.LOCATION_FIELD) { toggleWatchpoints(part, ts); } else if (loc.getLocationType() == ValidBreakpointLocationLocator.LOCATION_LINE) { toggleLineBreakpoints(part, ts); } else { // fall back to old behavior, always create a line breakpoint toggleLineBreakpoints(part, selection, true); } } } // else if(member.getElementType() == IRubyElement.TYPE) { // toggleClassBreakpoints(part, sel); // } else { // fall back to old behavior, always create a line breakpoint toggleLineBreakpoints(part, selection, true); } } else { toggleLineBreakpoints(part, selection, true); } } private RootNode parseRubyScript(ITextEditor textEditor) throws RubyModelException { IRubyScript script = getTypeRoot(textEditor.getEditorInput()); RubyParserResult result = new RubyParser().parse(script.getElementName(), script.getSource()); return (RootNode) result.getAST(); } public boolean canToggleLineBreakpoints(IWorkbenchPart part, ISelection selection) { if (isRemote(part, selection)) { return false; } return selection instanceof ITextSelection; } public boolean canToggleMethodBreakpoints(IWorkbenchPart part, ISelection selection) { if (isRemote(part, selection)) { return false; } if (selection instanceof IStructuredSelection) { IStructuredSelection ss = (IStructuredSelection) selection; return getMethods(ss).length > 0; } return (selection instanceof ITextSelection) && isMethod((ITextSelection) selection, part); } public boolean canToggleWatchpoints(IWorkbenchPart part, ISelection selection) { // TODO Auto-generated method stub return false; } public void toggleLineBreakpoints(IWorkbenchPart part, ISelection selection) throws CoreException { toggleLineBreakpoints(part, selection, false); } /** * Toggles a line breakpoint. * * @param part * the currently active workbench part * @param selection * the current selection * @param bestMatch * if we should make a best match or not */ public void toggleLineBreakpoints(final IWorkbenchPart part, final ISelection selection, final boolean bestMatch) { Job job = new Job("Toggle Line Breakpoint") { //$NON-NLS-1$ protected IStatus run(IProgressMonitor monitor) { ITextEditor editor = getTextEditor(part); if (editor != null && selection instanceof ITextSelection) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } try { ISelection sel = selection; if (!(selection instanceof IStructuredSelection)) { sel = translateToMembers(part, selection); } String tname = null; IRubyElement element = null; if (sel instanceof IStructuredSelection) { IMember member = (IMember) ((IStructuredSelection) sel).getFirstElement(); IType type = null; if (member.getElementType() == IRubyElement.TYPE) { type = (IType) member; } else { type = member.getDeclaringType(); } tname = createQualifiedTypeName(type); element = type; } // free form code not inside some method or class. if (tname == null) tname = "Object"; // toplevel if (element == null) element = getTypeRoot(editor.getEditorInput()); IResource resource = BreakpointUtils.getBreakpointResource(element); int lnumber = ((ITextSelection) selection).getStartLine(); IRubyLineBreakpoint existingBreakpoint = RdtDebugModel.lineBreakpointExists(resource, tname, lnumber + 1); if (existingBreakpoint != null) { DebugPlugin.getDefault().getBreakpointManager().removeBreakpoint(existingBreakpoint, true); return Status.OK_STATUS; } Map attributes = new HashMap(10); IDocumentProvider documentProvider = editor.getDocumentProvider(); if (documentProvider == null) { return Status.CANCEL_STATUS; } IDocument document = documentProvider.getDocument(editor.getEditorInput()); try { IRegion line = document.getLineInformation(lnumber - 1); int start = line.getOffset(); int end = start + line.getLength() - 1; BreakpointUtils.addRubyBreakpointAttributesWithMemberDetails(attributes, element, start, end); } catch (BadLocationException ble) { RdtDebugUiPlugin.log(ble); } RdtDebugModel.createLineBreakpoint(resource, getFileName(editor.getEditorInput()), tname, lnumber + 1, true, attributes); } catch (CoreException x) { System.out.println(x.getMessage()); } } return Status.OK_STATUS; } }; job.setSystem(true); job.schedule(); } private String getFileName(IEditorInput editorInput) { if (editorInput instanceof IRubyScriptEditorInput) return ((IRubyScriptEditorInput) editorInput).getRubyScript().getPath().makeAbsolute().toOSString(); return null; } public void toggleMethodBreakpoints(final IWorkbenchPart part, final ISelection finalSelection) throws CoreException { Job job = new Job("Toggle Method Breakpoints") { //$NON-NLS-1$ protected IStatus run(IProgressMonitor monitor) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } try { ISelection selection = finalSelection; if (!(selection instanceof IStructuredSelection)) { selection = translateToMembers(part, selection); } if (selection instanceof IStructuredSelection) { IMethod[] members = getMethods((IStructuredSelection) selection); if (members.length == 0) { // report(ActionMessages.ToggleBreakpointAdapter_9, part); return Status.OK_STATUS; } IRubyBreakpoint breakpoint = null; ISourceRange range = null; Map attributes = null; IType type = null; String mname = null; for (int i = 0, length = members.length; i < length; i++) { breakpoint = getMethodBreakpoint(members[i]); if (breakpoint == null) { int start = -1; int end = -1; range = members[i].getNameRange(); if (range != null) { start = range.getOffset(); end = start + range.getLength(); } attributes = new HashMap(10); BreakpointUtils.addRubyBreakpointAttributes(attributes, members[i]); type = members[i].getDeclaringType(); mname = members[i].getElementName(); if (members[i].isConstructor()) { mname = "<init>"; //$NON-NLS-1$ } RdtDebugModel.createMethodBreakpoint(BreakpointUtils.getBreakpointResource(members[i]), createQualifiedTypeName(type), mname, true, false, -1, start, end, 0, true, attributes); } else { DebugPlugin.getDefault().getBreakpointManager().removeBreakpoint(breakpoint, true); } } } else { // report(ActionMessages.ToggleBreakpointAdapter_4, part); return Status.OK_STATUS; } } catch (CoreException e) { return e.getStatus(); } return Status.OK_STATUS; } }; job.setSystem(true); job.schedule(); } protected String createQualifiedTypeName(IType type) { if (type == null) return null; return type.getFullyQualifiedName(); } /** * Returns the <code>IRubyBreakpoint</code> from the specified <code>IMember</code> * * @param element * the element to get the breakpoint from * @return the current breakpoint from the element or <code>null</code> */ protected IRubyBreakpoint getMethodBreakpoint(IMember element) { IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager(); IBreakpoint[] breakpoints = breakpointManager.getBreakpoints(RdtDebugModel.getModelIdentifier()); if (element instanceof IMethod) { IMethod method = (IMethod) element; for (int i = 0; i < breakpoints.length; i++) { IBreakpoint breakpoint = breakpoints[i]; if (breakpoint instanceof IRubyMethodBreakpoint) { IRubyMethodBreakpoint methodBreakpoint = (IRubyMethodBreakpoint) breakpoint; IMember container = null; try { container = BreakpointUtils.getMember(methodBreakpoint); } catch (CoreException e) { RdtDebugUiPlugin.log(e); return null; } if (container == null) { try { if (method.getDeclaringType().getFullyQualifiedName() .equals(methodBreakpoint.getTypeName()) && method.getElementName().equals(methodBreakpoint.getMethodName())) { return methodBreakpoint; } } catch (CoreException e) { RdtDebugUiPlugin.log(e); } } else { if (container instanceof IMethod) { if (method.getDeclaringType().getFullyQualifiedName().equals( container.getDeclaringType().getFullyQualifiedName())) { if (method.isSimilar((IMethod) container)) { return methodBreakpoint; } } } } } } } return null; } public void toggleWatchpoints(IWorkbenchPart part, ISelection selection) throws CoreException { // TODO Auto-generated method stub } /** * Returns whether the given part/selection is remote (viewing a repository) * * @param part * @param selection * @return */ protected boolean isRemote(IWorkbenchPart part, ISelection selection) { if (selection instanceof IStructuredSelection) { IStructuredSelection ss = (IStructuredSelection) selection; Object element = ss.getFirstElement(); if (element instanceof IMember) { IMember member = (IMember) element; return !member.getRubyProject().getProject().exists(); } } ITextEditor editor = getTextEditor(part); if (editor != null) { IEditorInput input = editor.getEditorInput(); Object adapter = Platform.getAdapterManager().getAdapter(input, "org.eclipse.team.core.history.IFileRevision"); //$NON-NLS-1$ return adapter != null; } return false; } /** * Returns the text editor associated with the given part or <code>null</code> if none. In case of a multi-page * editor, this method should be used to retrieve the correct editor to perform the breakpoint operation on. * * @param part * workbench part * @return text editor part or <code>null</code> */ protected ITextEditor getTextEditor(IWorkbenchPart part) { if (part instanceof ITextEditor) { return (ITextEditor) part; } return (ITextEditor) part.getAdapter(ITextEditor.class); } /** * Returns the methods from the selection, or an empty array * * @param selection * the selection to get the methods from * @return an array of the methods from the selection or an empty array */ protected IMethod[] getMethods(IStructuredSelection selection) { if (selection.isEmpty()) { return new IMethod[0]; } List<IMethod> methods = new ArrayList<IMethod>(selection.size()); Iterator iterator = selection.iterator(); while (iterator.hasNext()) { Object thing = iterator.next(); if (thing instanceof IMethod) { methods.add((IMethod) thing); } } return (IMethod[]) methods.toArray(new IMethod[methods.size()]); } /** * Returns if the text selection is a valid method or not * * @param selection * the text selection * @param part * the associated workbench part * @return true if the selection is a valid method, false otherwise */ private boolean isMethod(ITextSelection selection, IWorkbenchPart part) { ITextEditor editor = getTextEditor(part); if (editor != null) { IRubyElement element = getRubyElement(editor.getEditorInput()); if (element != null) { try { if (element instanceof IRubyScript) { element = ((IRubyScript) element).getElementAt(selection.getOffset()); } return element != null && element.getElementType() == IRubyElement.METHOD; } catch (RubyModelException e) { return false; } } } return false; } /** * gets the <code>IRubyElement</code> from the editor input * * @param input * the current editor input * @return the corresponding <code>IRubyElement</code> * @since 1.3 */ private IRubyElement getRubyElement(IEditorInput input) { IRubyElement re = RubyUI.getEditorInputRubyElement(input); if (re != null) { return re; } // try to get from the working copy manager return DebugWorkingCopyManager.getWorkingCopy(input, false); } /** * Returns a selection of the member in the given text selection, or the original selection if none. * * @param part * @param selection * @return a structured selection of the member in the given text selection, or the original selection if none * @exception CoreException * if an exception occurs */ protected ISelection translateToMembers(IWorkbenchPart part, ISelection selection) throws CoreException { ITextEditor textEditor = getTextEditor(part); if (textEditor != null && selection instanceof ITextSelection) { ITextSelection textSelection = (ITextSelection) selection; IEditorInput editorInput = textEditor.getEditorInput(); IDocumentProvider documentProvider = textEditor.getDocumentProvider(); if (documentProvider == null) { throw new CoreException(Status.CANCEL_STATUS); } IDocument document = documentProvider.getDocument(editorInput); int offset = textSelection.getOffset(); if (document != null) { try { IRegion region = document.getLineInformationOfOffset(offset); int end = region.getOffset() + region.getLength(); while (Character.isWhitespace(document.getChar(offset)) && offset < end) { offset++; } } catch (BadLocationException e) { } } IMember m = null; IRubyScript root = getTypeRoot(editorInput); if (root != null) { synchronized (root) { root.reconcile(false, null, null); } IRubyElement e = root.getElementAt(offset); if (e instanceof IMember) { m = (IMember) e; } } if (m != null) { return new StructuredSelection(m); } } return selection; } /** * Returns the {@link IRubyScript} for the given {@link IEditorInput} * * @param input * @return the type root or <code>null</code> if one cannot be derived * @since 1.3.0 */ private IRubyScript getTypeRoot(IEditorInput input) { IWorkingCopyManager manager = RubyUI.getWorkingCopyManager(); IRubyScript root = manager.getWorkingCopy(input); if (root == null) { root = DebugWorkingCopyManager.getWorkingCopy(input, false); } return root; } }