/** * Copyright (c) 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.xtext.ui.editor.formatting; import java.util.Iterator; 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.eclipse.emf.ecore.EObject; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.formatter.IContentFormatter; import org.eclipse.jface.text.formatter.IFormattingStrategy; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.swt.widgets.Display; import org.eclipse.xtext.parsetree.reconstr.IHiddenTokenHelper; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.serializer.diagnostic.ISerializationDiagnostic; import org.eclipse.xtext.serializer.sequencer.IContextFinder; import org.eclipse.xtext.ui.editor.formatting.IContentFormatterFactory; import org.eclipse.xtext.ui.editor.model.IXtextDocument; import org.eclipse.xtext.ui.editor.reconciler.ReplaceRegion; import org.eclipse.xtext.util.EmfFormatter; import org.eclipse.xtext.util.TextRegion; import org.eclipse.xtext.util.concurrent.IUnitOfWork; import com.google.inject.Inject; import com.google.inject.Provider; public class ContentFormatterFactory implements IContentFormatterFactory { public class ContentFormatter implements IContentFormatter { public void format(IDocument document, IRegion region) { IXtextDocument doc = (IXtextDocument) document; doc.modify(new FormattingUnitOfWork(doc, region)); } public IFormattingStrategy getFormattingStrategy(String contentType) { return null; } } public class FormattingUnitOfWork implements IUnitOfWork<ReplaceRegion, XtextResource> { protected final IRegion region; protected final IXtextDocument doc; public FormattingUnitOfWork(IXtextDocument doc, IRegion region) { super(); this.region = region; this.doc = doc; // Xtext issue, a dummy read only is needed before all modify operations. doc.readOnly(DummyReadOnly.Instance); } public ReplaceRegion exec(XtextResource state) throws Exception { // Do not format if there are syntax errors if(PPValidationUtils.hasSyntaxErrors(state)) { Display.getCurrent().beep(); return null; } // TODO: Q: viewer related parameters passed to formatting context? // TODO: A: the document is aware of the current Preference store - this could be used instead of the // roundabout way with the resoureScope. // TODO: get the dom root 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(region.getOffset(), region.getLength()), // getFormattingContextFactory().create(state, FormattingOption.Format), errors); ReplaceRegion replaceRegion = new ReplaceRegion(r.getOffset(), r.getLength(), r.getText()); try { if(replaceRegion != null) { String current = null; try { current = doc.get(replaceRegion.getOffset(), replaceRegion.getLength()); } catch(BadLocationException e) { // ignore, current is null } // Optimize - if replacement is equal to current if(current == null || !current.equals(replaceRegion.getText())) doc.replace(replaceRegion.getOffset(), replaceRegion.getLength(), r.getText()); } } catch(BadLocationException e) { throw new RuntimeException(e); } return replaceRegion; } finally { resourceScope.exit(); } } protected EObject getContext(EObject semanticObject) { Iterator<EObject> contexts = contextFinder.findContextsByContentsAndContainer(semanticObject, null).iterator(); if(!contexts.hasNext()) throw new RuntimeException("No Context for " + EmfFormatter.objPath(semanticObject) + " could be found"); return contexts.next(); } } @Inject private Provider<IDomModelFormatter> formatterProvider; @Inject private Provider<IFormattingContextFactory> formattingContextProvider; @Inject private ResourceAccessScope resourceScope; @Inject protected IContextFinder contextFinder; @Inject private Provider<DomBasedSerializer> serializerProvider; @Inject IHiddenTokenHelper hiddenTokenHelper; public IContentFormatter createConfiguredFormatter(SourceViewerConfiguration configuration, ISourceViewer sourceViewer) { // TODO: pick up important information about viewer and pass on // configuration.getTabWidth(sourceViewer); // ?? Always returns 4 ?!? return new ContentFormatter(); } 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(); } }