/** * Copyright (c) 2011, 2012 Cloudsmith Inc. and other contributors, as listed below. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cloudsmith * */ package org.cloudsmith.geppetto.pp.dsl.ui.editor.actions; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.cloudsmith.geppetto.pp.dsl.ui.linked.ISaveActions; import org.cloudsmith.geppetto.pp.dsl.ui.preferences.PPPreferencesHelper; import org.cloudsmith.geppetto.pp.dsl.validation.PPValidationUtils; import org.cloudsmith.xtext.dommodel.IDomNode; import org.cloudsmith.xtext.dommodel.formatter.IDomModelFormatter; import org.cloudsmith.xtext.dommodel.formatter.context.IFormattingContextFactory; import org.cloudsmith.xtext.dommodel.formatter.context.IFormattingContextFactory.FormattingOption; import org.cloudsmith.xtext.resource.ResourceAccessScope; import org.cloudsmith.xtext.serializer.DomBasedSerializer; import org.cloudsmith.xtext.ui.editor.formatting.DummyReadOnly; import org.eclipse.core.resources.IResource; import org.eclipse.jface.text.BadLocationException; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.serializer.diagnostic.ISerializationDiagnostic; import org.eclipse.xtext.ui.editor.model.IXtextDocument; import org.eclipse.xtext.ui.editor.reconciler.ReplaceRegion; import org.eclipse.xtext.util.TextRegion; import org.eclipse.xtext.util.concurrent.IUnitOfWork; import com.google.inject.Inject; import com.google.inject.Provider; /** * Implementation of save actions * */ public class SaveActions implements ISaveActions { // case '\u00A0': // NBSP // case '\u1680': // OGHAM SPACE MARK"); // case '\u2000': // EN QUAD"); // case '\u2001': // EM QUAD"); // case '\u2002': // EN SPACE"); // case '\u2003': // EM SPACE"); // case '\u2004': // THREE-PER-EM SPACE"); // case '\u2005': // FOUR-PER-EM SPACE"); // case '\u2006': // SIX-PER-EM SPACE"); // case '\u2007': // FIGURE SPACE"); // case '\u2008': // PUNCTUATION SPACE"); // case '\u2009': // THIN SPACE"); // case '\u200A': // HAIR SPACE"); // case '\u200B': // ZERO WIDTH SPACE"); // case '\u202F': // NARROW NO-BREAK SPACE"); // case '\u3000': // IDEOGRAPHIC SPACE"); public static String funkySpaces = "\\u00A0\\u1680\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u200B\\u202F\\u3000"; private static Pattern trimPattern = Pattern.compile("[ \\t\\f\\x0B" + funkySpaces + "]+(\\r\\n|\\n)"); private static Pattern funkySpacePattern = Pattern.compile("[\\f\\x0B" + funkySpaces + "]"); @Inject private PPPreferencesHelper preferenceHelper; @Inject private ResourceAccessScope resourceScope; @Inject private Provider<IDomModelFormatter> formatterProvider; @Inject private Provider<DomBasedSerializer> serializerProvider; @Inject private Provider<IFormattingContextFactory> formattingContextProvider; protected IDomModelFormatter getFormatter() { // get via injector, as formatter is resource dependent // return injector.getInstance(IDomModelFormatter.class); return formatterProvider.get(); } protected IFormattingContextFactory getFormattingContextFactory() { // get via injector, as formatting context may be resource dependent // return injector.getInstance(IFormattingContextFactory.class); return formattingContextProvider.get(); } protected DomBasedSerializer getSerializer() { // get via injector, as formatting context as serialization uses the formatter // which is resource dependent // return injector.getInstance(DomBasedSerializer.class); return serializerProvider.get(); } /* * (non-Javadoc) * * @see org.cloudsmith.geppetto.pp.dsl.ui.linked.ISaveActions#perform(org.eclipse.core.resources.IResource, * org.eclipse.xtext.ui.editor.model.IXtextDocument) */ @Override public void perform(IResource r, final IXtextDocument document) { final boolean ensureNl = preferenceHelper.getSaveActionEnsureEndsWithNewLine(r); final boolean replaceFunkySpace = preferenceHelper.getSaveActionReplaceFunkySpaces(r); final boolean trimLines = preferenceHelper.getSaveActionTrimLines(r); final boolean fullFormat = preferenceHelper.getSaveActionFormat(r); if(ensureNl || replaceFunkySpace || trimLines || fullFormat) { // Xtext issue, a dummy read only is needed before all modify operations. document.readOnly(DummyReadOnly.Instance); document.modify(new IUnitOfWork<ReplaceRegion, XtextResource>() { @Override public ReplaceRegion exec(XtextResource state) throws Exception { // No action if there are hard syntax errors if(!PPValidationUtils.hasSyntaxErrors(state) && process(state)) return new ReplaceRegion(0, document.getLength(), document.get()); return null; // return new ReplaceRegion(0, 0, ""); // nothing changed } public boolean process(XtextResource state) throws Exception { // Do any semantic changes here boolean changed = false; String content = document.get(); if(ensureNl) if(!content.endsWith("\n")) { content = content + "\n"; try { document.replace(content.length() - 1, 0, "\n"); content = document.get(); changed = true; } catch(BadLocationException e) { // ignore } } if(trimLines) { Matcher matcher = trimPattern.matcher(content); boolean mustRefetch = false; int lengthAdjustment = 0; while(matcher.find()) { int offset = matcher.start(); int length = matcher.end() - offset; try { String replacement = matcher.group(1); document.replace(offset - lengthAdjustment, length, replacement); lengthAdjustment += (length - replacement.length()); mustRefetch = true; changed = true; } catch(BadLocationException e) { // ignore } } if(mustRefetch) content = document.get(); } if(replaceFunkySpace) { Matcher matcher = funkySpacePattern.matcher(content); int lengthAdjustment = 0; while(matcher.find()) { int offset = matcher.start(); int length = matcher.end() - offset; try { document.replace(offset - lengthAdjustment, length, " "); lengthAdjustment += length - 1; changed = true; } catch(BadLocationException e) { // ignore } } } if(fullFormat) { // Most of this, and the required methods is a copy of ContentFormatterFactory - which // runs this in a separate UnitOfWork. TODO: Can be combined. // content = document.get(); ISerializationDiagnostic.Acceptor errors = ISerializationDiagnostic.EXCEPTION_THROWING_ACCEPTOR; try { resourceScope.enter(state); // EObject context = getContext(state.getContents().get(0)); IDomNode root = getSerializer().serializeToDom(state.getContents().get(0), false); org.eclipse.xtext.util.ReplaceRegion r = getFormatter().format( root, new TextRegion(0, document.getLength()), // getFormattingContextFactory().create(state, FormattingOption.Format), errors); if(!content.equals(r.getText())) { document.replace(0, document.getLength(), r.getText()); changed = true; } } finally { resourceScope.exit(); } } return changed; // no change } }); } } }