Move cursor after insert required properties quickfix
This commit is contained in:
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -741,4 +741,8 @@ public class Editor {
|
||||
return harness.getDocumentSymbols(this.doc);
|
||||
}
|
||||
|
||||
public void setCursor(Position position) {
|
||||
this.selectionStart = this.selectionEnd = doc.toOffset(position);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -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"+
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user