From 3a7b1217375975e0c40c21d8194c89357178c2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Galland?= Date: Wed, 24 Aug 2016 14:14:15 +0200 Subject: [PATCH] [lang][ui] Auto format code on paste. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close #372 Signed-off-by: Stéphane Galland --- .../io.sarl.lang.mwe2/GenerateSARL2.mwe2 | 2 +- .../io/sarl/lang/ui/AbstractSARLUiModule.java | 4 +- .../lang/ui/editor/DocumentAutoFormatter.java | 161 ++++++++++++++++++ .../ui/editor/IDocumentAutoFormatter.java | 61 +++++++ .../sarl/lang/ui/editor/SARLSourceViewer.java | 136 +++++++++++++++ .../SuppressWarningsAddModification.java | 11 +- .../lang/formatting2/FormatterFacade.java | 54 +++++- 7 files changed, 422 insertions(+), 7 deletions(-) create mode 100644 eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/DocumentAutoFormatter.java create mode 100644 eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/IDocumentAutoFormatter.java create mode 100644 eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/SARLSourceViewer.java diff --git a/eclipse-sarl/plugins/io.sarl.lang.mwe2/GenerateSARL2.mwe2 b/eclipse-sarl/plugins/io.sarl.lang.mwe2/GenerateSARL2.mwe2 index 96b2b7bffe..73aa315a42 100644 --- a/eclipse-sarl/plugins/io.sarl.lang.mwe2/GenerateSARL2.mwe2 +++ b/eclipse-sarl/plugins/io.sarl.lang.mwe2/GenerateSARL2.mwe2 @@ -829,7 +829,7 @@ Workflow { } ui = { bind = "org.eclipse.xtext.ui.editor.XtextSourceViewer$Factory" - to = "org.eclipse.xtend.ide.editor.RichStringAwareSourceViewer$Factory" + to = "io.sarl.lang.ui.editor.SARLSourceViewer$Factory" } ui = { bind = "org.eclipse.xtext.ui.editor.actions.IActionContributor" diff --git a/eclipse-sarl/plugins/io.sarl.lang.ui/src-gen/io/sarl/lang/ui/AbstractSARLUiModule.java b/eclipse-sarl/plugins/io.sarl.lang.ui/src-gen/io/sarl/lang/ui/AbstractSARLUiModule.java index 56d2aefe00..bcd11a49a7 100644 --- a/eclipse-sarl/plugins/io.sarl.lang.ui/src-gen/io/sarl/lang/ui/AbstractSARLUiModule.java +++ b/eclipse-sarl/plugins/io.sarl.lang.ui/src-gen/io/sarl/lang/ui/AbstractSARLUiModule.java @@ -39,6 +39,7 @@ import io.sarl.lang.ui.contentassist.SARLProposalProvider; import io.sarl.lang.ui.contentassist.SARLTemplateContextType; import io.sarl.lang.ui.contentassist.SARLTemplateProposalProvider; +import io.sarl.lang.ui.editor.SARLSourceViewer; import io.sarl.lang.ui.highlighting.SARLHighlightingCalculator; import io.sarl.lang.ui.images.IQualifiedNameImageProvider; import io.sarl.lang.ui.images.QualifiedPluginImageHelper; @@ -80,7 +81,6 @@ import org.eclipse.xtend.ide.editor.OccurrenceComputer; import org.eclipse.xtend.ide.editor.OverrideIndicatorModelListener; import org.eclipse.xtend.ide.editor.OverrideIndicatorRulerAction; -import org.eclipse.xtend.ide.editor.RichStringAwareSourceViewer; import org.eclipse.xtend.ide.editor.RichStringAwareToggleCommentAction; import org.eclipse.xtend.ide.editor.SingleLineCommentHelper; import org.eclipse.xtend.ide.editor.XtendDoubleClickStrategyProvider; @@ -686,7 +686,7 @@ public Class bindIResourceUIServiceProvide // contributed by io.sarl.lang.mwe2.binding.InjectionFragment2 [Bindings required by extended Xtend API] public Class bindXtextSourceViewer$Factory() { - return RichStringAwareSourceViewer.Factory.class; + return SARLSourceViewer.Factory.class; } // contributed by io.sarl.lang.mwe2.binding.InjectionFragment2 [Bindings required by extended Xtend API] diff --git a/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/DocumentAutoFormatter.java b/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/DocumentAutoFormatter.java new file mode 100644 index 0000000000..4f4f5bc9ef --- /dev/null +++ b/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/DocumentAutoFormatter.java @@ -0,0 +1,161 @@ +/* + * $Id$ + * + * SARL is an general-purpose agent programming language. + * More details on http://www.sarl.io + * + * Copyright (C) 2014-2016 the original authors or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sarl.lang.ui.editor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.formatter.IContentFormatter; +import org.eclipse.xtext.ui.editor.model.IXtextDocument; +import org.eclipse.xtext.util.Strings; +import org.eclipse.xtext.xbase.lib.Exceptions; + +/** A service that enables to do auto-formatting when a document changed. + * + *

FIXME: Remove if Xtext accept the patch https://github.com/eclipse/xtext-eclipse/pull/63 + * + * @author $Author: sgalland$ + * @version $FullVersion$ + * @mavengroupid $GroupId$ + * @mavenartifactid $ArtifactId$ + */ +public class DocumentAutoFormatter implements IDocumentAutoFormatter { + + private Collection formattingRequests = Collections.synchronizedList(new ArrayList<>(1)); + + private IDocumentListener autoFormatListener; + + private IXtextDocument document; + + private IContentFormatter contentFormatter; + + @Override + public void bind(IXtextDocument document, IContentFormatter contentFormatter) { + assert document != null; + assert contentFormatter != null; + this.document = document; + this.contentFormatter = contentFormatter; + } + + @Override + public synchronized void beginAutoFormat() { + if (this.document != null && this.autoFormatListener == null) { + this.formattingRequests.clear(); + this.autoFormatListener = new IDocumentListener() { + @Override + public void documentAboutToBeChanged(DocumentEvent event) { + // + } + + @SuppressWarnings("synthetic-access") + @Override + public void documentChanged(DocumentEvent event) { + if (!Strings.isEmpty(event.getText()) + && event.getDocument() instanceof IXtextDocument) { + DocumentAutoFormatter.this.formattingRequests.add(new RegionFormattingRequest( + (IXtextDocument) event.getDocument(), event.getOffset(), event.getText().length())); + } + } + }; + this.document.addDocumentListener(this.autoFormatListener); + } + } + + @Override + public void endAutoFormat() { + final Collection requests; + synchronized (this) { + requests = this.formattingRequests; + this.formattingRequests = Collections.synchronizedList(new ArrayList<>(1)); + if (this.autoFormatListener != null) { + final IDocumentListener listener = this.autoFormatListener; + this.autoFormatListener = null; + if (this.document != null) { + this.document.removeDocumentListener(listener); + } + } + } + if (this.contentFormatter != null) { + for (final RegionFormattingRequest request : requests) { + formatRegion(request.document, request.offset, request.length); + } + } + } + + /** Called for formatting a region. + * + * @param document the document to format. + * @param offset the offset of the text to format. + * @param length the length of the text. + */ + protected void formatRegion(IXtextDocument document, int offset, int length) { + try { + final int startRegionOffset = document.getLineInformationOfOffset( + previousSiblingChar(document, offset)).getOffset(); + final IRegion endLine = document.getLineInformationOfOffset(offset + length); + final int endRegionOffset = endLine.getOffset() + endLine.getLength(); + final int regionLength = endRegionOffset - startRegionOffset; + for (final IRegion region : document.computePartitioning(startRegionOffset, regionLength)) { + this.contentFormatter.format(document, region); + } + } catch (BadLocationException exception) { + Exceptions.sneakyThrow(exception); + } + } + + private static int previousSiblingChar(IXtextDocument document, int offset) throws BadLocationException { + int off = offset - 1; + while (off >= 0 && Character.isWhitespace(document.getChar(off))) { + --off; + } + return off; + } + + /** Request for formatting a region. + * @author $Author: sgalland$ + * @version $FullVersion$ + * @mavengroupid $GroupId$ + * @mavenartifactid $ArtifactId$ + */ + @SuppressWarnings("checkstyle:visibilitymodifier") + private static class RegionFormattingRequest { + + public final IXtextDocument document; + + public final int offset; + + public final int length; + + RegionFormattingRequest(IXtextDocument document, int offset, int length) { + this.document = document; + this.offset = offset; + this.length = length; + } + + } + +} diff --git a/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/IDocumentAutoFormatter.java b/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/IDocumentAutoFormatter.java new file mode 100644 index 0000000000..b9369c8051 --- /dev/null +++ b/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/IDocumentAutoFormatter.java @@ -0,0 +1,61 @@ +/* + * $Id$ + * + * SARL is an general-purpose agent programming language. + * More details on http://www.sarl.io + * + * Copyright (C) 2014-2016 the original authors or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sarl.lang.ui.editor; + +import com.google.inject.ImplementedBy; +import org.eclipse.jface.text.formatter.IContentFormatter; +import org.eclipse.xtext.ui.editor.model.IXtextDocument; + +/** A service that enables to do auto-formatting when a document changed. + * + *

FIXME: Remove if Xtext accept the patch https://github.com/eclipse/xtext-eclipse/pull/63 + * + * @author $Author: sgalland$ + * @version $FullVersion$ + * @mavengroupid $GroupId$ + * @mavenartifactid $ArtifactId$ + */ +@ImplementedBy(DocumentAutoFormatter.class) +public interface IDocumentAutoFormatter { + + /** Create an instance of document auto formatter. + * + * @param document the Xtext document associated to this auto-formatter. + * @param contentFormatter the formatter of content to be used. + */ + default void bind(IXtextDocument document, IContentFormatter contentFormatter) { + // + } + + /** Start auto-formating. + */ + default void beginAutoFormat() { + // + } + + /** End auto-formating. + */ + default void endAutoFormat() { + // + } + +} diff --git a/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/SARLSourceViewer.java b/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/SARLSourceViewer.java new file mode 100644 index 0000000000..8f5133c5a0 --- /dev/null +++ b/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/editor/SARLSourceViewer.java @@ -0,0 +1,136 @@ +/* + * $Id$ + * + * SARL is an general-purpose agent programming language. + * More details on http://www.sarl.io + * + * Copyright (C) 2014-2016 the original authors or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sarl.lang.ui.editor; + +import java.lang.reflect.Field; + +import javax.inject.Inject; + +import com.google.inject.MembersInjector; +import com.google.inject.Provider; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRewriteTarget; +import org.eclipse.jface.text.ITextOperationTarget; +import org.eclipse.jface.text.source.IOverviewRuler; +import org.eclipse.jface.text.source.IVerticalRuler; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.xtend.ide.editor.RichStringAwareSourceViewer; +import org.eclipse.xtend.ide.editor.TypedRegionMerger; +import org.eclipse.xtext.ui.editor.XtextSourceViewer; +import org.eclipse.xtext.ui.editor.model.IXtextDocument; +import org.eclipse.xtext.xbase.lib.Exceptions; + +/** Viewer of SARL code. + * + *

Based on the Xtend implementation, extended with the auto-formating feature when pasting. + * FIXME: Remove if Xtext accept the patch https://github.com/eclipse/xtext-eclipse/pull/63 + * + * @author $Author: sgalland$ + * @version $FullVersion$ + * @mavengroupid $GroupId$ + * @mavenartifactid $ArtifactId$ + */ +public class SARLSourceViewer extends RichStringAwareSourceViewer { + + @Inject + private Provider autoFormatterProvider; + + /** Constructor. + * + * @param parent the container. + * @param ruler the vertical ruler. + * @param overviewRuler the overview ruler. + * @param showsAnnotationOverview the annotation shower. + * @param styles the styles. + */ + public SARLSourceViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, + boolean showsAnnotationOverview, int styles) { + super(parent, ruler, overviewRuler, showsAnnotationOverview, styles); + } + + /** Replies the document auto-formatter. + * + * @return the service. + */ + public IDocumentAutoFormatter getDocumentAutoFormatter() { + final IDocument document = getDocument(); + if (document instanceof IXtextDocument) { + final IDocumentAutoFormatter formatter = this.autoFormatterProvider.get(); + formatter.bind((IXtextDocument) document, this.fContentFormatter); + return formatter; + } + return new IDocumentAutoFormatter() { + // + }; + } + + @Override + public void doOperation(int operation) { + if (operation == ITextOperationTarget.PASTE) { + final IRewriteTarget target = getRewriteTarget(); + target.beginCompoundChange(); + final IDocumentAutoFormatter formatter = getDocumentAutoFormatter(); + formatter.beginAutoFormat(); + try { + super.doOperation(operation); + } finally { + formatter.endAutoFormat(); + target.endCompoundChange(); + } + } else { + super.doOperation(operation); + } + } + + /** Factory of SARL code viewer. + * + * @author $Author: sgalland$ + * @version $FullVersion$ + * @mavengroupid $GroupId$ + * @mavenartifactid $ArtifactId$ + */ + public static class Factory implements XtextSourceViewer.Factory { + + @Inject + private TypedRegionMerger merger; + + @Inject + private MembersInjector memberInjector; + + @Override + public XtextSourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, + IOverviewRuler overviewRuler, boolean showsAnnotationOverview, int styles) { + final SARLSourceViewer result = new SARLSourceViewer(parent, ruler, overviewRuler, showsAnnotationOverview, styles); + try { + final Field field = RichStringAwareSourceViewer.class.getDeclaredField("merger"); //$NON-NLS-1$ + field.setAccessible(true); + field.set(result, this.merger); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException exception) { + Exceptions.sneakyThrow(exception); + } + this.memberInjector.injectMembers(result); + return result; + } + + } + +} diff --git a/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/quickfix/acceptors/SuppressWarningsAddModification.java b/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/quickfix/acceptors/SuppressWarningsAddModification.java index 73c623eb3c..c86d1a31bf 100644 --- a/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/quickfix/acceptors/SuppressWarningsAddModification.java +++ b/eclipse-sarl/plugins/io.sarl.lang.ui/src/io/sarl/lang/ui/quickfix/acceptors/SuppressWarningsAddModification.java @@ -80,9 +80,14 @@ private SuppressWarningsAddModification(URI uri, String code) { public static void accept(SARLQuickfixProvider provider, Issue issue, IssueResolutionAcceptor acceptor) { final IFile file = provider.getProjectUtil().findFileStorage(issue.getUriToProblem(), false); final ResourceSet resourceSet = provider.getResourceSetProvider().get(file.getProject()); - XtendAnnotationTarget eObject = EcoreUtil2.getContainerOfType( - resourceSet.getEObject(issue.getUriToProblem(), true), - XtendAnnotationTarget.class); + final EObject failingObject; + try { + failingObject = resourceSet.getEObject(issue.getUriToProblem(), true); + } catch (Exception exception) { + // Something is going really wrong. Must of the time the cause is a major syntax error. + return; + } + XtendAnnotationTarget eObject = EcoreUtil2.getContainerOfType(failingObject, XtendAnnotationTarget.class); boolean first = true; int relevance = IProposalRelevance.ADD_SUPPRESSWARNINGS; while (eObject != null) { diff --git a/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/formatting2/FormatterFacade.java b/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/formatting2/FormatterFacade.java index 400d502e38..6d173766f7 100644 --- a/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/formatting2/FormatterFacade.java +++ b/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/formatting2/FormatterFacade.java @@ -47,6 +47,7 @@ import org.eclipse.xtext.resource.XtextResourceSet; import org.eclipse.xtext.util.StringInputStream; import org.eclipse.xtext.xbase.lib.Exceptions; +import org.eclipse.xtext.xbase.lib.Pure; /** * Provide a facade for the SARL formatter. @@ -79,6 +80,7 @@ public class FormatterFacade { * @param sarlCode the code to format. * @return the code to format. */ + @Pure public String format(String sarlCode) { return format(sarlCode, new XtextResourceSet()); } @@ -90,6 +92,7 @@ public String format(String sarlCode) { * used for resolving types by the underlying code. * @return the code to format. */ + @Pure public String format(String sarlCode, ResourceSet resourceSet) { try { final URI createURI = URI.createURI("synthetic://to-be-formatted." + this.fileExtension); //$NON-NLS-1$ @@ -127,11 +130,14 @@ public void format(XtextResource resource) { } /** Format the code in the given resource. + * + *

This function does not change the resource content. * * @param resource the resource of the code to format. * @return the result of the formatting. */ - protected String formatResource(final XtextResource resource) { + @Pure + protected String formatResource(XtextResource resource) { assert resource != null; try { final ITextRegionAccess regionAccess = this.regionAccessBuilder.get().forNodeModel(resource).create(); @@ -148,4 +154,50 @@ protected String formatResource(final XtextResource resource) { } } + /** Format the code in the given region. + * + * @param resource the resource to format. + * @param offset the offset of the text to format. + * @param length the length of the text. + */ + public void formatRegion(XtextResource resource, int offset, int length) { + assert resource != null; + assert offset >= 0; + assert length >= 0; + final String result = formatResourceRegion(resource, offset, length); + // Write back to the resource + try (StringInputStream stringInputStream = new StringInputStream(result)) { + resource.load(stringInputStream, Collections.emptyMap()); + } catch (Exception exception) { + throw Exceptions.sneakyThrow(exception); + } + } + + /** Format the code in the given region. + * + *

This function does not change the resource content. + * + * @param resource the resource to format. + * @param offset the offset of the text to format. + * @param length the length of the text. + * @return the result of the formatting. + */ + @Pure + public String formatResourceRegion(XtextResource resource, int offset, int length) { + assert resource != null; + assert offset >= 0; + assert length >= 0; + try { + final ITextRegionAccess regionAccess = this.regionAccessBuilder.get().forNodeModel(resource).create(); + final FormatterRequest formatterRequest = new FormatterRequest(); + formatterRequest.setAllowIdentityEdits(false); + formatterRequest.setRegions(Collections.singleton(regionAccess.regionForOffset(offset, length))); + formatterRequest.setTextRegionAccess(regionAccess); + final List replacements = this.formatter.format(formatterRequest); + return regionAccess.getRewriter().renderToString(replacements); + } catch (Exception exception) { + throw Exceptions.sneakyThrow(exception); + } + } + }