diff --git a/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/FormattingTest.xtend b/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/FormattingTest.xtend new file mode 100644 index 000000000..0a1b7fe2a --- /dev/null +++ b/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/FormattingTest.xtend @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2016 itemis AG (http://www.itemis.com) and others. + * 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 + *******************************************************************************/ +package org.eclipse.xtext.ide.tests.server + +import io.typefox.lsapi.builders.RangeBuilder +import org.eclipse.xtext.ide.server.formatting.FormattingService +import org.eclipse.xtext.testing.AbstractLanguageServerTest +import org.junit.Test + +/** + * Tests for {@link FormattingService} + * + * @author Christian Dietrich - Initial contribution and API + */ +class FormattingTest extends AbstractTestLangLanguageServerTest { + + @Test def void testFormattingService() { + testFormatting [ + model = '''type Foo{int bar} type Bar{Foo foo}''' + expectedText = ''' + type Foo{ + int bar + } + type Bar{ + Foo foo + } + ''' + ] + } + + @Test def void testRangeFormattingService() { + testRangeFormatting [ + model = '''type Foo{int bar} type Bar{Foo foo}''' + range = new RangeBuilder[ + start(0,0) + end(0,17) + ].build + expectedText = ''' + type Foo{ + int bar + } type Bar{Foo foo}''' + ] + } + +} \ No newline at end of file diff --git a/org.eclipse.xtext.ide.tests/testlang-src-gen/META-INF/services/org.eclipse.xtext.ISetup b/org.eclipse.xtext.ide.tests/testlang-src/META-INF/services/org.eclipse.xtext.ISetup similarity index 100% rename from org.eclipse.xtext.ide.tests/testlang-src-gen/META-INF/services/org.eclipse.xtext.ISetup rename to org.eclipse.xtext.ide.tests/testlang-src/META-INF/services/org.eclipse.xtext.ISetup diff --git a/org.eclipse.xtext.ide.tests/testlang-src/org/eclipse/xtext/ide/tests/testlanguage/TestLanguageRuntimeModule.xtend b/org.eclipse.xtext.ide.tests/testlang-src/org/eclipse/xtext/ide/tests/testlanguage/TestLanguageRuntimeModule.xtend index 3df9cf07e..7d640a037 100644 --- a/org.eclipse.xtext.ide.tests/testlang-src/org/eclipse/xtext/ide/tests/testlanguage/TestLanguageRuntimeModule.xtend +++ b/org.eclipse.xtext.ide.tests/testlang-src/org/eclipse/xtext/ide/tests/testlanguage/TestLanguageRuntimeModule.xtend @@ -7,9 +7,16 @@ */ package org.eclipse.xtext.ide.tests.testlanguage +import org.eclipse.xtext.formatting2.IFormatter2 +import org.eclipse.xtext.ide.tests.testlanguage.formatting2.TestLanguageFormatter + /** * Use this class to register components to be used at runtime / without the Equinox extension registry. */ class TestLanguageRuntimeModule extends AbstractTestLanguageRuntimeModule { + + def Class bindIFormatter2() { + return TestLanguageFormatter; + } } diff --git a/org.eclipse.xtext.ide.tests/testlang-src/org/eclipse/xtext/ide/tests/testlanguage/formatting2/TestLanguageFormatter.xtend b/org.eclipse.xtext.ide.tests/testlang-src/org/eclipse/xtext/ide/tests/testlanguage/formatting2/TestLanguageFormatter.xtend new file mode 100644 index 000000000..a4f102f58 --- /dev/null +++ b/org.eclipse.xtext.ide.tests/testlang-src/org/eclipse/xtext/ide/tests/testlanguage/formatting2/TestLanguageFormatter.xtend @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2016 itemis AG (http://www.itemis.com) and others. + * 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 + *******************************************************************************/ +package org.eclipse.xtext.ide.tests.testlanguage.formatting2 + +import javax.inject.Inject +import org.eclipse.xtext.formatting2.AbstractFormatter2 +import org.eclipse.xtext.formatting2.IFormattableDocument +import org.eclipse.xtext.ide.tests.testlanguage.services.TestLanguageGrammarAccess +import org.eclipse.xtext.ide.tests.testlanguage.testLanguage.Model +import org.eclipse.xtext.ide.tests.testlanguage.testLanguage.Property +import org.eclipse.xtext.ide.tests.testlanguage.testLanguage.TypeDeclaration + +/** + * @author Christian Dietrich - Initial contribution and API + */ +class TestLanguageFormatter extends AbstractFormatter2 { + + @Inject extension TestLanguageGrammarAccess + + def dispatch void format(Model model, extension IFormattableDocument document) { + for (type : model.types) { + type.format + } + } + + def dispatch void format(TypeDeclaration type, extension IFormattableDocument document) { + //type.regionFor.keyword(typeDeclarationAccess.leftCurlyBracketKeyword_2).prepend[oneSpace] + type.regionFor.keyword(typeDeclarationAccess.leftCurlyBracketKeyword_2).append[newLine] + type.regionFor.keyword(typeDeclarationAccess.rightCurlyBracketKeyword_4).prepend[newLine].append[newLine] + interior( + type.regionFor.keyword(typeDeclarationAccess.leftCurlyBracketKeyword_2), + type.regionFor.keyword(typeDeclarationAccess.rightCurlyBracketKeyword_4) + ) [indent] + for (property : type.properties) { + property.format + } + } + def dispatch void format(Property property, extension IFormattableDocument document) { + property.append[newLine] + } + +} \ No newline at end of file diff --git a/org.eclipse.xtext.ide.tests/xtend-gen/org/eclipse/xtext/ide/tests/server/FormattingTest.java b/org.eclipse.xtext.ide.tests/xtend-gen/org/eclipse/xtext/ide/tests/server/FormattingTest.java new file mode 100644 index 000000000..a7bdba9de --- /dev/null +++ b/org.eclipse.xtext.ide.tests/xtend-gen/org/eclipse/xtext/ide/tests/server/FormattingTest.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2016 itemis AG (http://www.itemis.com) and others. + * 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 + */ +package org.eclipse.xtext.ide.tests.server; + +import io.typefox.lsapi.Range; +import io.typefox.lsapi.builders.RangeBuilder; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtext.ide.server.formatting.FormattingService; +import org.eclipse.xtext.ide.tests.server.AbstractTestLangLanguageServerTest; +import org.eclipse.xtext.testing.FormattingConfiguration; +import org.eclipse.xtext.testing.RangeFormattingConfiguration; +import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; +import org.junit.Test; + +/** + * Tests for {@link FormattingService} + * + * @author Christian Dietrich - Initial contribution and API + */ +@SuppressWarnings("all") +public class FormattingTest extends AbstractTestLangLanguageServerTest { + @Test + public void testFormattingService() { + final Procedure1 _function = (FormattingConfiguration it) -> { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("type Foo{int bar} type Bar{Foo foo}"); + it.setModel(_builder.toString()); + StringConcatenation _builder_1 = new StringConcatenation(); + _builder_1.append("type Foo{"); + _builder_1.newLine(); + _builder_1.append("\t"); + _builder_1.append("int bar"); + _builder_1.newLine(); + _builder_1.append("}"); + _builder_1.newLine(); + _builder_1.append("type Bar{"); + _builder_1.newLine(); + _builder_1.append("\t"); + _builder_1.append("Foo foo"); + _builder_1.newLine(); + _builder_1.append("}"); + _builder_1.newLine(); + it.setExpectedText(_builder_1.toString()); + }; + this.testFormatting(_function); + } + + @Test + public void testRangeFormattingService() { + final Procedure1 _function = (RangeFormattingConfiguration it) -> { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("type Foo{int bar} type Bar{Foo foo}"); + it.setModel(_builder.toString()); + final Procedure1 _function_1 = (RangeBuilder it_1) -> { + it_1.start(0, 0); + it_1.end(0, 17); + }; + RangeBuilder _rangeBuilder = new RangeBuilder(_function_1); + Range _build = _rangeBuilder.build(); + it.setRange(_build); + StringConcatenation _builder_1 = new StringConcatenation(); + _builder_1.append("type Foo{"); + _builder_1.newLine(); + _builder_1.append("\t"); + _builder_1.append("int bar"); + _builder_1.newLine(); + _builder_1.append("} type Bar{Foo foo}"); + it.setExpectedText(_builder_1.toString()); + }; + this.testRangeFormatting(_function); + } +} diff --git a/org.eclipse.xtext.ide.tests/xtend-gen/org/eclipse/xtext/ide/tests/testlanguage/TestLanguageRuntimeModule.java b/org.eclipse.xtext.ide.tests/xtend-gen/org/eclipse/xtext/ide/tests/testlanguage/TestLanguageRuntimeModule.java index eb254ba03..50df998bb 100644 --- a/org.eclipse.xtext.ide.tests/xtend-gen/org/eclipse/xtext/ide/tests/testlanguage/TestLanguageRuntimeModule.java +++ b/org.eclipse.xtext.ide.tests/xtend-gen/org/eclipse/xtext/ide/tests/testlanguage/TestLanguageRuntimeModule.java @@ -7,11 +7,16 @@ */ package org.eclipse.xtext.ide.tests.testlanguage; +import org.eclipse.xtext.formatting2.IFormatter2; import org.eclipse.xtext.ide.tests.testlanguage.AbstractTestLanguageRuntimeModule; +import org.eclipse.xtext.ide.tests.testlanguage.formatting2.TestLanguageFormatter; /** * Use this class to register components to be used at runtime / without the Equinox extension registry. */ @SuppressWarnings("all") public class TestLanguageRuntimeModule extends AbstractTestLanguageRuntimeModule { + public Class bindIFormatter2() { + return TestLanguageFormatter.class; + } } diff --git a/org.eclipse.xtext.ide.tests/xtend-gen/org/eclipse/xtext/ide/tests/testlanguage/formatting2/TestLanguageFormatter.java b/org.eclipse.xtext.ide.tests/xtend-gen/org/eclipse/xtext/ide/tests/testlanguage/formatting2/TestLanguageFormatter.java new file mode 100644 index 000000000..beba47283 --- /dev/null +++ b/org.eclipse.xtext.ide.tests/xtend-gen/org/eclipse/xtext/ide/tests/testlanguage/formatting2/TestLanguageFormatter.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2016 itemis AG (http://www.itemis.com) and others. + * 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 + */ +package org.eclipse.xtext.ide.tests.testlanguage.formatting2; + +import java.util.Arrays; +import javax.inject.Inject; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.formatting2.AbstractFormatter2; +import org.eclipse.xtext.formatting2.IFormattableDocument; +import org.eclipse.xtext.formatting2.IHiddenRegionFormatter; +import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion; +import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder; +import org.eclipse.xtext.ide.tests.testlanguage.services.TestLanguageGrammarAccess; +import org.eclipse.xtext.ide.tests.testlanguage.testLanguage.Model; +import org.eclipse.xtext.ide.tests.testlanguage.testLanguage.Property; +import org.eclipse.xtext.ide.tests.testlanguage.testLanguage.TypeDeclaration; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.xbase.lib.Extension; +import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; + +/** + * @author Christian Dietrich - Initial contribution and API + */ +@SuppressWarnings("all") +public class TestLanguageFormatter extends AbstractFormatter2 { + @Inject + @Extension + private TestLanguageGrammarAccess _testLanguageGrammarAccess; + + protected void _format(final Model model, @Extension final IFormattableDocument document) { + EList _types = model.getTypes(); + for (final TypeDeclaration type : _types) { + document.format(type); + } + } + + protected void _format(final TypeDeclaration type, @Extension final IFormattableDocument document) { + ISemanticRegionsFinder _regionFor = this.textRegionExtensions.regionFor(type); + TestLanguageGrammarAccess.TypeDeclarationElements _typeDeclarationAccess = this._testLanguageGrammarAccess.getTypeDeclarationAccess(); + Keyword _leftCurlyBracketKeyword_2 = _typeDeclarationAccess.getLeftCurlyBracketKeyword_2(); + ISemanticRegion _keyword = _regionFor.keyword(_leftCurlyBracketKeyword_2); + final Procedure1 _function = (IHiddenRegionFormatter it) -> { + it.newLine(); + }; + document.append(_keyword, _function); + ISemanticRegionsFinder _regionFor_1 = this.textRegionExtensions.regionFor(type); + TestLanguageGrammarAccess.TypeDeclarationElements _typeDeclarationAccess_1 = this._testLanguageGrammarAccess.getTypeDeclarationAccess(); + Keyword _rightCurlyBracketKeyword_4 = _typeDeclarationAccess_1.getRightCurlyBracketKeyword_4(); + ISemanticRegion _keyword_1 = _regionFor_1.keyword(_rightCurlyBracketKeyword_4); + final Procedure1 _function_1 = (IHiddenRegionFormatter it) -> { + it.newLine(); + }; + ISemanticRegion _prepend = document.prepend(_keyword_1, _function_1); + final Procedure1 _function_2 = (IHiddenRegionFormatter it) -> { + it.newLine(); + }; + document.append(_prepend, _function_2); + ISemanticRegionsFinder _regionFor_2 = this.textRegionExtensions.regionFor(type); + TestLanguageGrammarAccess.TypeDeclarationElements _typeDeclarationAccess_2 = this._testLanguageGrammarAccess.getTypeDeclarationAccess(); + Keyword _leftCurlyBracketKeyword_2_1 = _typeDeclarationAccess_2.getLeftCurlyBracketKeyword_2(); + ISemanticRegion _keyword_2 = _regionFor_2.keyword(_leftCurlyBracketKeyword_2_1); + ISemanticRegionsFinder _regionFor_3 = this.textRegionExtensions.regionFor(type); + TestLanguageGrammarAccess.TypeDeclarationElements _typeDeclarationAccess_3 = this._testLanguageGrammarAccess.getTypeDeclarationAccess(); + Keyword _rightCurlyBracketKeyword_4_1 = _typeDeclarationAccess_3.getRightCurlyBracketKeyword_4(); + ISemanticRegion _keyword_3 = _regionFor_3.keyword(_rightCurlyBracketKeyword_4_1); + final Procedure1 _function_3 = (IHiddenRegionFormatter it) -> { + it.indent(); + }; + document.interior(_keyword_2, _keyword_3, _function_3); + EList _properties = type.getProperties(); + for (final Property property : _properties) { + document.format(property); + } + } + + protected void _format(final Property property, @Extension final IFormattableDocument document) { + final Procedure1 _function = (IHiddenRegionFormatter it) -> { + it.newLine(); + }; + document.append(property, _function); + } + + public void format(final Object model, final IFormattableDocument document) { + if (model instanceof XtextResource) { + _format((XtextResource)model, document); + return; + } else if (model instanceof Model) { + _format((Model)model, document); + return; + } else if (model instanceof Property) { + _format((Property)model, document); + return; + } else if (model instanceof TypeDeclaration) { + _format((TypeDeclaration)model, document); + return; + } else if (model instanceof EObject) { + _format((EObject)model, document); + return; + } else if (model == null) { + _format((Void)null, document); + return; + } else if (model != null) { + _format(model, document); + return; + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(model, document).toString()); + } + } +} diff --git a/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/Document.xtend b/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/Document.xtend index b0d24dad4..a2ab81c0f 100644 --- a/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/Document.xtend +++ b/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/Document.xtend @@ -46,7 +46,7 @@ import org.eclipse.xtend.lib.annotations.Data def PositionImpl getPosition(int offset) { val l = contents.length - if (offset < 0 || offset >= l) + if (offset < 0 || offset > l) throw new IndexOutOfBoundsException(offset + " text was : " + contents) val char NL = '\n' diff --git a/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LanguageServerImpl.xtend b/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LanguageServerImpl.xtend index 097dfedde..b9fee93b9 100644 --- a/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LanguageServerImpl.xtend +++ b/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LanguageServerImpl.xtend @@ -55,6 +55,7 @@ import io.typefox.lsapi.services.LanguageServer import io.typefox.lsapi.services.TextDocumentService import io.typefox.lsapi.services.WindowService import io.typefox.lsapi.services.WorkspaceService +import java.util.Collections import java.util.List import java.util.concurrent.CompletableFuture import java.util.function.Consumer @@ -74,6 +75,7 @@ import org.eclipse.xtext.resource.FileExtensionProvider import org.eclipse.xtext.resource.IMimeTypeProvider import org.eclipse.xtext.resource.IResourceServiceProvider import org.eclipse.xtext.validation.Issue +import org.eclipse.xtext.ide.server.formatting.FormattingService /** * @author Sven Efftinge - Initial contribution and API @@ -116,6 +118,8 @@ import org.eclipse.xtext.validation.Issue resolveProvider = false triggerCharacters = #["."] ] + documentFormattingProvider = true + documentRangeFormattingProvider = true ] result.supportedLanguages = newArrayList() for (serviceProvider : languagesRegistry.extensionToFactoryMap.values.filter(IResourceServiceProvider).toSet) { @@ -436,11 +440,35 @@ import org.eclipse.xtext.validation.Issue } override formatting(DocumentFormattingParams params) { - throw new UnsupportedOperationException("TODO: auto-generated method stub") + return requestManager.runRead[ cancelIndicator | + val uri = params.textDocument.uri.toUri + val resourceServiceProvider = uri.resourceServiceProvider + val formatterService = resourceServiceProvider?.get(FormattingService) + if (formatterService === null) + return Collections.emptyList + + return workspaceManager.doRead(uri) [ document, resource | + val offset = 0 + val length = document.contents.length + return formatterService.format(resource, document, offset, length) + ] + ] } override rangeFormatting(DocumentRangeFormattingParams params) { - throw new UnsupportedOperationException("TODO: auto-generated method stub") + return requestManager.runRead[ cancelIndicator | + val uri = params.textDocument.uri.toUri + val resourceServiceProvider = uri.resourceServiceProvider + val formatterService = resourceServiceProvider?.get(FormattingService) + if (formatterService === null) + return Collections.emptyList + + return workspaceManager.doRead(uri) [ document, resource | + val offset = document.getOffSet(params.range.start) + val length = document.getOffSet(params.range.end) - offset + return formatterService.format(resource, document, offset, length) + ] + ] } override onTypeFormatting(DocumentOnTypeFormattingParams params) { diff --git a/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/formatting/FormattingService.xtend b/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/formatting/FormattingService.xtend new file mode 100644 index 000000000..d6e77873a --- /dev/null +++ b/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/formatting/FormattingService.xtend @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2016 itemis AG (http://www.itemis.com) and others. + * 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 + *******************************************************************************/ +package org.eclipse.xtext.ide.server.formatting + +import com.google.inject.Inject +import com.google.inject.Provider +import io.typefox.lsapi.TextEdit +import io.typefox.lsapi.impl.RangeImpl +import io.typefox.lsapi.impl.TextEditImpl +import java.util.List +import org.eclipse.xtext.formatting2.FormatterRequest +import org.eclipse.xtext.formatting2.IFormatter2 +import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder +import org.eclipse.xtext.ide.server.Document +import org.eclipse.xtext.preferences.ITypedPreferenceValues +import org.eclipse.xtext.resource.XtextResource +import org.eclipse.xtext.util.ITextRegion +import org.eclipse.xtext.util.TextRegion + +/** + * Language Service Implementation for Formatting and Range-Formatting + * + * @author Christian Dietrich - Initial contribution and API + * @since 2.11 + */ +class FormattingService { + + @Inject(optional = true) Provider formatter2Provider + + @Inject Provider formatterRequestProvider + + @Inject TextRegionAccessBuilder regionBuilder + + def List format(XtextResource resource, Document document, int offset, int length) { + if (formatter2Provider !== null) { + val replacements = format2(resource, new TextRegion(offset, length), null) + return replacements.map[ + r | document.toTextEdit(r.replacementText, r.offset, r.length) + ].toList + } else { + return newArrayList + } + + } + + protected def TextEdit toTextEdit(Document document, String formattedText, int startOffset, int length) { + new TextEditImpl => [ + newText = formattedText + range = new RangeImpl => [ + start = document.getPosition(startOffset) + end = document.getPosition(startOffset+length) + ] + ] + } + + + protected def format2(XtextResource resource, ITextRegion selection, ITypedPreferenceValues preferences) { + val request = formatterRequestProvider.get() + request.allowIdentityEdits = false + request.formatUndefinedHiddenRegionsOnly = false + if (selection !== null) + request.regions = #[selection] + if (preferences !== null) { + request.preferences = preferences + } + val regionAccess = regionBuilder.forNodeModel(resource).create() + request.textRegionAccess = regionAccess + val formatter2 = formatter2Provider.get(); + val replacements = formatter2.format(request) + return replacements + } + +} \ No newline at end of file diff --git a/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/Document.java b/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/Document.java index 98253945e..a9e30e4a6 100644 --- a/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/Document.java +++ b/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/Document.java @@ -56,7 +56,7 @@ public class Document { public PositionImpl getPosition(final int offset) { final int l = this.contents.length(); - if (((offset < 0) || (offset >= l))) { + if (((offset < 0) || (offset > l))) { String _plus = (Integer.valueOf(offset) + " text was : "); String _plus_1 = (_plus + this.contents); throw new IndexOutOfBoundsException(_plus_1); diff --git a/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/LanguageServerImpl.java b/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/LanguageServerImpl.java index bad93a1c7..658c6a718 100644 --- a/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/LanguageServerImpl.java +++ b/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/LanguageServerImpl.java @@ -92,6 +92,7 @@ import org.eclipse.xtext.ide.server.concurrent.CancellableIndicator; import org.eclipse.xtext.ide.server.concurrent.RequestManager; import org.eclipse.xtext.ide.server.contentassist.ContentAssistService; import org.eclipse.xtext.ide.server.findReferences.WorkspaceResourceAccess; +import org.eclipse.xtext.ide.server.formatting.FormattingService; import org.eclipse.xtext.ide.server.hover.HoverService; import org.eclipse.xtext.ide.server.symbol.DocumentSymbolService; import org.eclipse.xtext.ide.server.symbol.WorkspaceSymbolService; @@ -176,6 +177,8 @@ public class LanguageServerImpl implements LanguageServer, WorkspaceService, Win }; CompletionOptionsImpl _doubleArrow = ObjectExtensions.operator_doubleArrow(_completionOptionsImpl, _function_1); it.setCompletionProvider(_doubleArrow); + it.setDocumentFormattingProvider(Boolean.valueOf(true)); + it.setDocumentRangeFormattingProvider(Boolean.valueOf(true)); }; ServerCapabilitiesImpl _doubleArrow = ObjectExtensions.operator_doubleArrow(_serverCapabilitiesImpl, _function); result.setCapabilities(_doubleArrow); @@ -735,12 +738,58 @@ public class LanguageServerImpl implements LanguageServer, WorkspaceService, Win @Override public CompletableFuture> formatting(final DocumentFormattingParams params) { - throw new UnsupportedOperationException("TODO: auto-generated method stub"); + final Function1> _function = (CancelIndicator cancelIndicator) -> { + TextDocumentIdentifier _textDocument = params.getTextDocument(); + String _uri = _textDocument.getUri(); + final URI uri = this._uriExtensions.toUri(_uri); + final IResourceServiceProvider resourceServiceProvider = this.languagesRegistry.getResourceServiceProvider(uri); + FormattingService _get = null; + if (resourceServiceProvider!=null) { + _get=resourceServiceProvider.get(FormattingService.class); + } + final FormattingService formatterService = _get; + if ((formatterService == null)) { + return Collections.emptyList(); + } + final Function2> _function_1 = (Document document, XtextResource resource) -> { + final int offset = 0; + String _contents = document.getContents(); + final int length = _contents.length(); + return formatterService.format(resource, document, offset, length); + }; + return this.workspaceManager.>doRead(uri, _function_1); + }; + return this.requestManager.>runRead(_function); } @Override public CompletableFuture> rangeFormatting(final DocumentRangeFormattingParams params) { - throw new UnsupportedOperationException("TODO: auto-generated method stub"); + final Function1> _function = (CancelIndicator cancelIndicator) -> { + TextDocumentIdentifier _textDocument = params.getTextDocument(); + String _uri = _textDocument.getUri(); + final URI uri = this._uriExtensions.toUri(_uri); + final IResourceServiceProvider resourceServiceProvider = this.languagesRegistry.getResourceServiceProvider(uri); + FormattingService _get = null; + if (resourceServiceProvider!=null) { + _get=resourceServiceProvider.get(FormattingService.class); + } + final FormattingService formatterService = _get; + if ((formatterService == null)) { + return Collections.emptyList(); + } + final Function2> _function_1 = (Document document, XtextResource resource) -> { + Range _range = params.getRange(); + Position _start = _range.getStart(); + final int offset = document.getOffSet(_start); + Range _range_1 = params.getRange(); + Position _end = _range_1.getEnd(); + int _offSet = document.getOffSet(_end); + final int length = (_offSet - offset); + return formatterService.format(resource, document, offset, length); + }; + return this.workspaceManager.>doRead(uri, _function_1); + }; + return this.requestManager.>runRead(_function); } @Override diff --git a/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/formatting/FormattingService.java b/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/formatting/FormattingService.java new file mode 100644 index 000000000..acd90e95f --- /dev/null +++ b/org.eclipse.xtext.ide/xtend-gen/org/eclipse/xtext/ide/server/formatting/FormattingService.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2016 itemis AG (http://www.itemis.com) and others. + * 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 + */ +package org.eclipse.xtext.ide.server.formatting; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import io.typefox.lsapi.TextEdit; +import io.typefox.lsapi.impl.PositionImpl; +import io.typefox.lsapi.impl.RangeImpl; +import io.typefox.lsapi.impl.TextEditImpl; +import java.util.Collections; +import java.util.List; +import org.eclipse.xtext.formatting2.FormatterRequest; +import org.eclipse.xtext.formatting2.IFormatter2; +import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess; +import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement; +import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder; +import org.eclipse.xtext.ide.server.Document; +import org.eclipse.xtext.preferences.ITypedPreferenceValues; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.util.ITextRegion; +import org.eclipse.xtext.util.TextRegion; +import org.eclipse.xtext.xbase.lib.CollectionLiterals; +import org.eclipse.xtext.xbase.lib.Functions.Function1; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.ListExtensions; +import org.eclipse.xtext.xbase.lib.ObjectExtensions; +import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; + +/** + * Language Service Implementation for Formatting and Range-Formatting + * + * @author Christian Dietrich - Initial contribution and API + * @since 2.11 + */ +@SuppressWarnings("all") +public class FormattingService { + @Inject(optional = true) + private Provider formatter2Provider; + + @Inject + private Provider formatterRequestProvider; + + @Inject + private TextRegionAccessBuilder regionBuilder; + + public List format(final XtextResource resource, final Document document, final int offset, final int length) { + if ((this.formatter2Provider != null)) { + TextRegion _textRegion = new TextRegion(offset, length); + final List replacements = this.format2(resource, _textRegion, null); + final Function1 _function = (ITextReplacement r) -> { + String _replacementText = r.getReplacementText(); + int _offset = r.getOffset(); + int _length = r.getLength(); + return this.toTextEdit(document, _replacementText, _offset, _length); + }; + List _map = ListExtensions.map(replacements, _function); + return IterableExtensions.toList(_map); + } else { + return CollectionLiterals.newArrayList(); + } + } + + protected TextEdit toTextEdit(final Document document, final String formattedText, final int startOffset, final int length) { + TextEditImpl _textEditImpl = new TextEditImpl(); + final Procedure1 _function = (TextEditImpl it) -> { + it.setNewText(formattedText); + RangeImpl _rangeImpl = new RangeImpl(); + final Procedure1 _function_1 = (RangeImpl it_1) -> { + PositionImpl _position = document.getPosition(startOffset); + it_1.setStart(_position); + PositionImpl _position_1 = document.getPosition((startOffset + length)); + it_1.setEnd(_position_1); + }; + RangeImpl _doubleArrow = ObjectExtensions.operator_doubleArrow(_rangeImpl, _function_1); + it.setRange(_doubleArrow); + }; + return ObjectExtensions.operator_doubleArrow(_textEditImpl, _function); + } + + protected List format2(final XtextResource resource, final ITextRegion selection, final ITypedPreferenceValues preferences) { + final FormatterRequest request = this.formatterRequestProvider.get(); + request.setAllowIdentityEdits(false); + request.setFormatUndefinedHiddenRegionsOnly(false); + if ((selection != null)) { + request.setRegions(Collections.unmodifiableList(CollectionLiterals.newArrayList(selection))); + } + if ((preferences != null)) { + request.setPreferences(preferences); + } + TextRegionAccessBuilder _forNodeModel = this.regionBuilder.forNodeModel(resource); + final ITextRegionAccess regionAccess = _forNodeModel.create(); + request.setTextRegionAccess(regionAccess); + final IFormatter2 formatter2 = this.formatter2Provider.get(); + final List replacements = formatter2.format(request); + return replacements; + } +} diff --git a/org.eclipse.xtext.testing/src/org/eclipse/xtext/testing/AbstractLanguageServerTest.xtend b/org.eclipse.xtext.testing/src/org/eclipse/xtext/testing/AbstractLanguageServerTest.xtend index 92a3757ac..21dcf04ab 100644 --- a/org.eclipse.xtext.testing/src/org/eclipse/xtext/testing/AbstractLanguageServerTest.xtend +++ b/org.eclipse.xtext.testing/src/org/eclipse/xtext/testing/AbstractLanguageServerTest.xtend @@ -52,6 +52,11 @@ import org.eclipse.xtext.util.Files import org.eclipse.xtext.util.Modules2 import org.junit.Assert import org.junit.Before +import io.typefox.lsapi.impl.RangeImpl +import io.typefox.lsapi.impl.PositionImpl +import io.typefox.lsapi.builders.DocumentFormattingParamsBuilder +import org.eclipse.xtext.ide.server.Document +import io.typefox.lsapi.builders.DocumentRangeFormattingParamsBuilder /** * @author Sven Efftinge - Initial contribution and API @@ -318,7 +323,42 @@ abstract class AbstractLanguageServerTest implements Consumervoid configurator) { + val extension configuration = new FormattingConfiguration + configuration.filePath = 'MyModel.' + fileExtension + configurator.apply(configuration) + + val fileUri = filePath -> model + + initialize + open(fileUri, model) + val changes = languageServer.formatting(new DocumentFormattingParamsBuilder [ + textDocument(fileUri) + ].build) + val result = new Document(1, model).applyChanges(newArrayList(changes.get()).reverse) + assertEquals(configuration.expectedText, result.contents) + + } + + protected def testRangeFormatting((RangeFormattingConfiguration)=>void configurator) { + val extension configuration = new RangeFormattingConfiguration + configuration.filePath = 'MyModel.' + fileExtension + configurator.apply(configuration) + val fileUri = filePath -> model + + initialize + open(fileUri, model) + val changes = languageServer.rangeFormatting(new DocumentRangeFormattingParamsBuilder [ + textDocument(fileUri) + range(configuration.range) + ].build) + val result = new Document(1, model).applyChanges(newArrayList(changes.get()).reverse) + assertEquals(configuration.expectedText, result.contents) + } } @@ -366,3 +406,16 @@ class TextDocumentConfiguration { String model = '' String filePath } + +@Accessors +class FormattingConfiguration extends TextDocumentConfiguration { + String expectedText = '' +} + +@Accessors +class RangeFormattingConfiguration extends FormattingConfiguration { + Range range = new RangeImpl=>[ + start = new PositionImpl(0,0) + end = new PositionImpl(0,1) + ] +} diff --git a/org.eclipse.xtext.testing/xtend-gen/org/eclipse/xtext/testing/AbstractLanguageServerTest.java b/org.eclipse.xtext.testing/xtend-gen/org/eclipse/xtext/testing/AbstractLanguageServerTest.java index 7dfae918c..a14f427e2 100644 --- a/org.eclipse.xtext.testing/xtend-gen/org/eclipse/xtext/testing/AbstractLanguageServerTest.java +++ b/org.eclipse.xtext.testing/xtend-gen/org/eclipse/xtext/testing/AbstractLanguageServerTest.java @@ -19,6 +19,8 @@ import io.typefox.lsapi.CompletionList; import io.typefox.lsapi.Diagnostic; import io.typefox.lsapi.DidCloseTextDocumentParams; import io.typefox.lsapi.DidOpenTextDocumentParams; +import io.typefox.lsapi.DocumentFormattingParams; +import io.typefox.lsapi.DocumentRangeFormattingParams; import io.typefox.lsapi.Hover; import io.typefox.lsapi.InitializeParams; import io.typefox.lsapi.InitializeResult; @@ -34,6 +36,8 @@ import io.typefox.lsapi.TextDocumentPositionParams; import io.typefox.lsapi.TextEdit; import io.typefox.lsapi.builders.DidCloseTextDocumentParamsBuilder; import io.typefox.lsapi.builders.DidOpenTextDocumentParamsBuilder; +import io.typefox.lsapi.builders.DocumentFormattingParamsBuilder; +import io.typefox.lsapi.builders.DocumentRangeFormattingParamsBuilder; import io.typefox.lsapi.builders.InitializeParamsBuilder; import io.typefox.lsapi.builders.ReferenceParamsBuilder; import io.typefox.lsapi.builders.TextDocumentItemBuilder; @@ -48,6 +52,7 @@ import java.io.FileWriter; import java.net.URI; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -57,6 +62,7 @@ import org.eclipse.xtend.lib.annotations.Accessors; import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor; import org.eclipse.xtend2.lib.StringConcatenation; import org.eclipse.xtext.LanguageInfo; +import org.eclipse.xtext.ide.server.Document; import org.eclipse.xtext.ide.server.LanguageServerImpl; import org.eclipse.xtext.ide.server.ServerModule; import org.eclipse.xtext.ide.server.UriExtensions; @@ -64,7 +70,9 @@ import org.eclipse.xtext.ide.server.concurrent.RequestManager; import org.eclipse.xtext.resource.IResourceServiceProvider; import org.eclipse.xtext.testing.DefinitionTestConfiguration; import org.eclipse.xtext.testing.DocumentSymbolConfiguraiton; +import org.eclipse.xtext.testing.FormattingConfiguration; import org.eclipse.xtext.testing.HoverTestConfiguration; +import org.eclipse.xtext.testing.RangeFormattingConfiguration; import org.eclipse.xtext.testing.ReferenceTestConfiguration; import org.eclipse.xtext.testing.TestCompletionConfiguration; import org.eclipse.xtext.testing.WorkspaceSymbolConfiguraiton; @@ -72,9 +80,11 @@ import org.eclipse.xtext.util.CancelIndicator; import org.eclipse.xtext.util.Files; import org.eclipse.xtext.util.Modules2; import org.eclipse.xtext.xbase.lib.CollectionLiterals; +import org.eclipse.xtext.xbase.lib.Conversions; import org.eclipse.xtext.xbase.lib.Exceptions; import org.eclipse.xtext.xbase.lib.Extension; import org.eclipse.xtext.xbase.lib.Functions.Function1; +import org.eclipse.xtext.xbase.lib.ListExtensions; import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; import org.eclipse.xtext.xbase.lib.Pure; import org.eclipse.xtext.xbase.lib.StringExtensions; @@ -597,7 +607,74 @@ public abstract class AbstractLanguageServerTest implements Consumer configurator) { + try { + @Extension + final FormattingConfiguration configuration = new FormattingConfiguration(); + configuration.setFilePath(("MyModel." + this.fileExtension)); + configurator.apply(configuration); + String _filePath = configuration.getFilePath(); + String _model = configuration.getModel(); + final String fileUri = this.operator_mappedTo(_filePath, _model); + this.initialize(); + String _model_1 = configuration.getModel(); + this.open(fileUri, _model_1); + final Procedure1 _function = (DocumentFormattingParamsBuilder it) -> { + it.textDocument(fileUri); + }; + DocumentFormattingParamsBuilder _documentFormattingParamsBuilder = new DocumentFormattingParamsBuilder(_function); + DocumentFormattingParams _build = _documentFormattingParamsBuilder.build(); + final CompletableFuture> changes = this.languageServer.formatting(_build); + String _model_2 = configuration.getModel(); + Document _document = new Document(1, _model_2); + List _get = changes.get(); + ArrayList _newArrayList = CollectionLiterals.newArrayList(((TextEdit[])Conversions.unwrapArray(_get, TextEdit.class))); + List _reverse = ListExtensions.reverse(_newArrayList); + final Document result = _document.applyChanges(_reverse); + String _expectedText = configuration.getExpectedText(); + String _contents = result.getContents(); + this.assertEquals(_expectedText, _contents); + } catch (Throwable _e) { + throw Exceptions.sneakyThrow(_e); + } + } + + protected void testRangeFormatting(final Procedure1 configurator) { + try { + @Extension + final RangeFormattingConfiguration configuration = new RangeFormattingConfiguration(); + configuration.setFilePath(("MyModel." + this.fileExtension)); + configurator.apply(configuration); + String _filePath = configuration.getFilePath(); + String _model = configuration.getModel(); + final String fileUri = this.operator_mappedTo(_filePath, _model); + this.initialize(); + String _model_1 = configuration.getModel(); + this.open(fileUri, _model_1); + final Procedure1 _function = (DocumentRangeFormattingParamsBuilder it) -> { + it.textDocument(fileUri); + Range _range = configuration.getRange(); + it.range(_range); + }; + DocumentRangeFormattingParamsBuilder _documentRangeFormattingParamsBuilder = new DocumentRangeFormattingParamsBuilder(_function); + DocumentRangeFormattingParams _build = _documentRangeFormattingParamsBuilder.build(); + final CompletableFuture> changes = this.languageServer.rangeFormatting(_build); + String _model_2 = configuration.getModel(); + Document _document = new Document(1, _model_2); + List _get = changes.get(); + ArrayList _newArrayList = CollectionLiterals.newArrayList(((TextEdit[])Conversions.unwrapArray(_get, TextEdit.class))); + List _reverse = ListExtensions.reverse(_newArrayList); + final Document result = _document.applyChanges(_reverse); + String _expectedText = configuration.getExpectedText(); + String _contents = result.getContents(); + this.assertEquals(_expectedText, _contents); + } catch (Throwable _e) { + throw Exceptions.sneakyThrow(_e); + } } protected String toExpectation(final Object elements) { diff --git a/org.eclipse.xtext.testing/xtend-gen/org/eclipse/xtext/testing/FormattingConfiguration.java b/org.eclipse.xtext.testing/xtend-gen/org/eclipse/xtext/testing/FormattingConfiguration.java new file mode 100644 index 000000000..c74bf83a0 --- /dev/null +++ b/org.eclipse.xtext.testing/xtend-gen/org/eclipse/xtext/testing/FormattingConfiguration.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2016 TypeFox GmbH (http://www.typefox.io) and others. + * 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 + */ +package org.eclipse.xtext.testing; + +import org.eclipse.xtend.lib.annotations.Accessors; +import org.eclipse.xtext.testing.TextDocumentConfiguration; +import org.eclipse.xtext.xbase.lib.Pure; + +@Accessors +@SuppressWarnings("all") +public class FormattingConfiguration extends TextDocumentConfiguration { + private String expectedText = ""; + + @Pure + public String getExpectedText() { + return this.expectedText; + } + + public void setExpectedText(final String expectedText) { + this.expectedText = expectedText; + } +} diff --git a/org.eclipse.xtext.testing/xtend-gen/org/eclipse/xtext/testing/RangeFormattingConfiguration.java b/org.eclipse.xtext.testing/xtend-gen/org/eclipse/xtext/testing/RangeFormattingConfiguration.java new file mode 100644 index 000000000..8ec1e121a --- /dev/null +++ b/org.eclipse.xtext.testing/xtend-gen/org/eclipse/xtext/testing/RangeFormattingConfiguration.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016 TypeFox GmbH (http://www.typefox.io) and others. + * 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 + */ +package org.eclipse.xtext.testing; + +import io.typefox.lsapi.Range; +import io.typefox.lsapi.impl.PositionImpl; +import io.typefox.lsapi.impl.RangeImpl; +import org.eclipse.xtend.lib.annotations.Accessors; +import org.eclipse.xtext.testing.FormattingConfiguration; +import org.eclipse.xtext.xbase.lib.ObjectExtensions; +import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; +import org.eclipse.xtext.xbase.lib.Pure; + +@Accessors +@SuppressWarnings("all") +public class RangeFormattingConfiguration extends FormattingConfiguration { + private Range range = ObjectExtensions.operator_doubleArrow(new RangeImpl(), + ((Procedure1) (RangeImpl it) -> { + PositionImpl _positionImpl = new PositionImpl(0, 0); + it.setStart(_positionImpl); + PositionImpl _positionImpl_1 = new PositionImpl(0, 1); + it.setEnd(_positionImpl_1); + })); + + @Pure + public Range getRange() { + return this.range; + } + + public void setRange(final Range range) { + this.range = range; + } +}