Move cursor after insert required properties quickfix

This commit is contained in:
Kris De Volder
2017-05-01 17:53:29 -07:00
parent 76c7c3c791
commit 4a6b71f1fb
10 changed files with 177 additions and 43 deletions

View File

@@ -11,8 +11,12 @@
package org.springframework.ide.vscode.commons.languageserver;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.services.LanguageClient;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit.CursorMovement;
/**
* Some 'custom' extensions to standard LSP {@link LanguageClient}.
@@ -24,4 +28,7 @@ public interface STS4LanguageClient extends LanguageClient {
@JsonNotification("sts/progress")
void progress(ProgressParams progressEvent);
@JsonRequest("sts/moveCursor")
CompletableFuture<Object> moveCursor(CursorMovement cursorMovement);
}

View File

@@ -0,0 +1,55 @@
/*******************************************************************************
* 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.Position;
import org.eclipse.lsp4j.WorkspaceEdit;
public class QuickfixEdit {
public static class CursorMovement {
private String uri;
private Position position;
public CursorMovement() {
}
public CursorMovement(String uri, Position position) {
this.uri = uri;
this.position = position;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public Position getPosition() {
return position;
}
public void setPosition(Position position) {
this.position = position;
}
}
public final WorkspaceEdit workspaceEdit;
public final CursorMovement cursorMovement;
public QuickfixEdit(WorkspaceEdit workspaceEdit, CursorMovement cursorMovement) {
this.workspaceEdit = workspaceEdit;
this.cursorMovement = cursorMovement;
}
}

View File

@@ -10,9 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.quickfix;
import org.eclipse.lsp4j.WorkspaceEdit;
@FunctionalInterface
public interface QuickfixHandler {
WorkspaceEdit createEdits(Object params);
QuickfixEdit createEdits(Object params);
}

View File

@@ -13,7 +13,6 @@ package org.springframework.ide.vscode.commons.languageserver.quickfix;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.Assert;
@@ -40,7 +39,7 @@ public class QuickfixRegistry {
return new QuickfixType() {
@Override
public WorkspaceEdit createEdits(Object params) {
public QuickfixEdit createEdits(Object params) {
return handler.createEdits(params);
}
@@ -51,7 +50,7 @@ public class QuickfixRegistry {
};
}
public Mono<WorkspaceEdit> handle(QuickfixResolveParams params) {
public Mono<QuickfixEdit> handle(QuickfixResolveParams params) {
QuickfixHandler handler = registry.get(params.getType());
return Mono.fromSupplier(() -> {
return handler.createEdits(params.getParams());

View File

@@ -18,6 +18,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
@@ -31,7 +32,6 @@ import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.LanguageServer;
@@ -40,6 +40,7 @@ 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.QuickfixEdit;
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;
@@ -124,15 +125,20 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl
(String)params.getArguments().get(0), params.getArguments().get(1)
);
return quickfixResolve(quickfixParams)
.then((WorkspaceEdit edit) -> Mono.fromFuture(client.applyEdit(new ApplyWorkspaceEditParams(edit))))
.map(r -> (Object)r.getApplied())
.toFuture();
.then((QuickfixEdit edit) -> {
Mono<ApplyWorkspaceEditResponse> applyEdit = Mono.fromFuture(client.applyEdit(new ApplyWorkspaceEditParams(edit.workspaceEdit)));
Mono<Object> moveCursor = edit.cursorMovement==null
? Mono.just(new ApplyWorkspaceEditResponse(true))
: Mono.fromFuture(client.moveCursor(edit.cursorMovement));
return applyEdit.then(r -> r.getApplied() ? moveCursor : Mono.just(new ApplyWorkspaceEditResponse(true)));
})
.toFuture();
}
Log.warn("Unknown command ignored: "+params.getCommand());
return CompletableFuture.completedFuture(false);
}
public Mono<WorkspaceEdit> quickfixResolve(QuickfixResolveParams params) {
public Mono<QuickfixEdit> quickfixResolve(QuickfixResolveParams params) {
QuickfixRegistry quickfixes = getQuickfixRegistry();
return quickfixes.handle(params);
}

View File

@@ -10,13 +10,16 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.quickfix;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.TextReplace;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixType;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.text.IRegion;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.commons.yaml.completion.YamlPathEdits;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
@@ -32,8 +35,14 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit.CursorMovement;
public class YamlQuickfixes {
private static final QuickfixEdit NULL_FIX = new QuickfixEdit(
new WorkspaceEdit(ImmutableMap.of(), null),
null
);
public final QuickfixType MISSING_PROP_FIX;
public final QuickfixType SIMPLE_TEXT_EDIT;
@@ -53,6 +62,7 @@ public class YamlQuickfixes {
SChildBearingNode target = (SChildBearingNode) _target;
for (String prop : params.getProps()) {
edits.createPath(target, new YamlPath(YamlPathSegment.valueAt(prop)), " ");
edits.freezeCursor();
}
TextReplace replaceEdit = edits.asReplacement(_doc);
if (replaceEdit!=null) {
@@ -61,7 +71,8 @@ public class YamlQuickfixes {
params.getUri(),
ImmutableList.of(new TextEdit(_doc.toRange(replaceEdit.getRegion()), replaceEdit.newText))
));
return wsEdits;
Position newCursor = getCursorPostionAfter(_doc, edits);
return new QuickfixEdit(wsEdits, newCursor==null ? null : new CursorMovement(params.getUri(), newCursor));
}
}
}
@@ -70,7 +81,7 @@ public class YamlQuickfixes {
Log.log(e);
}
//Something went wrong. Return empty edit object.
return new WorkspaceEdit(ImmutableMap.of(), null);
return NULL_FIX;
});
SIMPLE_TEXT_EDIT = r.register("SIMPLE_TEXT_EDIT", (_params) -> {
@@ -78,9 +89,12 @@ public class YamlQuickfixes {
ReplaceStringData params = new ObjectMapper().convertValue(_params, ReplaceStringData.class);
TextDocument _doc = textDocumentService.getDocument(params.getUri());
if (_doc!=null) {
return new WorkspaceEdit(
return new QuickfixEdit(
new WorkspaceEdit(
ImmutableMap.of(params.getUri(), ImmutableList.of(params.getEdit())),
null
),
null //TODO: compute end of the range after applying the edit
);
}
} catch (Exception e) {
@@ -88,8 +102,26 @@ public class YamlQuickfixes {
}
//Something went wrong. Return empty edit object.
//Something went wrong. Return empty edit object.
return new WorkspaceEdit(ImmutableMap.of(), null);
return NULL_FIX;
});
}
private Position getCursorPostionAfter(TextDocument _doc, YamlPathEdits edits) {
try {
IRegion newSelection = edits.getSelection();
if (newSelection!=null) {
//There is probably a more efficient way to compute the new cursor position. But its tricky...
//... because we need to compute line/char coordinate, in terms of lines in the *new* document.
//So we have to take into account how newlines have been inserted or shifted around by the edits.
//Doing that without actually applying the edits is... difficult.
TextDocument doc = _doc.copy();
edits.apply(doc);
return doc.toPosition(newSelection.getOffset());
}
} catch (Exception e) {
Log.log(e);
}
return null;
}
}

View File

@@ -741,4 +741,8 @@ public class Editor {
return harness.getDocumentSymbols(this.doc);
}
public void setCursor(Position position) {
this.selectionStart = this.selectionEnd = doc.toOffset(position);
}
}

View File

@@ -75,6 +75,7 @@ import org.eclipse.lsp4j.services.LanguageClientAware;
import org.springframework.ide.vscode.commons.languageserver.ProgressParams;
import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit.CursorMovement;
import org.springframework.ide.vscode.commons.languageserver.util.LanguageServerTestListener;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.Assert;
@@ -229,6 +230,17 @@ public class LanguageServerHarness {
// TODO Auto-generated method stub
}
@Override
public CompletableFuture<Object> moveCursor(CursorMovement cursorMovement) {
for (Editor editor : activeEditors) {
if (editor.getUri().equals(cursorMovement.getUri())) {
editor.setCursor(cursorMovement.getPosition());
return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(true));
}
}
return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
}
});
}

View File

@@ -58,16 +58,41 @@ public class ConcourseEditorTest {
assertEquals("Add property 'type'", quickfix.getLabel());
quickfix.perform();
editor.assertRawText(
editor.assertText(
"resources:\n" +
"- name: foo\n" +
" source:\n" +
" username: someone\n" +
" type: \n" +
" type: <*>\n" +
"# Confuse"
);
}
@Test public void addMultipleRequiredPropertiesQuickfix() throws Exception {
Editor editor = harness.newEditor(
"resources:\n" +
"- name: foo\n" +
" type: pool\n" +
" source:\n" +
" username: someone\n"
);
Diagnostic problem = editor.assertProblems("source|[branch, pool, uri] are required").get(0);
CodeAction quickfix = editor.assertCodeAction(problem);
assertEquals("Add properties: [branch, pool, uri]", quickfix.getLabel());
quickfix.perform();
editor.assertText(
"resources:\n" +
"- name: foo\n" +
" type: pool\n" +
" source:\n" +
" username: someone\n" +
" branch: <*>\n" +
" pool: \n" +
" uri: \n"
);
}
@Test public void reconcileResourceTypeType() throws Exception {
Editor editor;
editor = harness.newEditor(
@@ -89,31 +114,6 @@ public class ConcourseEditorTest {
);
}
@Test public void addMultipleRequiredPropertiesQuickfix() throws Exception {
Editor editor = harness.newEditor(
"resources:\n" +
"- name: foo\n" +
" type: pool\n" +
" source:\n" +
" username: someone\n"
);
Diagnostic problem = editor.assertProblems("source|[branch, pool, uri] are required").get(0);
CodeAction quickfix = editor.assertCodeAction(problem);
assertEquals("Add properties: [branch, pool, uri]", quickfix.getLabel());
quickfix.perform();
editor.assertRawText(
"resources:\n" +
"- name: foo\n" +
" type: pool\n" +
" source:\n" +
" username: someone\n" +
" branch: \n" +
" pool: \n" +
" uri: \n"
);
}
@Test public void testReconcileCatchesParseError() throws Exception {
Editor editor = harness.newEditor(
"somemap: val\n"+

View File

@@ -14,7 +14,7 @@ import { RequestType, LanguageClient, LanguageClientOptions, SettingMonitor, Ser
import { TextDocument, OutputChannel, Disposable, window } from 'vscode';
import { Trace, NotificationType } from 'vscode-jsonrpc';
import * as P2C from 'vscode-languageclient/lib/protocolConverter';
import {WorkspaceEdit} from 'vscode-languageserver-types';
import {WorkspaceEdit, Position} from 'vscode-languageserver-types';
let p2c = P2C.createConverter();
@@ -145,6 +145,7 @@ function setupLanguageClient(context: VSCode.ExtensionContext, createServer: Ser
}
let progressNotification = new NotificationType<ProgressParams,void>("sts/progress");
let moveCursorRequest = new RequestType<MoveCursorParams,MoveCursorResponse,void,void>("sts/moveCursor");
let disposable = client.start();
@@ -155,6 +156,17 @@ function setupLanguageClient(context: VSCode.ExtensionContext, createServer: Ser
client.onNotification(progressNotification, (params: ProgressParams) => {
progressService.handle(params);
});
client.onRequest(moveCursorRequest, (params: MoveCursorParams) => {
let editors = VSCode.window.visibleTextEditors;
for (let editor of editors) {
if (editor.document.uri.toString() == params.uri) {
let cursor = p2c.asPosition(params.position);
let selection : VSCode.Selection = new VSCode.Selection(cursor, cursor);
editor.selections = [ selection ];
}
}
return { applied: true};
});
return client;
});
}
@@ -204,6 +216,15 @@ function correctBinname(binname: string) {
return binname;
}
interface MoveCursorParams {
uri: string
position: Position
}
interface MoveCursorResponse {
applied: boolean
}
interface ProgressParams {
id: string
statusMsg?: string