/** * This file Copyright (c) 2005-2008 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain other free and open source software ("FOSS") code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * Any modifications to this file must keep this entire header intact. */ package com.aptana.ide.editor.html; import java.io.File; import java.io.IOException; import java.net.URI; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IPartService; import org.eclipse.ui.IPathEditorInput; import org.eclipse.ui.IURIEditorInput; import org.eclipse.ui.IWindowListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.editors.text.NonExistingFileEditorInput; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.texteditor.IDocumentProvider; import com.aptana.ide.core.ui.CoreUIUtils; import com.aptana.ide.editor.html.HTMLDocumentProvider.HTMLFileInfo; import com.aptana.ide.editor.html.actions.PrettyPrintHTML; import com.aptana.ide.editor.html.lexing.HTMLTokenTypes; import com.aptana.ide.editor.html.preferences.IPreferenceConstants; import com.aptana.ide.editors.unified.DocumentSourceProvider; import com.aptana.ide.editors.unified.IFileServiceFactory; import com.aptana.ide.editors.unified.IUnifiedEditorContributor; import com.aptana.ide.editors.unified.UnifiedEditor; import com.aptana.ide.lexer.IToken; import com.aptana.ide.lexer.Lexeme; import com.aptana.ide.lexer.LexemeList; /** * HTMLSourceEditor */ public class HTMLSourceEditor extends UnifiedEditor { /** * */ protected HTMLContributor contributor; private boolean _isDisposing = false; private boolean _isMarkingBothTags = false; private Action tidyAction; /** * Internal part and shell activation listener for triggering state validation. */ private class ActivationListener implements IPartListener, IWindowListener { /** Cache of the active workbench part. */ private IWorkbenchPart fActivePart; /** Indicates whether activation handling is currently be done. */ private boolean fIsHandlingActivation= false; /** * The part service. */ private IPartService fPartService; /** * Creates this activation listener. * * @param partService the part service on which to add the part listener */ public ActivationListener(IPartService partService) { fPartService= partService; fPartService.addPartListener(this); PlatformUI.getWorkbench().addWindowListener(this); } /** * Disposes this activation listener. */ public void dispose() { fPartService.removePartListener(this); PlatformUI.getWorkbench().removeWindowListener(this); fPartService= null; } /** * @see IPartListener#partActivated(org.eclipse.ui.IWorkbenchPart) */ public void partActivated(IWorkbenchPart part) { if (part instanceof HTMLEditor) { fActivePart = ((HTMLEditor) part).getEditor(); } else { fActivePart= part; } handleActivation(); } /** * @see IPartListener#partBroughtToTop(org.eclipse.ui.IWorkbenchPart) */ public void partBroughtToTop(IWorkbenchPart part) { } /** * @see IPartListener#partClosed(org.eclipse.ui.IWorkbenchPart) */ public void partClosed(IWorkbenchPart part) { } /** * @see IPartListener#partDeactivated(org.eclipse.ui.IWorkbenchPart) */ public void partDeactivated(IWorkbenchPart part) { fActivePart= null; } /** * @see IPartListener#partOpened(org.eclipse.ui.IWorkbenchPart) */ public void partOpened(IWorkbenchPart part) { } /** * Handles the activation triggering a element state check in the editor. */ private void handleActivation() { if (fIsHandlingActivation) return; if (fActivePart == HTMLSourceEditor.this) { fIsHandlingActivation= true; try { safelySanityCheckState(getEditorInput()); } finally { fIsHandlingActivation= false; } } } /** * @see org.eclipse.ui.IWindowListener#windowActivated(org.eclipse.ui.IWorkbenchWindow) */ public void windowActivated(IWorkbenchWindow window) { if (window == getEditorSite().getWorkbenchWindow()) { /* * Workaround for problem described in * http://dev.eclipse.org/bugs/show_bug.cgi?id=11731 * Will be removed when SWT has solved the problem. */ window.getShell().getDisplay().asyncExec(new Runnable() { public void run() { handleActivation(); } }); } } /** * @see org.eclipse.ui.IWindowListener#windowDeactivated(org.eclipse.ui.IWorkbenchWindow) */ public void windowDeactivated(IWorkbenchWindow window) { } /** * @see org.eclipse.ui.IWindowListener#windowClosed(org.eclipse.ui.IWorkbenchWindow) */ public void windowClosed(IWorkbenchWindow window) { } /** * @see org.eclipse.ui.IWindowListener#windowOpened(org.eclipse.ui.IWorkbenchWindow) */ public void windowOpened(IWorkbenchWindow window) { } } /** * The editor's activation listener. */ private ActivationListener fActivationListener; /** * HTMLSourceEditor */ public HTMLSourceEditor() { super(); addPluginToPreferenceStore(HTMLPlugin.getDefault()); _isMarkingBothTags = getPreferenceStore().getBoolean(IPreferenceConstants.HTMLEDITOR_HIGHLIGHT_START_END_TAGS); } /** * @see com.aptana.ide.editors.unified.UnifiedEditor#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput) */ public void init(IEditorSite site, IEditorInput input) throws PartInitException { super.init(site, input); fActivationListener= new ActivationListener(site.getWorkbenchWindow().getPartService()); } @Override protected void initializeKeyBindingScopes() { setKeyBindingScopes(new String[] { "com.aptana.ide.editors.UnifiedEditorsScope", "com.aptana.ide.editors.HTMLEditorScope" }); //$NON-NLS-1$ //$NON-NLS-2$ } /** * @see com.aptana.ide.editors.unified.UnifiedEditor#createActions() */ protected void createActions() { tidyAction = new Action(Messages.HTMLSourceEditor_RunHTMLTidy) { public void run() { PrettyPrintHTML formatter = new PrettyPrintHTML(); formatter.setActiveEditor(null, HTMLSourceEditor.this); formatter.run(); } }; super.createActions(); } /** * @see com.aptana.ide.editors.unified.UnifiedEditor#editorContextMenuAboutToShow(org.eclipse.jface.action.IMenuManager) */ protected void editorContextMenuAboutToShow(IMenuManager menu) { super.editorContextMenuAboutToShow(menu); menu.add(tidyAction); } /** * @see com.aptana.ide.editors.unified.UnifiedEditor#createLocalContributor() */ protected IUnifiedEditorContributor createLocalContributor() { this.contributor = new HTMLContributor(); return this.contributor; } /** * @see com.aptana.ide.editors.unified.UnifiedEditor#getFileServiceFactory() */ public IFileServiceFactory getFileServiceFactory() { return HTMLFileServiceFactory.getInstance(); } /** * Returns a java.io.File object of a temporary file for preview. * * @return File */ public File getTempFile() { IEditorInput in = this.getEditorInput(); File outFile = null; if (in instanceof FileEditorInput) { IFile file = ((FileEditorInput) this.getEditorInput()).getFile(); IPath path = CoreUIUtils.getPathOfIFileLocation(file); if (path != null) { File f = path.makeAbsolute().toFile(); String location = getTempFileLocation(f, file.getName()); outFile = new File(location); } } else if (in instanceof NonExistingFileEditorInput) { NonExistingFileEditorInput nin = (NonExistingFileEditorInput) in; IPath path = nin.getPath(nin); String spath = path.toOSString(); File f = new File(spath); String location = getTempFileLocation(f, path.lastSegment()); outFile = new File(location); } else if (in instanceof IPathEditorInput) { IPathEditorInput pin = (IPathEditorInput) in; File path = pin.getPath().toFile(); String location = getTempFileLocation(path, pin.getName()); outFile = new File(location); } else if (in instanceof IURIEditorInput) { URI uri = ((IURIEditorInput) in).getURI(); String location; if ("file".equals(uri.getScheme())) { location = getTempFileLocation(new File(uri), Path.fromPortableString(uri.getPath()).lastSegment()); } else { location = getTempFileLocation(null, Path.fromPortableString(uri.getPath()).lastSegment()); } outFile = new File(location); } if (outFile != null) { outFile.deleteOnExit(); } return outFile; } /** * The location of the temp file * * @param file * @param name * @return String */ public String getTempFileLocation(File file, String name) { if (file != null) { file = file.getParentFile(); } else { try { return File.createTempFile(".tmp_" + name, "~").getAbsolutePath(); } catch (IOException e) { return null; } } // Add cache busting random number to fix STU-2094 return file + File.separator + ".tmp_" + name + "." + ((int) (Math.random() * 100000)) + "~"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } /** * isFileEditorInput * * @return boolean */ public boolean isFileEditorInput() { if (getEditorInput() instanceof IFileEditorInput || getEditorInput() instanceof IPathEditorInput || getEditorInput() instanceof IURIEditorInput || getEditorInput() instanceof NonExistingFileEditorInput) { return true; } return false; } /** * @see com.aptana.ide.editors.unified.UnifiedEditor#getDefaultFileExtension() */ public String getDefaultFileExtension() { return "html"; //$NON-NLS-1$ } /** * @see org.eclipse.ui.IWorkbenchPart#dispose() */ public void dispose() { if (_isDisposing) { return; } _isDisposing = true; if (fActivationListener != null) { fActivationListener.dispose(); fActivationListener= null; } if (contributor != null) { contributor.dispose(); contributor = null; } // setSite(null); super.dispose(); } /** * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#collectContextMenuPreferencePages() */ protected String[] collectContextMenuPreferencePages() { return new String[] { "com.aptana.ide.editor.html.preferences.GeneralPreferencePage", //$NON-NLS-1$ "com.aptana.ide.editor.html.preferences.ColorPizationreferencePage", //$NON-NLS-1$ "com.aptana.ide.editor.html.preferences.FoldingPreferencePage", //$NON-NLS-1$ "com.aptana.ide.editor.html.preferences.FormattingPreferencePage", //$NON-NLS-1$ "com.aptana.ide.editor.html.preferences.PreviewPreferencePage", //$NON-NLS-1$ "com.aptana.ide.editor.html.preferences.ProblemsPreferencePage", //$NON-NLS-1$ "com.aptana.ide.editor.html.preferences.CodeAssistPreferencePage", //$NON-NLS-1$ "com.aptana.ide.editor.html.preferences.TypingPreferencePage", //$NON-NLS-1$ "com.aptana.ide.editor.html.preferences.TidyPreferencePage", //$NON-NLS-1$ "org.eclipse.ui.preferencePages.GeneralTextEditor", //$NON-NLS-1$ "org.eclipse.ui.editors.preferencePages.Annotations", //$NON-NLS-1$ "org.eclipse.ui.editors.preferencePages.QuickDiff", //$NON-NLS-1$ "org.eclipse.ui.editors.preferencePages.Accessibility", //$NON-NLS-1$ "org.eclipse.ui.editors.preferencePages.Spelling", //$NON-NLS-1$ "org.eclipse.ui.editors.preferencePages.LinkedModePreferencePage", //$NON-NLS-1$ }; } /** * isNewInput * * @param input * @return boolean */ protected boolean isNewInput(IEditorInput input) { HTMLFileInfo cuInfo = getHTMLFileInfo(input); if (cuInfo == null) { return false; // throw new RuntimeException(Messages.HTMLSourceEditor_cuInfo_Null_At_DoSetInput); } HTMLDocumentProvider dp = (HTMLDocumentProvider) getDocumentProvider(); // Get document from input IDocument document = dp.getDocument(input); DocumentSourceProvider provider = new DocumentSourceProvider(document, input); if (provider == null) { throw new RuntimeException(Messages.HTMLSourceEditor_Provider_Null); } return (cuInfo.sourceProvider == null || cuInfo.sourceProvider.equals(provider) == false); } private HTMLFileInfo getHTMLFileInfo(IEditorInput input) { HTMLDocumentProvider dp = (HTMLDocumentProvider) getDocumentProvider(); if (dp == null) { throw new RuntimeException(Messages.HTMLSourceEditor_Document_Provider_Null); } return (HTMLFileInfo) dp.getFileInfoPublic(input); } /** * Updates the file information * * @param input * @param provider * @param document */ protected void updateFileInfo(IEditorInput input, DocumentSourceProvider provider, IDocument document) { super.updateFileInfo(input, provider, document); if (isNewInput(input)) { // save reference to provider getHTMLFileInfo(input).sourceProvider = provider; } } /** * @see com.aptana.ide.editors.unified.UnifiedEditor#createDocumentProvider() */ public IDocumentProvider createDocumentProvider() { return HTMLDocumentProvider.getInstance(); } /** * Can this lexeme be highlighted for matching occurrences ? * * @param lexeme * @return true if this token type is eligible for occurrence marking */ public boolean canMarkOccurrences(Lexeme lexeme) { IToken token = lexeme.getToken(); int typeIndex = token.getTypeIndex(); if (typeIndex == HTMLTokenTypes.TEXT) { return false; } if (typeIndex == HTMLTokenTypes.START_TAG || typeIndex == HTMLTokenTypes.END_TAG) { return true; } return super.canMarkOccurrences(lexeme); } /** * Overridden parent method to handle highlighting of both start and end tag when either is selected If/when we have * common based class for tag based languages (html, xml etc.), this method should be moved there. * * @see com.aptana.ide.editors.unified.UnifiedEditor#markOccurences(com.aptana.ide.lexer.LexemeList, * com.aptana.ide.lexer.Lexeme) */ protected void markOccurences(LexemeList lexemeList, Lexeme selectedLexeme) { boolean deferToParent = true; if (this._isMarkingBothTags && (selectedLexeme.typeIndex == HTMLTokenTypes.START_TAG || selectedLexeme.typeIndex == HTMLTokenTypes.END_TAG)) { deferToParent = false; String selectedText = selectedLexeme.getText(); String normalizedSelectedText = selectedText; // convert e.g. "</table" to <table" if (selectedLexeme.typeIndex == HTMLTokenTypes.END_TAG) { if (selectedLexeme.length >= 3) { normalizedSelectedText = selectedText.substring(0, 1) + selectedText.substring(2); } } for (int i = 0; i < lexemeList.size(); i++) { Lexeme lexeme = lexemeList.get(i); if (lexeme != null) { if (lexeme.isHighlighted()) { lexeme.setHighlighted(false); } if (lexeme.typeIndex == HTMLTokenTypes.START_TAG || lexeme.typeIndex == HTMLTokenTypes.END_TAG) { if (lexeme.typeIndex == selectedLexeme.typeIndex && lexeme.length == selectedLexeme.length) { if (selectedText.equals(lexeme.getText())) { lexeme.setHighlighted(true); } } else if ((lexeme.length - selectedLexeme.length) == 1) { if (lexeme.typeIndex == HTMLTokenTypes.END_TAG) { if (lexeme.length >= 3) { String normalizedText = lexeme.getText().substring(0, 1) + lexeme.getText().substring(2); if (normalizedText.equals(selectedText)) { lexeme.setHighlighted(true); } } } } else if ((selectedLexeme.length - lexeme.length) == 1) { if (normalizedSelectedText.equals(lexeme.getText())) { lexeme.setHighlighted(true); } } } } } } if (deferToParent) { super.markOccurences(lexemeList, selectedLexeme); } } /** * Returns name and extension filters for display in file dialog * * @param fileName * @return FileDialogFilterInfo */ protected FileDialogFilterInfo getFileDialogFilterInformation(String fileName) { String fileExtension = getFileExtension(fileName); if (Messages.HTMLSourceEditor_ExtensionHTM.equals(fileExtension) || Messages.HTMLSourceEditor_ExtensionHTML.equals(fileExtension)) { FileDialogFilterInfo filterInfo = new FileDialogFilterInfo(); filterInfo .setFilterExtensions(new String[] { "*" + Messages.HTMLSourceEditor_ExtensionHTM, "*" + Messages.HTMLSourceEditor_ExtensionHTML, "*" + Messages.HTMLSourceEditor_ExtensionAll }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ filterInfo.setFilterNames(new String[] { Messages.HTMLSourceEditor_DescriptionHTM, Messages.HTMLSourceEditor_DescriptionHTML, Messages.HTMLSourceEditor_DescriptionAll }); return filterInfo; } else { return super.getFileDialogFilterInformation(fileName); } } /** * @see com.aptana.ide.editors.unified.UnifiedEditor#handlePreferenceStoreChanged(org.eclipse.jface.util.PropertyChangeEvent) */ protected void handlePreferenceStoreChanged(PropertyChangeEvent event) { String property = event.getProperty(); if (IPreferenceConstants.HTMLEDITOR_HIGHLIGHT_START_END_TAGS.equals(property)) { _isMarkingBothTags = getPreferenceStore().getBoolean( IPreferenceConstants.HTMLEDITOR_HIGHLIGHT_START_END_TAGS); } else { super.handlePreferenceStoreChanged(event); } } }