/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.plugin.languageserver.ide.editor; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.ide.api.editor.document.Document; import org.eclipse.che.ide.api.editor.events.DocumentChangeEvent; import org.eclipse.che.ide.api.editor.events.DocumentChangeHandler; import org.eclipse.che.ide.api.editor.formatter.ContentFormatter; import org.eclipse.che.ide.api.editor.text.TextPosition; import org.eclipse.che.ide.api.editor.text.TextRange; import org.eclipse.che.ide.api.editor.texteditor.HandlesUndoRedo; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import org.eclipse.che.ide.api.editor.texteditor.UndoableEditor; import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.dto.DtoFactory; import org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties; import org.eclipse.che.ide.editor.preferences.editorproperties.EditorPropertiesManager; import org.eclipse.che.ide.util.loging.Log; import org.eclipse.che.plugin.languageserver.ide.service.TextDocumentServiceClient; import org.eclipse.lsp4j.DocumentFormattingParams; import org.eclipse.lsp4j.DocumentOnTypeFormattingParams; import org.eclipse.lsp4j.DocumentRangeFormattingParams; import org.eclipse.lsp4j.FormattingOptions; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextEdit; import java.util.Collections; import java.util.List; /** * @author Evgen Vidolob */ public class LanguageServerFormatter implements ContentFormatter { private final TextDocumentServiceClient client; private final DtoFactory dtoFactory; private final NotificationManager manager; private final ServerCapabilities capabilities; private final EditorPropertiesManager editorPropertiesManager; private TextEditor editor; @Inject public LanguageServerFormatter(TextDocumentServiceClient client, DtoFactory dtoFactory, NotificationManager manager, @Assisted ServerCapabilities capabilities, EditorPropertiesManager editorPropertiesManager) { this.client = client; this.dtoFactory = dtoFactory; this.manager = manager; this.capabilities = capabilities; this.editorPropertiesManager = editorPropertiesManager; } @Override public void format(Document document) { TextRange selectedRange = document.getSelectedTextRange(); if (selectedRange != null && !selectedRange.getFrom().equals(selectedRange.getTo()) && capabilities.getDocumentRangeFormattingProvider()) { //selection formatting formatRange(selectedRange, document); } else if (capabilities.getDocumentFormattingProvider()) { //full document formatting formatFullDocument(document); } } @Override public void install(TextEditor editor) { this.editor = editor; if (capabilities.getDocumentOnTypeFormattingProvider() != null && capabilities.getDocumentOnTypeFormattingProvider().getFirstTriggerCharacter() != null) { editor.getDocument().getDocumentHandle().getDocEventBus().addHandler(DocumentChangeEvent.TYPE, new DocumentChangeHandler() { @Override public void onDocumentChange(DocumentChangeEvent event) { if (capabilities.getDocumentOnTypeFormattingProvider().getFirstTriggerCharacter().equals(event.getText())) { Document document = event.getDocument().getDocument(); DocumentOnTypeFormattingParams params = dtoFactory.createDto(DocumentOnTypeFormattingParams.class); TextDocumentIdentifier identifier = dtoFactory.createDto(TextDocumentIdentifier.class); identifier.setUri(document.getFile().getLocation().toString()); params.setTextDocument(identifier); params.setOptions(getFormattingOptions()); params.setCh(event.getText()); TextPosition position = document.getPositionFromIndex(event.getOffset()); Position start = dtoFactory.createDto(Position.class); start.setLine(position.getLine()); start.setCharacter(position.getCharacter()); params.setPosition(start); Promise<List<TextEdit>> promise = client.onTypeFormatting(params); handleFormatting(promise, document); } } }); } } private void formatFullDocument(Document document) { DocumentFormattingParams params = dtoFactory.createDto(DocumentFormattingParams.class); TextDocumentIdentifier identifier = dtoFactory.createDto(TextDocumentIdentifier.class); identifier.setUri(document.getFile().getLocation().toString()); params.setTextDocument(identifier); params.setOptions(getFormattingOptions()); Promise<List<TextEdit>> promise = client.formatting(params); handleFormatting(promise, document); } private void handleFormatting(Promise<List<TextEdit>> promise, final Document document) { promise.then(new Operation<List<TextEdit>>() { @Override public void apply(List<TextEdit> arg) throws OperationException { applyEdits(arg, document); } }).catchError(new Operation<PromiseError>() { @Override public void apply(PromiseError arg) throws OperationException { manager.notify(arg.getMessage()); } }); } private void applyEdits(List<TextEdit> edits, Document document) { HandlesUndoRedo undoRedo = null; if (editor instanceof UndoableEditor) { undoRedo = ((UndoableEditor)editor).getUndoRedo(); } try { if (undoRedo != null) { undoRedo.beginCompoundChange(); } // #2437: apply the text edits from last to first to avoid messing up the document Collections.reverse(edits); for (TextEdit change : edits) { Range range = change.getRange(); document.replace(range.getStart().getLine(), range.getStart().getCharacter(), range.getEnd().getLine(), range.getEnd().getCharacter(), change.getNewText()); } } catch (final Exception e) { Log.error(getClass(), e); } finally { if (undoRedo != null) { undoRedo.endCompoundChange(); } } } private FormattingOptions getFormattingOptions() { FormattingOptions options = dtoFactory.createDto(FormattingOptions.class); options.setInsertSpaces(Boolean.parseBoolean(getEditorProperty(EditorProperties.EXPAND_TAB))); options.setTabSize(Integer.parseInt(getEditorProperty(EditorProperties.TAB_SIZE))); return options; } private String getEditorProperty(EditorProperties property) { return editorPropertiesManager.getEditorProperties().get(property.toString()).toString(); } private void formatRange(TextRange selectedRange, Document document) { DocumentRangeFormattingParams params = dtoFactory.createDto(DocumentRangeFormattingParams.class); TextDocumentIdentifier identifier = dtoFactory.createDto(TextDocumentIdentifier.class); identifier.setUri(document.getFile().getLocation().toString()); params.setTextDocument(identifier); params.setOptions(getFormattingOptions()); Range range = dtoFactory.createDto(Range.class); Position start = dtoFactory.createDto(Position.class); Position end = dtoFactory.createDto(Position.class); start.setLine(selectedRange.getFrom().getLine()); start.setCharacter(selectedRange.getFrom().getCharacter()); end.setLine(selectedRange.getTo().getLine()); end.setCharacter(selectedRange.getTo().getCharacter()); range.setStart(start); range.setEnd(end); params.setRange(range); Promise<List<TextEdit>> promise = client.rangeFormatting(params); handleFormatting(promise, document); } }