Add quickfix for missing properties in concourse editor

This commit is contained in:
Kris De Volder
2017-04-11 17:08:03 -07:00
parent 7e2f6a6c9c
commit aee00fb487
40 changed files with 721 additions and 165 deletions

View File

@@ -1,37 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* 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:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.quickfix;
import java.util.List;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
/**
* Represents a strategy for computing potential quickfixes for a given problem.
*
* @author Kris De Volder
*/
@FunctionalInterface
public interface ProblemFixer {
/**
* Implementor can inspect the problem and quickfix context provided as parameters.
* <p>
* If the problem is deemed fixable, the strategy can contribute one or more fixes by
* adding them to the list of proposals (provided as third parameter).
*/
void contributeFixes(
QuickfixContext context, ReconcileProblem problem,
List<ICompletionProposal> proposals
);
}

View File

@@ -0,0 +1,57 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* 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:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.quickfix;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.Range;
import com.google.common.collect.ImmutableList;
public class Quickfix<T> {
public static class QuickfixData<T> {
public final QuickfixType type;
public final T params;
public final String title;
public QuickfixData(QuickfixType type, T params, String title) {
super();
this.type = type;
this.params = params;
this.title = title;
}
}
private final Range range;
private final QuickfixData<T> data;
public Quickfix(Range range, QuickfixData<T> data) {
super();
this.range = range;
this.data = data;
}
public Range getRange() {
return range;
}
public Command getCodeAction() {
return new Command(
data.title,
"sts.quickfix",
ImmutableList.of(data.type.getId(), data.params)
);
}
public boolean appliesTo(Range range, CodeActionContext context) {
return range.equals(this.range);
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* Copyright (c) 2017 Pivotal, Inc.
* 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
@@ -10,19 +10,9 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.quickfix;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.eclipse.lsp4j.WorkspaceEdit;
/**
* Provides access to additional context info and objects that quickfixes might
* need in order to be able to apply themselves.
*
* @author Kris De Volder
*/
public interface QuickfixContext {
// IProject getProject();
// IPreferenceStore getWorkspacePreferences();
// IPreferenceStore getProjectPreferences();
// IJavaProject getJavaProject();
// UserInteractions getUI();
IDocument getDocument();
@FunctionalInterface
public interface QuickfixHandler {
WorkspaceEdit createEdits(Object params);
}

View File

@@ -0,0 +1,63 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* 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:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.quickfix;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.Assert;
import reactor.core.publisher.Mono;
/**
* Registry keeping track of quickfix types for a {@link SimpleLanguageServer}.
* <p>
* Each type must be associated with a handler. The handler accepts a parameter object
* and computes workspace edits to be applied when the quickfix is executed.
* <p>
* The parameters object must be convertible to json since it is sent over the
* wire to the client and later sent back when the quickfix is selected.
*
* @author Kris De Volder
*/
public class QuickfixRegistry {
private Map<String, QuickfixHandler> registry = new HashMap<>();
public synchronized QuickfixType register(String typeName, QuickfixHandler handler) {
Assert.isLegal(!registry.containsKey(typeName), "Quickfix type already registered: '"+typeName);
registry.put(typeName, handler);
return new QuickfixType() {
@Override
public WorkspaceEdit createEdits(Object params) {
return handler.createEdits(params);
}
@Override
public String getId() {
return typeName;
}
};
}
public CompletableFuture<WorkspaceEdit> handle(QuickfixResolveParams params) {
QuickfixHandler handler = registry.get(params.getType());
return Mono.fromSupplier(() -> {
return handler.createEdits(params.getParams());
}).toFuture();
}
}

View File

@@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* 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:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.quickfix;
public class QuickfixResolveParams {
private String type;
private Object params;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Object getParams() {
return params;
}
public void setParams(Object params) {
this.params = params;
}
@Override
public String toString() {
return "QuickfixResolveParams [type=" + type + ", params=" + params + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((params == null) ? 0 : params.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
QuickfixResolveParams other = (QuickfixResolveParams) obj;
if (params == null) {
if (other.params != null)
return false;
} else if (!params.equals(other.params))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
return true;
}
}

View File

@@ -0,0 +1,15 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* 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:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.quickfix;
public interface QuickfixType extends QuickfixHandler {
String getId();
}

View File

@@ -11,6 +11,10 @@
package org.springframework.ide.vscode.commons.languageserver.reconcile;
import java.util.List;
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
/**
* Minamal interface that objects representing a reconciler problem must
* implement.
@@ -23,4 +27,5 @@ public interface ReconcileProblem {
int getOffset();
int getLength();
String getCode();
List<QuickfixData<?>> getQuickfixes();
}

View File

@@ -11,6 +11,10 @@
package org.springframework.ide.vscode.commons.languageserver.reconcile;
import java.util.ArrayList;
import java.util.List;
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
/**
@@ -24,6 +28,7 @@ public class ReconcileProblemImpl implements ReconcileProblem {
final private String msg;
final private int offset;
final private int len;
private List<QuickfixData<?>> fixes = new ArrayList<>();
public ReconcileProblemImpl(ProblemType type, String msg, int offset, int len) {
super();
@@ -78,4 +83,14 @@ public class ReconcileProblemImpl implements ReconcileProblem {
return c!='\n'&&c!='\r';
}
@Override
public List<QuickfixData<?>> getQuickfixes() {
return fixes;
}
public ReconcileProblemImpl addQuickfix(QuickfixData<?> command) {
fixes.add(command);
return this;
}
}

View File

@@ -8,7 +8,6 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.util;
import java.nio.file.Path;
@@ -25,18 +24,26 @@ import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.LanguageServer;
import org.springframework.ide.vscode.commons.languageserver.ProgressParams;
import org.springframework.ide.vscode.commons.languageserver.ProgressService;
import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient;
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix;
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixResolveParams;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import reactor.core.publisher.Mono;
@@ -72,11 +79,20 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl
private CompletableFuture<Void> busyReconcile = CompletableFuture.completedFuture(null);
private QuickfixRegistry quickfixRegistry;
@Override
public void connect(LanguageClient _client) {
this.client = (STS4LanguageClient) _client;
}
protected synchronized QuickfixRegistry getQuickfixRegistry() {
if (quickfixRegistry==null) {
quickfixRegistry = new QuickfixRegistry();
}
return quickfixRegistry;
}
@Override
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
// LOG.info("Initializing");
@@ -166,9 +182,11 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl
IProblemCollector problems = new IProblemCollector() {
private List<Diagnostic> diagnostics = new ArrayList<>();
private List<Quickfix> quickfixes = new ArrayList<>();
@Override
public void endCollecting() {
documents.setQuickfixes(doc, quickfixes);
documents.publishDiagnostics(doc, diagnostics);
}
@@ -185,8 +203,15 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl
Diagnostic d = new Diagnostic();
d.setCode(problem.getCode());
d.setMessage(problem.getMessage());
d.setRange(doc.toRange(problem.getOffset(), problem.getLength()));
Range rng = doc.toRange(problem.getOffset(), problem.getLength());
d.setRange(rng);
d.setSeverity(severity);
List<QuickfixData<?>> fixes = problem.getQuickfixes();
if (CollectionUtil.hasElements(fixes)) {
for (QuickfixData<?> fix : fixes) {
quickfixes.add(new Quickfix<>(rng, fix));
}
}
diagnostics.add(d);
}
} catch (BadLocationException e) {
@@ -235,4 +260,11 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl
return progressService;
}
@JsonRequest("sts/quickfix")
public CompletableFuture<WorkspaceEdit> quickfixResolve(QuickfixResolveParams params) {
QuickfixRegistry quickfixes = getQuickfixRegistry();
return quickfixes.handle(params);
}
}

View File

@@ -11,7 +11,6 @@
package org.springframework.ide.vscode.commons.languageserver.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -21,6 +20,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.CodeLens;
@@ -55,6 +55,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.springframework.ide.vscode.commons.languageserver.LanguageIds;
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
@@ -62,12 +63,14 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
public class SimpleTextDocumentService implements TextDocumentService {
private static final Logger LOG = Logger.getLogger(SimpleTextDocumentService.class.getName());
final private SimpleLanguageServer server;
private Map<String, TextDocument> documents = new HashMap<>();
private Map<String, TrackedDocument> documents = new HashMap<>();
private ListenerList<TextDocumentContentChange> documentChangeListeners = new ListenerList<>();
private CompletionHandler completionHandler = null;
private CompletionResolveHandler completionResolveHandler = null;
@@ -100,7 +103,7 @@ public class SimpleTextDocumentService implements TextDocumentService {
this.definitionHandler = h;
}
public synchronized void onRefeences(ReferencesHandler h) {
public synchronized void onReferences(ReferencesHandler h) {
Assert.isNull("A references handler is already set, multiple handlers not supported yet", referencesHandler);
this.referencesHandler = h;
}
@@ -110,7 +113,9 @@ public class SimpleTextDocumentService implements TextDocumentService {
* and not yet closed.
*/
public synchronized Collection<TextDocument> getAll() {
return new ArrayList<>(documents.values());
return documents.values().stream()
.map((td) -> td.getDocument())
.collect(Collectors.toList());
}
@Override
@@ -151,7 +156,7 @@ public class SimpleTextDocumentService implements TextDocumentService {
String languageId = params.getTextDocument().getLanguageId();
if (url!=null) {
String text = params.getTextDocument().getText();
TextDocument doc = createDocument(url, languageId);
TextDocument doc = createDocument(url, languageId).getDocument();
doc.setText(text);
TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent() {
@Override
@@ -191,20 +196,20 @@ public class SimpleTextDocumentService implements TextDocumentService {
documentChangeListeners.add(l);
}
private synchronized TextDocument getDocument(String url) {
TextDocument doc = documents.get(url);
public synchronized TextDocument getDocument(String url) {
TrackedDocument doc = documents.get(url);
if (doc==null) {
LOG.warning("Trying to get document ["+url+"] but it did not exists. Creating it with language-id 'plaintext'");
doc = createDocument(url, LanguageIds.PLAINTEXT);
}
return doc;
return doc.getDocument();
}
private synchronized TextDocument createDocument(String url, String languageId) {
private synchronized TrackedDocument createDocument(String url, String languageId) {
if (documents.get(url)!=null) {
LOG.warning("Creating document ["+url+"] but it already exists. Existing document discarded!");
}
TextDocument doc = new TextDocument(url, languageId);
TrackedDocument doc = new TrackedDocument(new TextDocument(url, languageId));
documents.put(url, doc);
return doc;
}
@@ -273,7 +278,17 @@ public class SimpleTextDocumentService implements TextDocumentService {
@Override
public CompletableFuture<List<? extends Command>> codeAction(CodeActionParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
TrackedDocument doc = documents.get(params.getTextDocument().getUri());
if (doc!=null) {
return Flux.fromIterable(doc.getQuickfixes())
.filter((fix) -> fix.appliesTo(params.getRange(), params.getContext()))
.map(Quickfix::getCodeAction)
.collectList()
.toFuture()
.thenApply(l -> (List<? extends Command>) l);
} else {
return CompletableFuture.completedFuture(ImmutableList.of());
}
}
@Override
@@ -320,12 +335,21 @@ public class SimpleTextDocumentService implements TextDocumentService {
}
}
public void setQuickfixes(TextDocument doc, List<Quickfix> quickfixes) {
TrackedDocument td = documents.get(doc.getUri());
if (td!=null) {
td.setQuickfixes(quickfixes);
}
}
public synchronized TextDocument get(TextDocumentPositionParams params) {
return documents.get(params.getTextDocument().getUri());
TrackedDocument td = documents.get(params.getTextDocument().getUri());
return td == null ? null : td.getDocument();
}
@Override
public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(TextDocumentPositionParams position) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
}

View File

@@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* 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:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.util;
import java.util.List;
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
public class TrackedDocument {
private final TextDocument doc;
private List<Quickfix> quickfixes = ImmutableList.of();
public TrackedDocument(TextDocument doc) {
this.doc = doc;
}
public TextDocument getDocument() {
return doc;
}
public void setQuickfixes(List<Quickfix> quickfixes) {
this.quickfixes = quickfixes;
}
public List<Quickfix> getQuickfixes() {
return quickfixes;
}
}