/******************************************************************************* * Copyright (c) 2017 Red Hat. * 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: * Red Hat - Initial Contribution *******************************************************************************/ package org.eclipse.che.plugin.languageserver.ide.editor.quickassist; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Widget; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.ide.api.action.Action; import org.eclipse.che.ide.api.action.ActionManager; import org.eclipse.che.ide.api.action.Presentation; import org.eclipse.che.ide.api.editor.annotation.QueryAnnotationsEvent; import org.eclipse.che.ide.api.editor.codeassist.CodeAssistCallback; import org.eclipse.che.ide.api.editor.codeassist.Completion; import org.eclipse.che.ide.api.editor.codeassist.CompletionProposal; import org.eclipse.che.ide.api.editor.document.Document; import org.eclipse.che.ide.api.editor.quickfix.QuickAssistInvocationContext; import org.eclipse.che.ide.api.editor.quickfix.QuickAssistProcessor; import org.eclipse.che.ide.api.editor.text.LinearRange; import org.eclipse.che.ide.api.editor.text.TextPosition; import org.eclipse.che.ide.api.editor.text.annotation.Annotation; import org.eclipse.che.ide.api.icon.Icon; import org.eclipse.che.ide.api.parts.PerspectiveManager; import org.eclipse.che.plugin.languageserver.ide.editor.DiagnosticAnnotation; import org.eclipse.che.plugin.languageserver.ide.service.TextDocumentServiceClient; import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.CodeActionParams; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentIdentifier; import javax.inject.Inject; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; /** * A {@link QuickAssistProcessor} that implements LSP code actions as quick * assists. * * @author Thomas Mäder * */ public class LanguageServerQuickAssistProcessor implements QuickAssistProcessor { private TextDocumentServiceClient textDocumentService; private ActionManager actionManager; private PerspectiveManager perspectiveManager; private final class ActionCompletionProposal implements CompletionProposal { private final Command command; private final Action action; private ActionCompletionProposal(Command command, Action action) { this.command = command; this.action = action; } @Override public void getAdditionalProposalInfo(AsyncCallback<Widget> callback) { } @Override public String getDisplayString() { return command.getTitle(); } @Override public Icon getIcon() { return null; } @Override public void getCompletion(CompletionCallback callback) { callback.onCompletion(new Completion() { @Override public LinearRange getSelection(Document document) { return null; } @Override public void apply(Document document) { QuickassistActionEvent evt = new QuickassistActionEvent(new Presentation(), actionManager, perspectiveManager, command.getArguments()); action.actionPerformed(evt); } }); } } @Inject public LanguageServerQuickAssistProcessor(TextDocumentServiceClient textDocumentService, ActionManager actionManager, PerspectiveManager perspectiveManager) { this.textDocumentService = textDocumentService; this.actionManager = actionManager; this.perspectiveManager = perspectiveManager; } @Override public void computeQuickAssistProposals(QuickAssistInvocationContext invocationContext, CodeAssistCallback callback) { LinearRange range = invocationContext.getTextEditor().getSelectedLinearRange(); Document document = invocationContext.getTextEditor().getDocument(); QueryAnnotationsEvent.QueryCallback annotationCallback = new QueryAnnotationsEvent.QueryCallback() { @Override public void respond(Map<Annotation, org.eclipse.che.ide.api.editor.text.Position> annotations) { List<Diagnostic> diagnostics = new ArrayList<>(); // iteration with range never returns anything; need to filter ourselves. // https://github.com/eclipse/che/issues/4338 annotations.entrySet().stream().filter((e) -> e.getValue().overlapsWith(range.getStartOffset(), range.getLength())) .map(Entry::getKey).map(a -> (DiagnosticAnnotation) a).map(DiagnosticAnnotation::getDiagnostic) .collect(Collectors.toList()); CodeActionContext context = new CodeActionContext(diagnostics); TextPosition start = document.getPositionFromIndex(range.getStartOffset()); TextPosition end = document.getPositionFromIndex(range.getStartOffset() + range.getLength()); Position rangeStart = new Position(start.getLine(), start.getCharacter()); Position rangeEnd = new Position(end.getLine(), end.getCharacter()); Range rangeParam = new Range(rangeStart, rangeEnd); rangeParam.setEnd(rangeEnd); TextDocumentIdentifier textDocumentIdentifier = new TextDocumentIdentifier(document.getFile().getLocation().toString()); CodeActionParams params = new CodeActionParams(textDocumentIdentifier, rangeParam, context); Promise<List<Command>> codeAction = textDocumentService.codeAction(params); List<CompletionProposal> proposals = new ArrayList<>(); codeAction.then((commands) -> { for (Command command : commands) { Action action = actionManager.getAction(command.getCommand()); if (action != null) { proposals.add(new ActionCompletionProposal(command, action)); } } ; callback.proposalComputed(proposals); }); } }; QueryAnnotationsEvent event = new QueryAnnotationsEvent.Builder().withFilter(a -> a instanceof DiagnosticAnnotation) .withCallback(annotationCallback).build(); document.getDocumentHandle().getDocEventBus().fireEvent(event); } }