/* * JBoss by Red Hat * Copyright 2006-2009, Red Hat Middleware, LLC, and individual contributors as indicated * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.ide.eclipse.freemarker.editor; import com.liferay.ide.portal.ui.debug.IDebugEditor; import freemarker.core.ParseException; import freemarker.template.Configuration; import freemarker.template.Template; import java.io.Reader; import java.io.StringReader; import java.util.HashMap; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.internal.ui.javaeditor.JarEntryEditorInput; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.ITextViewerExtension2; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.MatchingCharacterPainter; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.texteditor.ContentAssistAction; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.eclipse.ui.texteditor.MarkerUtilities; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import org.eclipse.wst.sse.ui.internal.debug.DebugTextEditor; import org.eclipse.wst.sse.ui.internal.provisional.extensions.ISourceEditingTextTools; import org.jboss.ide.eclipse.freemarker.Constants; import org.jboss.ide.eclipse.freemarker.Plugin; import org.jboss.ide.eclipse.freemarker.configuration.ConfigurationManager; import org.jboss.ide.eclipse.freemarker.model.Item; import org.jboss.ide.eclipse.freemarker.model.ItemSet; import org.jboss.ide.eclipse.freemarker.outline.OutlinePage; /** * @author <a href="mailto:joe@binamics.com">Joe Hudson</a> */ public class Editor extends DebugTextEditor implements KeyListener, MouseListener, ISourceEditingTextTools, IDebugEditor { private OutlinePage fOutlinePage; private org.jboss.ide.eclipse.freemarker.editor.Configuration configuration; private ColorManager colorManager = new ColorManager(); private ItemSet itemSet; private Item selectedItem; private Item[] relatedItems; private static final char[] VALIDATION_TOKENS = new char[]{'\"', '[', ']', ',', '.', '\n', '4'}; private boolean readOnly = false; private boolean mouseDown = false; private boolean ctrlDown = false; private boolean shiftDown = false; public Editor() { super(); configuration = new org.jboss.ide.eclipse.freemarker.editor.Configuration(getPreferenceStore(), colorManager, this); setSourceViewerConfiguration(configuration); setDocumentProvider(new DocumentProvider()); } public void dispose() { ConfigurationManager.getInstance(getProject()).reload(); super.dispose(); if(matchingCharacterPainter!=null) { matchingCharacterPainter.dispose(); } } public Object getAdapter(Class aClass) { Object adapter; if (aClass.equals(IContentOutlinePage.class)) { if (fOutlinePage == null) { fOutlinePage = new OutlinePage(this); if (getEditorInput() != null) { fOutlinePage.setInput(getEditorInput()); } } adapter = fOutlinePage; } else if(aClass.equals(ISourceEditingTextTools.class)) { adapter = this; } else { adapter = super.getAdapter(aClass); } return adapter; } protected static final char[] BRACKETS= {'{', '}', '(', ')', '[', ']', '<', '>' }; private MatchingCharacterPainter matchingCharacterPainter; public void createPartControl(Composite parent) { super.createPartControl(parent); getSourceViewer().getTextWidget().addKeyListener(this); getSourceViewer().getTextWidget().addMouseListener(this); //matchingCharacterPainter = new MatchingCharacterPainter( // getSourceViewer(), // new JavaPairMatcher(BRACKETS)); //((SourceViewer) getSourceViewer()).addPainter(matchingCharacterPainter); } protected void createActions() { super.createActions(); // Add content assist propsal action ContentAssistAction action = new ContentAssistAction( Plugin.getDefault().getResourceBundle(), "FreemarkerEditor.ContentAssist", this); //$NON-NLS-1$ action.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); setAction("FreemarkerEditor.ContentAssist", action); //$NON-NLS-1$ action.setEnabled(true); } protected void handleCursorPositionChanged() { super.handleCursorPositionChanged(); if (!mouseDown) { int offset = getCaretOffset(); Item item = getItemSet().getSelectedItem(offset); if (null == item && offset > 0) item = getItemSet().getSelectedItem(offset-1); if (Plugin.getInstance().getPreferenceStore().getBoolean( Constants.HIGHLIGHT_RELATED_ITEMS)) { if (null != item && null != item.getRelatedItems() && item.getRelatedItems().length > 0) { highlightRelatedRegions(item.getRelatedItems(), item); } else { highlightRelatedRegions(null, item); } } if (null == item) { item = getItemSet().getContextItem(getCaretOffset()); } if (null != fOutlinePage) fOutlinePage.update(item); } } public void mouseDoubleClick(MouseEvent e) { } public void mouseDown(MouseEvent e) { mouseDown = true; } public void mouseUp(MouseEvent e) { mouseDown = false; handleCursorPositionChanged(); } public void select (Item item) { selectAndReveal(item.getRegion().getOffset(), item.getRegion().getLength()); } public IDocument getDocument() { ISourceViewer viewer = getSourceViewer(); if (viewer != null) { return viewer.getDocument(); } return null; } public ITextViewer getTextViewer() { return getSourceViewer(); } public void addProblemMarker(String aMessage, int aLine) { IFile file = ((IFileEditorInput)getEditorInput()).getFile(); try { Map attributes = new HashMap(5); attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_ERROR)); attributes.put(IMarker.LINE_NUMBER, new Integer(aLine)); attributes.put(IMarker.MESSAGE, aMessage); attributes.put(IMarker.TEXT, aMessage); MarkerUtilities.createMarker(file, attributes, IMarker.PROBLEM); } catch (Exception e) { } } private synchronized void highlightRelatedRegions (Item[] items, Item selectedItem) { if (null == items || items.length == 0) { if (null != relatedItems && relatedItems.length > 0) { for (int i=0; i<relatedItems.length; i++) { if (getDocument().getLength() >= relatedItems[i].getRegion().getOffset() + relatedItems[i].getRegion().getLength()) { if (null == this.selectedItem || !relatedItems[i].equals(this.selectedItem)) resetRange(relatedItems[i].getRegion()); } } } relatedItems = null; } if (null != relatedItems) { for (int i=0; i<relatedItems.length; i++) { if (getDocument().getLength() >= relatedItems[i].getRegion().getOffset() + relatedItems[i].getRegion().getLength()) { if (null == this.selectedItem || !relatedItems[i].equals(this.selectedItem)) resetRange(relatedItems[i].getRegion()); } } } if (null != items && items.length > 0) { for (int i=0; i<items.length; i++) { if (getDocument().getLength() >= items[i].getRegion().getOffset() + items[i].getRegion().getLength() && !items[i].equals(selectedItem)) { ITypedRegion region = items[i].getRegion(); getSourceViewer().getTextWidget().setStyleRange( new StyleRange(region.getOffset(), region.getLength(), null, colorManager.getColor( Constants.COLOR_RELATED_ITEM))); } } } relatedItems = items; this.selectedItem = selectedItem; } private void resetRange (ITypedRegion region) { if (getSourceViewer() instanceof ITextViewerExtension2) ((ITextViewerExtension2) getSourceViewer()).invalidateTextPresentation(region.getOffset(), region.getLength()); else getSourceViewer().invalidateTextPresentation(); } public Item getSelectedItem (boolean allowFudge) { int caretOffset = getCaretOffset(); Item item = getItemSet().getSelectedItem(getCaretOffset()); if (null == item && caretOffset > 0) item = getItemSet().getSelectedItem(caretOffset - 1); return item; } public Item getSelectedItem () { return getItemSet().getSelectedItem(getCaretOffset()); } public int getCaretOffset () { return getSourceViewer().getTextWidget().getCaretOffset(); } private void clearCache () { this.itemSet = null; } public ItemSet getItemSet () { if (null == this.itemSet) { IResource resource = null; if (getEditorInput() instanceof IFileEditorInput) { resource = ((IFileEditorInput) getEditorInput()).getFile(); } else if (getEditorInput() instanceof JarEntryEditorInput) { resource = null; } this.itemSet = new ItemSet( getSourceViewer(), resource); } return this.itemSet; } public OutlinePage getOutlinePage() { return fOutlinePage; } public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.CTRL) { ctrlDown = true; } if (e.keyCode == SWT.SHIFT) { shiftDown = true; } if (e.keyCode == ']') { try { char c = getDocument().getChar(getCaretOffset()); if (c == ']') { // remove this getDocument().replace(getCaretOffset(), 1, ""); //$NON-NLS-1$ } } catch (BadLocationException e1) {} } else if (e.keyCode == '}') { try { char c = getDocument().getChar(getCaretOffset()); if (c == '}') { // remove this getDocument().replace(getCaretOffset(), 1, "}"); //$NON-NLS-1$ } } catch (BadLocationException e1) {} } } public void keyReleased(KeyEvent e) { if (e.keyCode == SWT.CTRL) { ctrlDown = false; } else if (e.keyCode == SWT.SHIFT) { shiftDown = false; } try { if (shiftDown && (e.keyCode == '3' || e.keyCode == '2')) { int offset = getCaretOffset(); char c = getSourceViewer().getDocument().getChar(offset-2); if (c == '[' || c == '<') { // directive char endChar = Character.MIN_VALUE; if (c == '[') endChar = ']'; else endChar = '>'; if (getSourceViewer().getDocument().getLength() > offset) { if (offset > 0) { for (int i=offset+1; i<getSourceViewer().getDocument().getLength(); i++) { char c2 = getSourceViewer().getDocument().getChar(i); if (i == endChar) return; else if (i == '\n') break; } getSourceViewer().getDocument().replace(offset, 0, new String(new char[]{endChar})); } } else { getSourceViewer().getDocument().replace(offset, 0, new String(new char[]{endChar})); } } } else if (shiftDown && e.keyCode == '[') { int offset = getCaretOffset(); char c = getSourceViewer().getDocument().getChar(offset-2); if (c == '$') { // interpolation if (getSourceViewer().getDocument().getLength() > offset) { if (offset > 0) { for (int i=offset+1; i<getSourceViewer().getDocument().getLength(); i++) { char c2 = getSourceViewer().getDocument().getChar(i); if (i == '}') return; else if (i == '\n') break; } getSourceViewer().getDocument().replace(offset, 0, "}"); //$NON-NLS-1$ } } else { getSourceViewer().getDocument().replace(offset, 0, "}"); //$NON-NLS-1$ } } } } catch (BadLocationException exc) { // do nothing } boolean stale = false; if (e.keyCode == SWT.DEL || e.keyCode == SWT.BS) { stale = true; } else if (null != getSelectedItem(true)) { stale = true; } else { char c = (char) e.keyCode; for (int j=0; j<VALIDATION_TOKENS.length; j++) { if (c == VALIDATION_TOKENS[j]) { stale = true; break; } } if (ctrlDown && (e.keyCode == 'v' || e.keyCode == 'x')) { stale = true; } } if (stale) { int offset = getCaretOffset(); Item item = getItemSet().getSelectedItem(offset); if (null == item && offset > 0) item = getItemSet().getSelectedItem(offset-1); if (Plugin.getInstance().getPreferenceStore().getBoolean( Constants.HIGHLIGHT_RELATED_ITEMS)) { if (null != item && null != item.getRelatedItems() && item.getRelatedItems().length > 0) { highlightRelatedRegions(item.getRelatedItems(), item); } else { highlightRelatedRegions(null, item); } } clearCache(); validateContents(); if (null != fOutlinePage) fOutlinePage.update(getSelectedItem()); } } public static Validator VALIDATOR; public synchronized void validateContents () { if (null == VALIDATOR) { VALIDATOR = new Validator(this); VALIDATOR.start(); } } public IProject getProject () { return ((IFileEditorInput) getEditorInput()).getFile().getProject(); } public IFile getFile () { return (null != getEditorInput()) ? ((IFileEditorInput) getEditorInput()).getFile() : null; } private Configuration fmConfiguration; public class Validator extends Thread { Editor editor; public Validator (Editor editor) { this.editor = editor; } public void run () { try { if (null != getFile()) { if (null == fmConfiguration) { fmConfiguration = new Configuration(); fmConfiguration.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX); } getFile().deleteMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); String pageContents = getDocument().get(); Reader reader = new StringReader(pageContents); new Template(getFile().getName(), reader, fmConfiguration); reader.close(); } } catch (ParseException e) { if (e.getMessage() != null) { String errorStr = e.getMessage(); int errorLine = 0; try { errorLine = e.getLineNumber(); if (errorLine == 0) { // sometimes they forget to put it in int index = e.getMessage().indexOf("line: "); //$NON-NLS-1$ if (index > 0) { int index2 = e.getMessage().indexOf(" ", index+6); //$NON-NLS-1$ int index3 = e.getMessage().indexOf(",", index+6); //$NON-NLS-1$ if (index3 < index2 && index3 > 0) index2 = index3; String s = e.getMessage().substring(index+6, index2); try { errorLine = Integer.parseInt(s); } catch (Exception e2) {} } } } catch (NullPointerException npe) { errorLine = 0; } editor.addProblemMarker(errorStr, errorLine); } } catch (Exception e) { Plugin.log(e); } finally { editor.VALIDATOR = null; } } } protected void editorSaved() { super.editorSaved(); validateContents(); } public boolean isEditorInputReadOnly() { return readOnly; } public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } public IEditorPart getEditorPart() { return this; } public ITextSelection getSelection() { return this.getSelection(); } public IStatus validateBreakpointPosition( int lineNumber, int offset ) { IStatus retval = null; try { int lineOffset = getDocument().getLineOffset( lineNumber - 1 ); int lineLength = getDocument().getLineLength( lineNumber - 1 ); /* go through all of the offets from the start of the line to the end of the line and see if any of the * lines contain a valid item */ Item breakpointItem = null; for( int i = lineOffset; i < lineOffset + lineLength; i++ ) { Item item = getItemSet().getItem( i ); if( item != null ) { breakpointItem = item; break; } } if( breakpointItem == null ) { retval = new MultiStatus( Plugin.ID, IStatus.ERROR, new IStatus[] { createErrorStatus( "Please add a breakpoint to a different line that contains " + "a valid freemarker directive, e.g. ${...}, <#...>, <@...>, etc." ) }, "Unable to set breakpoint on this line. Select Details for more info.", null ); } } catch( BadLocationException e ) { retval = createErrorStatus( "Unable to determine breakpoint offset.", e ); } return retval == null ? Status.OK_STATUS : retval; } private Status createErrorStatus( String msg ) { return new Status( IStatus.ERROR, Plugin.ID, msg ); } private Status createErrorStatus( String msg, Exception e ) { return new Status( IStatus.ERROR, Plugin.ID, msg, e ); } }