package org.radrails.rails.internal.ui.actions.mvc;
import java.io.ByteArrayInputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.radrails.rails.core.Inflector;
import org.radrails.rails.core.RailsConventions;
import org.radrails.rails.ui.RailsUILog;
import org.rubypeople.rdt.core.IMethod;
import org.rubypeople.rdt.core.IParent;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.IRubyScript;
import org.rubypeople.rdt.core.IType;
import org.rubypeople.rdt.core.RubyCore;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.internal.core.RubyMethod;
import org.rubypeople.rdt.internal.ui.rubyeditor.RubyDocumentProvider;
import org.rubypeople.rdt.internal.ui.rubyeditor.RubyEditor;
import org.rubypeople.rdt.ui.RubyUI;
public class ViewEditorActionDelegate extends MVCEditorActionDelegate
{
private static final String APP_MODELS = "app/models";
private static final String APP_HELPERS = "app/helpers";
private static final String HAML_EDITOR_ID = "net.lucky_dip.hamleditor.editor.HamlEditor";
private static final String RHTML_EDITOR_ID = "com.aptana.ide.editors.ERBEditor";
public static final String[] VIEW_TYPES = { "rhtml", "rjs", "rxml", "html.erb", "xml.builder", "dryml", "haml" };
@Override
protected boolean isEnabled()
{
IFile currentFile = getCurrentFile();
return RailsConventions.looksLikeController(currentFile) || RailsConventions.looksLikeHelper(currentFile)
|| RailsConventions.looksLikeModel(currentFile) || RailsConventions.looksLikeTest(currentFile);
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
*/
public void run(IAction action)
{
IFile original = getCurrentFile();
IProject project = original.getProject();
String path = original.getProjectRelativePath().toString();
try
{
if (attemptToOpenMvcView(project, path, "app/controllers", "_controller"))
return;
if (attemptToOpenMvcView(project, path, APP_HELPERS, "_helper"))
return;
if (attemptToOpenMvcView(project, path, APP_MODELS, ""))
return;
if (attemptToOpenMvcView(project, path, "test/functional", "_controller_test"))
return;
if (attemptToOpenMvcView(project, path, "test/unit", "_test"))
return;
if (attemptToOpenMvcModelOrController(project, path, original))
return;
}
catch (RubyModelException e)
{
RailsUILog.logError("Error accessing ruby model", e);
}
catch (PartInitException e)
{
RailsUILog.logError("Error creating editor", e);
}
catch (CoreException e)
{
RailsUILog.logError("Error running action", e);
}
}
private boolean attemptToOpenMvcView(IProject project, String path, String srcDir, String srcSuffix)
throws RubyModelException, CoreException, PartInitException
{
Matcher m = Pattern.compile("^((?:.+?/)?)" + srcDir + "/(.+)" + srcSuffix + "\\.rb$").matcher(path);
if (!m.matches())
return false;
String appRootDir = (m.group(1).length() == 0 ? "" : "/") + m.group(1);
String viewName = m.group(2);
RubyEditor editor = (RubyEditor) getActiveEditor();
RubyDocumentProvider provider = (RubyDocumentProvider) editor.getDocumentProvider();
IRubyScript script = (IRubyScript) provider.getWorkingCopy(editor.getEditorInput());
TextSelection ts = (TextSelection) editor.getSelectionProvider().getSelection();
String actionName = findAction(script, ts.getOffset());
// If actionName is null, pop up a dialog asking which action/view they want!
if (actionName == null)
{
IRubyScript controllerScript = null;
if (srcDir.equals(APP_MODELS))
{
IFile original = getCurrentFile();
IFile controller = RailsConventions.getControllerFromModel(original);
controllerScript = RubyCore.create(controller);
viewName = Inflector.pluralize(viewName);
}
else if (srcDir.equals(APP_HELPERS))
{
IFile original = getCurrentFile();
IFile controller = RailsConventions.getControllerFromHelper(original);
controllerScript = RubyCore.create(controller);
}
else if (srcDir.equals("test/functional"))
{
IFile original = getCurrentFile();
IFile controller = RailsConventions.getControllerFromFunctionalTest(original);
controllerScript = RubyCore.create(controller);
}
else if (srcDir.equals("test/unit"))
{
IFile original = getCurrentFile();
IFile controller = RailsConventions.getControllerFromUnitTest(original);
controllerScript = RubyCore.create(controller);
viewName = Inflector.pluralize(viewName);
}
else
{
controllerScript = script;
}
IType[] types = controllerScript.getTypes();
WhichActionDialog dialog = new WhichActionDialog(Display.getDefault().getActiveShell(), types[0]);
if (dialog.open() == Dialog.OK)
{
actionName = dialog.getAction();
}
}
if (actionName != null)
{
IFile fileHandle = findViewFile(project, appRootDir, viewName, actionName);
if (fileHandle == null || !fileHandle.exists())
{
// new dialog
String filename = ViewSelectionDialog.openConfirm(Display.getDefault().getActiveShell(), project,
actionName);
if (filename != null)
{
IPath filePath = project.getProjectRelativePath().append(
project.getName() + appRootDir + "/app/views/" + viewName + "/" + filename);
fileHandle = ResourcesPlugin.getWorkspace().getRoot().getFile(filePath);
if (!fileHandle.getParent().exists())
{
try
{
((IFolder) fileHandle.getParent()).create(false, true, null);
}
catch (Exception e)
{
// ignore
}
}
// Use the contents that ERB New File wizard inserts!
IPreferenceStore store = new ScopedPreferenceStore(new InstanceScope(), "com.aptana.ide.editor.erb");
String contents = store.getString("com.aptana.ide.editor.erb.ERBEDITOR_INITIAL_CONTENTS");
fileHandle.create(new ByteArrayInputStream((contents).getBytes()), false /* force */, null);
}
}
if (fileHandle != null && fileHandle.exists())
{
IEditorInput editorInput = new FileEditorInput(fileHandle);
open(editorInput, fileHandle);
}
}
return true;
}
private IFile findViewFile(IProject project, String appRootDir, String viewName, String action)
{
IPath thePath = new Path(appRootDir).append("app").append("views").append(viewName);
for (int i = 0; i < VIEW_TYPES.length; i++)
{
IFile fileHandle = project.getFile(thePath.append(action + "." + VIEW_TYPES[i]));
if (fileHandle.exists())
{
return fileHandle;
}
}
IFolder folder = project.getFolder(thePath);
if (folder == null || !folder.exists())
return null;
try
{
IResource[] children = folder.members();
for (IResource child : children)
{
if (child.getName().startsWith(action + ".") && child.getType() == IResource.FILE)
{
return (IFile) child;
}
}
}
catch (CoreException e)
{
// ignore
}
return null;
}
private boolean attemptToOpenMvcModelOrController(IProject project, String path, IFile original)
throws PartInitException
{
String appRootDir = "";
Matcher m = Pattern.compile("^(.+?/)(app/views/.+)$").matcher(path);
if (m.matches())
{
appRootDir = "/" + m.group(1);
path = m.group(2);
}
if (path.startsWith("app/views"))
{
IPath tPath = original.getProjectRelativePath();
String[] segments = tPath.segments();
String patternStr = "app/views/(\\S+?)/" + segments[segments.length - 1];
Pattern pattern = Pattern.compile(patternStr);
Matcher matcher = pattern.matcher("");
// Set the input
matcher.reset(path);
// Get tagname and contents of tag
matcher.find(); // true
String controllername = matcher.group(1);
IPath thepath = project.getProjectRelativePath().append(
project.getName() + appRootDir + "/app/controllers/" + controllername + "_controller.rb");
IFile fileHandle = ResourcesPlugin.getWorkspace().getRoot().getFile(thepath);
IPath mailerpath = project.getProjectRelativePath().append(
project.getName() + appRootDir + "/app/models/" + controllername + ".rb");
IFile fileHandleMailer = ResourcesPlugin.getWorkspace().getRoot().getFile(mailerpath);
if (fileHandle.exists())
{
IEditorInput editorInput = new FileEditorInput(fileHandle);
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().openEditor(editorInput,
RubyUI.ID_RUBY_EDITOR);
return true;
}
else if (fileHandleMailer.exists())
{
IEditorInput editorInput = new FileEditorInput(fileHandleMailer);
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().openEditor(editorInput,
RubyUI.ID_RUBY_EDITOR);
return true;
}
}
return false;
}
private void open(IEditorInput newEditorInput, IFile newFileHandle) throws PartInitException
{
if (shouldOpenInRHTMLEditor(newFileHandle))
{
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().openEditor(newEditorInput,
RHTML_EDITOR_ID);
return;
}
if (shouldOpenInHAMLEditor(newFileHandle))
{
IEditorDescriptor desc = PlatformUI.getWorkbench().getEditorRegistry().findEditor(HAML_EDITOR_ID);
if (desc != null)
{
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().openEditor(newEditorInput,
HAML_EDITOR_ID);
return;
}
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().openEditor(newEditorInput,
RHTML_EDITOR_ID);
return;
}
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().openEditor(newEditorInput,
RubyUI.ID_RUBY_EDITOR);
}
private boolean shouldOpenInHAMLEditor(IFile fileHandle)
{
return fileHandle.getFileExtension().equals("haml") || fileHandle.getName().contains(".haml");
}
private boolean shouldOpenInRHTMLEditor(IFile fileHandle)
{
return fileHandle.getFileExtension().equals("rhtml") || fileHandle.getFileExtension().equals("erb")
|| fileHandle.getFileExtension().equals("dryml");
}
public String findAction(IRubyScript script, int offset) throws RubyModelException
{
String retVal = null;
IRubyElement method = findRubyMethod(script.getElementAt(offset));
if (method != null)
{
retVal = method.getElementName();
}
else
{
IParent parent = ((IParent) script.getChildren()[0]);
if (parent.hasChildren())
{
IRubyElement[] child = parent.getChildren();
// TODO key: ctrl+shift+v - ClassCastException when not in a method, instead
// RubyInstVar
for (int j = 0; j < child.length; j++)
{
if (child[j] instanceof RubyMethod)
{
RubyMethod m = (RubyMethod) child[j];
int methodStart = m.getSourceRange().getOffset();
int methodEnd = (m.getSourceRange().getOffset() + m.getSourceRange().getLength());
if ((offset >= methodStart) && (offset <= methodEnd))
{
retVal = m.getElementName();
break;
}
}
}
}
}
return retVal;
}
private IRubyElement findRubyMethod(IRubyElement e)
{
if (isRubyMethod(e))
{
return e;
}
else if (e == null)
{
return null;
}
else
{
return findRubyMethod(e.getParent());
}
}
private boolean isRubyMethod(IRubyElement e)
{
boolean retVal = false;
if (e instanceof RubyMethod)
{
retVal = true;
}
return retVal;
}
private class WhichActionDialog extends MessageDialog
{
private IType type;
private Combo combo;
private String action;
protected WhichActionDialog(Shell parentShell, IType type)
{
super(parentShell, "Cursor not inside an action", null, "For which action?", MessageDialog.QUESTION,
new String[] { IDialogConstants.OK_LABEL, IDialogConstants.CANCEL_LABEL }, 0);
this.type = type;
}
@Override
protected Control createCustomArea(Composite parent)
{
try
{
IMethod[] methods = type.getMethods();
if (methods == null || methods.length == 0)
{
Label label = new Label(parent, SWT.NULL);
label
.setText("The current controller has no actions. Please create a public method in the controller first");
}
else
{
combo = new Combo(parent, SWT.SINGLE);
for (int i = 0; i < methods.length; i++)
{
if (methods[i].getVisibility() == IMethod.PUBLIC)
{
combo.add(methods[i].getElementName());
}
}
if (combo.getItemCount() > 0)
combo.select(0);
}
}
catch (RubyModelException e)
{
RailsUILog.log(e);
}
return super.createCustomArea(parent);
}
@Override
protected void buttonPressed(int buttonId)
{
if (buttonId == 0)
{
if (combo != null)
action = combo.getText();
}
super.buttonPressed(buttonId);
}
public String getAction()
{
return action;
}
}
}