package com.aptana.editor.php.internal.ui.editor;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import org.eclipse.core.resources.IFile;
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.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPartService;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.texteditor.ChainedPreferenceStore;
import org2.eclipse.php.internal.core.PHPVersion;
import com.aptana.core.logging.IdeLog;
import com.aptana.editor.common.CommonEditorPlugin;
import com.aptana.editor.common.CommonSourceViewerConfiguration;
import com.aptana.editor.common.IDeclarationActions;
import com.aptana.editor.common.text.reconciler.IFoldingComputer;
import com.aptana.editor.html.HTMLEditor;
import com.aptana.editor.php.Messages;
import com.aptana.editor.php.PHPEditorPlugin;
import com.aptana.editor.php.core.IPHPVersionListener;
import com.aptana.editor.php.core.PHPNature;
import com.aptana.editor.php.core.PHPVersionProvider;
import com.aptana.editor.php.core.model.ISourceModule;
import com.aptana.editor.php.epl.PHPEplPlugin;
import com.aptana.editor.php.internal.builder.BuildPathManager;
import com.aptana.editor.php.internal.builder.FileSystemModule;
import com.aptana.editor.php.internal.builder.SingleFileBuildPath;
import com.aptana.editor.php.internal.contentAssist.mapping.PHPOffsetMapper;
import com.aptana.editor.php.internal.core.IPHPConstants;
import com.aptana.editor.php.internal.core.builder.IModule;
import com.aptana.editor.php.internal.core.model.ISourceModuleProviderEditor;
import com.aptana.editor.php.internal.model.utils.ModelUtils;
import com.aptana.editor.php.internal.parser.PHPParseState;
import com.aptana.editor.php.internal.parser.nodes.PHPExtendsNode;
import com.aptana.editor.php.internal.ui.actions.IPHPActionKeys;
import com.aptana.editor.php.internal.ui.actions.OpenDeclarationAction;
import com.aptana.editor.php.internal.ui.editor.outline.PHPDecoratingLabelProvider;
import com.aptana.editor.php.internal.ui.editor.outline.PHPOutlineItem;
import com.aptana.editor.php.internal.ui.editor.outline.PHTMLOutlineContentProvider;
import com.aptana.editor.php.util.EditorUtils;
import com.aptana.parsing.ParserPoolFactory;
import com.aptana.parsing.ast.ILanguageNode;
import com.aptana.parsing.ast.IParseNode;
import com.aptana.parsing.ast.IParseRootNode;
/**
* The PHP editor central class.
*
* @author Shalom Gibly <sgibly@aptana.com>
*/
@SuppressWarnings("restriction")
public class PHPSourceEditor extends HTMLEditor implements ILanguageNode, IPHPVersionListener,
ISourceModuleProviderEditor
{
/**
* The PHP editor context.<b> This context is also defined in the contexts extension point. <b> The returned value
* is <code>com.aptana.editor.php.editorContext</code>
*/
public static final String PHP_EDITOR_CONTEXT = "com.aptana.editor.php.editorContext"; //$NON-NLS-1$
/**
* The PHP Editor ID
*/
public static final String PHP_EDITOR_ID = "com.aptana.editor.php"; //$NON-NLS-1$
private static final char[] PAIR_MATCHING_CHARS = new char[] { '(', ')', '{', '}', '[', ']', '`', '`', '\'', '\'',
'"', '"' };
private Object mutex = new Object();
private IProject project;
private PHPDocumentProvider documentProvider;
private IModule module;
private ISourceModule sourceModule;
private boolean isOutOfWorkspace;
private String sourceUri;
private PHPOffsetMapper offsetMapper;
private PHPVersion phpVersionCache;
// Mark Occurrences management
private OccurrencesUpdater occurrencesUpdater;
static
{
// Update the Mark-Occurrences colors to match the Theme.
// The PHPEditorPlugin also adds a listener that will update that when the Theme changes.
EditorUtils.setOccurrenceColors();
}
/**
* Constructs a new PHP source editor.
*/
public PHPSourceEditor()
{
}
/*
* (non-Javadoc)
* @see com.aptana.editor.html.HTMLEditor#initializeEditor()
*/
@Override
protected void initializeEditor()
{
super.initializeEditor();
module = null;
sourceModule = null;
isOutOfWorkspace = false;
ChainedPreferenceStore store = new ChainedPreferenceStore(new IPreferenceStore[] {
PHPEditorPlugin.getDefault().getPreferenceStore(), PHPEplPlugin.getDefault().getPreferenceStore(),
CommonEditorPlugin.getDefault().getPreferenceStore(), EditorsPlugin.getDefault().getPreferenceStore() });
setPreferenceStore(store);
setSourceViewerConfiguration(new PHPSourceViewerConfiguration(getPreferenceStore(), this));
setDocumentProvider(documentProvider = PHPEditorPlugin.getDefault().getPHPDocumentProvider());
// TODO: Shalom - Do what updateFileInfo does in the old PHPSourceEditor?
}
@Override
public IFoldingComputer createFoldingComputer(IDocument document)
{
return new PHPFoldingComputer(this, document);
}
/*
* (non-Javadoc)
* @see com.aptana.editor.html.HTMLEditor#createPartControl(org.eclipse.swt.widgets.Composite)
*/
@Override
public void createPartControl(Composite parent)
{
super.createPartControl(parent);
IContextService contextService = (IContextService) getSite().getService(IContextService.class);
contextService.activateContext(PHP_EDITOR_CONTEXT);
}
/*
* (non-Javadoc)
* @see com.aptana.editor.html.HTMLEditor#getContentType()
*/
@Override
public String getContentType()
{
return IPHPConstants.CONTENT_TYPE_PHP;
}
/*
* (non-Javadoc)
* @see com.aptana.editor.common.AbstractThemeableEditor#getAST()
*/
@Override
public IParseRootNode getAST()
{
try
{
PHPParseState phpParseState = new PHPParseState(getDocument().get(), 0, phpVersionCache, getModule(),
getSourceModule());
return ParserPoolFactory.parse(getContentType(), phpParseState).getRootNode();
}
catch (Exception e)
{
IdeLog.logTrace(PHPEditorPlugin.getDefault(), "Failed to parse PHP editor contents", e, //$NON-NLS-1$
com.aptana.parsing.IDebugScopes.PARSING);
}
return null;
}
public IParseRootNode getAST(String buildType)
{
try
{
PHPParseState phpParseState = new PHPParseState(getDocument().get(), 0, phpVersionCache, getModule(),
getSourceModule());
phpParseState.setBuildType(buildType);
return ParserPoolFactory.parse(getContentType(), phpParseState).getRootNode();
}
catch (Exception e)
{
IdeLog.logTrace(PHPEditorPlugin.getDefault(), "Failed to parse PHP editor contents", e, //$NON-NLS-1$
com.aptana.parsing.IDebugScopes.PARSING);
}
return null;
}
/*
* (non-Javadoc)
* @see com.aptana.editor.common.AbstractThemeableEditor#installOccurrencesUpdater()
*/
@Override
protected void installOccurrencesUpdater()
{
super.installOccurrencesUpdater();
// Initialize the occurrences annotations marker
//PHP高亮直接高亮相同的字符即可,不走语法树进行高亮 --by 李关平
// occurrencesUpdater = new OccurrencesUpdater(this);
// occurrencesUpdater.initialize(getPreferenceStore());
}
/*
* (non-Javadoc)
* @see com.aptana.editor.common.AbstractThemeableEditor#createActions()
*/
@Override
protected void createActions()
{
super.createActions();
IAction action = new OpenDeclarationAction(Messages.getResourceBundle(), this);
//覆盖掉html的转到定义功能
action.setActionDefinitionId(IDeclarationActions.OPEN_DECLARATION);
// setAction(IPHPActionKeys.OPEN_DECLARATION, action);
setAction(IDeclarationActions.OPEN_DECLARATION, action);
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.texteditor.AbstractTextEditor#handleCursorPositionChanged()
*/
@Override
protected void handleCursorPositionChanged()
{
super.handleCursorPositionChanged();
// TODO: Shalom - Handle the position change for the contributed actions?
}
/*
* Override this to make sure we maintain valid editor instance members when the editor input changes (probably as a
* result of move).
* @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#doSetInput(org.eclipse.ui.IEditorInput)
*/
protected void doSetInput(IEditorInput input) throws CoreException
{
super.doSetInput(input);
// Register as a PHP version listener and re-set the document to trigger a refresh and re-parse.
IResource resource = (IResource) input.getAdapter(IResource.class);
PHPVersionProvider phpVersionProvider = PHPVersionProvider.getInstance();
// In case this is the second time we hit that doSetInput, we need to re-register the listeners to make sure
// the editor is still in a valid state.
boolean shouldRefresh = (sourceUri != null);
if (shouldRefresh)
{
module = null;
sourceModule = null;
project = null;
sourceUri = null;
phpVersionProvider.removePHPVersionListener(this);
phpVersionProvider.removePHPVersionListener(documentProvider);
}
if (resource != null)
{
sourceUri = resource.getLocationURI().toString();
project = resource.getProject();
phpVersionCache = PHPVersionProvider.getPHPVersion(project);
documentProvider.phpVersionChanged(phpVersionCache);
phpVersionProvider.addPHPVersionListener(project, documentProvider);
phpVersionProvider.addPHPVersionListener(project, this);
}
else
{
// Set the cached version to null (which later when used should mean == default)
phpVersionCache = null;
documentProvider.phpVersionChanged(null);
// It's probably a file out of the workspace
if (input instanceof FileStoreEditorInput)
{
FileStoreEditorInput fsInput = (FileStoreEditorInput) input;
sourceUri = fsInput.getURI().toString();
}
}
}
/*
* (non-Javadoc)
* @see com.aptana.editor.common.AbstractThemeableEditor#dispose()
*/
@Override
public void dispose()
{
PHPVersionProvider.getInstance().removePHPVersionListener(this);
PHPVersionProvider.getInstance().removePHPVersionListener(documentProvider);
if (occurrencesUpdater != null)
{
occurrencesUpdater.dispose();
}
super.dispose();
}
@Override
public ITreeContentProvider getOutlineContentProvider()
{
return new PHTMLOutlineContentProvider(this);
}
@Override
public ILabelProvider getOutlineLabelProvider()
{
return new PHPDecoratingLabelProvider(true);
}
@Override
public char[] getPairMatchingCharacters()
{
return PAIR_MATCHING_CHARS;
}
/*
* (non-Javadoc)
* @see com.aptana.editor.common.AbstractThemeableEditor#getOutlineElementAt(int)
*/
@Override
protected Object getOutlineElementAt(int caret)
{
if (hasOutlinePageCreated())
{
IParseNode parseResult = getOutlinePage().getCurrentAst();
if (parseResult != null)
{
IParseNode node = parseResult.getNodeAtOffset(caret);
if (node instanceof PHPExtendsNode)
{
node = node.getParent();
}
if (node != null)
{
return new PHPOutlineItem(node.getNameNode().getNameRange(), node);
}
}
return super.getOutlineElementAt(caret);
}
return null;
}
public String getLanguage()
{
return IPHPConstants.CONTENT_TYPE_PHP;
}
public void phpVersionChanged(PHPVersion newVersion)
{
phpVersionCache = newVersion;
Job refreshJob = new UIJob("Refresh document") //$NON-NLS-1$
{
@Override
public IStatus runInUIThread(IProgressMonitor monitor)
{
// Force a reconcile!
SourceViewerConfiguration svc = getSourceViewerConfiguration();
if (svc instanceof CommonSourceViewerConfiguration)
{
CommonSourceViewerConfiguration csvc = (CommonSourceViewerConfiguration) svc;
csvc.forceReconcile();
}
return Status.OK_STATUS;
}
};
refreshJob.setSystem(true);
refreshJob.schedule(500L);
}
/*
* (non-Javadoc)
* @seeorg.eclipse.ui.texteditor.AbstractDecoratedTextEditor#editorContextMenuAboutToShow(org.eclipse.jface.action.
* IMenuManager)
*/
@Override
protected void editorContextMenuAboutToShow(IMenuManager menu)
{
super.editorContextMenuAboutToShow(menu);
final String openGroup = "group.open"; //$NON-NLS-1$
IAction action = getAction(IPHPActionKeys.OPEN_DECLARATION);
if (action != null)
menu.appendToGroup(openGroup, action);
}
/**
* Returns true if this editor is now displaying content that is out of the workspace.
*
* @return True, if the displayed content is not in the workspace; False, if it is.
*/
public boolean isOutOfWorkspace()
{
return isOutOfWorkspace;
}
/**
* Returns the offset mapper for this editor.
*
* @return A {@link PHPOffsetMapper} for this editor.
*/
public PHPOffsetMapper getOffsetMapper()
{
synchronized (mutex)
{
if (offsetMapper == null)
{
offsetMapper = new PHPOffsetMapper(this);
}
}
return offsetMapper;
}
/**
* Returns an {@link ISourceModule} for this editor.
*
* @return An {@link ISourceModule}
*/
public ISourceModule getSourceModule()
{
synchronized (mutex)
{
if (sourceModule == null)
{
sourceModule = ModelUtils.convertModule(getModule());
}
}
return sourceModule;
}
/**
* Returns the PHP editor's ID.
*
* @return The editor's ID
* @see ISourceModuleProviderEditor#getEditorID()
*/
public String getEditorID()
{
return PHP_EDITOR_ID;
}
/**
* Returns true if this editor is active now.
*
* @return True, iff this editor is active.
*/
public boolean isActiveEditor()
{
IWorkbenchPartSite site = getSite();
if (site == null)
{
return false;
}
IWorkbenchWindow window = site.getWorkbenchWindow();
IPartService service = window.getPartService();
IWorkbenchPart part = service.getActivePart();
return part != null && part.equals(this);
}
/**
* Gets current module.<b> This method caches the returned module for any consequent calls.
*
* @return current module.
* @see #computeModule(String)
*/
public IModule getModule()
{
synchronized (mutex)
{
if (module != null)
{
return module;
}
return computeModule(this.sourceUri);
}
}
/**
* Returns the IModule using a given sourceURI.<br>
* You are encouraged to use the {@link #getModule()} method when an {@link IModule} is needed. This method does not
* check if the module was computed before, and runs the computation again.
*
* @param sourceURI
* @return A computed {@link IModule}
* @see #getModule()
*/
public IModule computeModule(String sourceURI)
{
synchronized (mutex)
{
if (sourceURI == null)
{
IdeLog.logWarning(
PHPEditorPlugin.getDefault(),
"PHPSourceEditor::computeModule() - sourceUri was null. Returning null", PHPEditorPlugin.DEBUG_SCOPE); //$NON-NLS-1$
return null;
}
String struri = sourceURI;
URI uri = null;
try
{
uri = new URI(struri);
}
catch (URISyntaxException e)
{
try
{
int fileNameStart = struri.lastIndexOf('/');
if (fileNameStart > -1 && fileNameStart < struri.length() - 1)
{
String fileName = struri.substring(fileNameStart + 1);
String encoded = URLEncoder.encode(fileName, "UTF-8"); //$NON-NLS-1$
uri = new URI(struri.substring(0, fileNameStart + 1) + encoded);
}
}
catch (UnsupportedEncodingException e1) // $codepro.audit.disable emptyCatchClause
{
}
catch (URISyntaxException e2) // $codepro.audit.disable emptyCatchClause
{
}
if (uri == null)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), "PHPSourceEditor::computeModule() - malformed URI", e); //$NON-NLS-1$
return null;
}
}
if (!uri.isAbsolute())
{
return null;
}
IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(uri);
if (files == null || files.length == 0)
{
return createSystemFileModule(uri, false);
}
this.isOutOfWorkspace = false;
if (module == null)
{
try
{
if (files[0].getProject().getNature(PHPNature.NATURE_ID) == null)
{
// we are outside of PHP project (probably web project or
// other)
return createSystemFileModule(uri, true);
}
}
catch (CoreException e)
{
IdeLog.logWarning(PHPEditorPlugin.getDefault(),
"PHP Source Editor error in computing module(computeModule)", //$NON-NLS-1$
e, PHPEditorPlugin.DEBUG_SCOPE);
}
}
module = BuildPathManager.getInstance().getModuleByResource(files[0]);
return module;
}
}
/*
* Override this one to expose the progress monitor to the current package.
* @see org.eclipse.ui.texteditor.AbstractTextEditor#getProgressMonitor()
*/
@Override
protected IProgressMonitor getProgressMonitor()
{
return super.getProgressMonitor();
}
private IModule createSystemFileModule(URI uri, boolean isInWorkspace)
{
File file = new File(uri.getPath());
FileSystemModule fileSystemModule = new FileSystemModule(file, new SingleFileBuildPath(file), isInWorkspace);
this.isOutOfWorkspace = true;
module = fileSystemModule;
sourceModule = null;
return fileSystemModule;
}
/*
* (non-Javadoc)
* @see com.aptana.editor.html.HTMLEditor#getPluginPreferenceStore()
*/
@Override
protected IPreferenceStore getPluginPreferenceStore()
{
return PHPEplPlugin.getDefault().getPreferenceStore();
}
}