Skip to content

Commit 7faecba

Browse files
tsmaedervparfonov
authored andcommitted
5485 apply workspace edit (eclipse-che#5955)
* Add ApplyWorkspaceEditAction Signed-off-by: Thomas Mäder <tmader@redhat.com> * Fix copyright statement Signed-off-by: Thomas Mäder <tmader@redhat.com>
1 parent c80b423 commit 7faecba

22 files changed

Lines changed: 1028 additions & 96 deletions

File tree

plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerExtension.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.eclipse.che.ide.util.input.KeyCodeMap;
3030
import org.eclipse.che.plugin.languageserver.ide.editor.LanguageServerEditorConfiguration;
3131
import org.eclipse.che.plugin.languageserver.ide.editor.quickassist.ApplyTextEditAction;
32+
import org.eclipse.che.plugin.languageserver.ide.editor.quickassist.ApplyWorkspaceEditAction;
3233
import org.eclipse.che.plugin.languageserver.ide.navigation.declaration.FindDefinitionAction;
3334
import org.eclipse.che.plugin.languageserver.ide.navigation.references.FindReferencesAction;
3435
import org.eclipse.che.plugin.languageserver.ide.navigation.symbol.GoToSymbolAction;
@@ -61,12 +62,14 @@ protected void registerAction(
6162
FindSymbolAction findSymbolAction,
6263
FindDefinitionAction findDefinitionAction,
6364
FindReferencesAction findReferencesAction,
64-
ApplyTextEditAction applyTextEditAction) {
65+
ApplyTextEditAction applyTextEditAction,
66+
ApplyWorkspaceEditAction applyWorkspaceEditAction) {
6567
actionManager.registerAction("LSGoToSymbolAction", goToSymbolAction);
6668
actionManager.registerAction("LSFindSymbolAction", findSymbolAction);
6769
actionManager.registerAction("LSFindDefinitionAction", findDefinitionAction);
6870
actionManager.registerAction("LSFindReferencesAction", findReferencesAction);
6971
actionManager.registerAction("lsp.applyTextEdit", applyTextEditAction);
72+
actionManager.registerAction("lsp.applyWorkspaceEdit", applyWorkspaceEditAction);
7073

7174
DefaultActionGroup assistantGroup =
7275
(DefaultActionGroup) actionManager.getAction(GROUP_ASSISTANT);

plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerLocalization.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,24 @@ public interface LanguageServerLocalization extends Messages {
2424
@Key("go.to.symbol.symbols")
2525
String goToSymbolSymbols(int num);
2626

27+
@Key("apply.workspace.edit.action.notification.title")
28+
String applyWorkspaceActionNotificationTitle();
29+
30+
@Key("apply.workspace.edit.action.notification.done")
31+
String applyWorkspaceActionNotificationDone();
32+
33+
@Key("apply.workspace.edit.action.notification.undoing")
34+
String applyWorkspaceActionNotificationUndoing();
35+
36+
@Key("apply.workspace.edit.action.notification.undone")
37+
String applyWorkspaceActionNotificationUndone();
38+
39+
@Key("apply.workspace.edit.action.notification.undo.failed")
40+
String applyWorkspaceActionNotificationUndoFailed();
41+
42+
@Key("apply.workspace.edit.action.notification.modifying")
43+
String applyWorkspaceActionNotificationModifying(String uri);
44+
2745
@Key("modules.type")
2846
String modulesType(int p0);
2947

plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/quickassist/ApplyTextEditAction.java

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@
1010
*/
1111
package org.eclipse.che.plugin.languageserver.ide.editor.quickassist;
1212

13-
import com.google.gwt.json.client.JSONValue;
1413
import com.google.inject.Inject;
1514
import com.google.inject.Singleton;
15+
import java.util.Comparator;
1616
import java.util.List;
17+
import org.eclipse.che.api.languageserver.shared.util.RangeComparator;
1718
import org.eclipse.che.ide.api.action.Action;
1819
import org.eclipse.che.ide.api.action.ActionEvent;
1920
import org.eclipse.che.ide.api.editor.EditorAgent;
2021
import org.eclipse.che.ide.api.editor.EditorPartPresenter;
2122
import org.eclipse.che.ide.api.editor.document.Document;
2223
import org.eclipse.che.ide.api.editor.texteditor.TextEditor;
2324
import org.eclipse.che.ide.dto.DtoFactory;
24-
import org.eclipse.che.plugin.languageserver.ide.util.DtoBuildHelper;
2525
import org.eclipse.lsp4j.Position;
2626
import org.eclipse.lsp4j.Range;
2727
import org.eclipse.lsp4j.TextEdit;
@@ -33,39 +33,44 @@
3333
*/
3434
@Singleton
3535
public class ApplyTextEditAction extends Action {
36+
private static final Comparator<TextEdit> COMPARATOR =
37+
RangeComparator.transform(new RangeComparator().reversed(), TextEdit::getRange);
38+
3639
private EditorAgent editorAgent;
3740
private DtoFactory dtoFactory;
3841

3942
@Inject
40-
public ApplyTextEditAction(
41-
EditorAgent editorAgent, DtoFactory dtoFactory, DtoBuildHelper dtoBuildHelper) {
43+
public ApplyTextEditAction(EditorAgent editorAgent, DtoFactory dtoFactory) {
4244
super("Apply Text Edit");
4345
this.editorAgent = editorAgent;
4446
this.dtoFactory = dtoFactory;
4547
}
4648

4749
@Override
48-
public void actionPerformed(ActionEvent e) {
50+
public void actionPerformed(ActionEvent evt) {
4951
EditorPartPresenter activeEditor = editorAgent.getActiveEditor();
5052
if (!(activeEditor instanceof TextEditor)) {
5153
return;
5254
}
5355
Document document = ((TextEditor) activeEditor).getDocument();
54-
// We expect the arguments to be of the correct type: static misconfiguration is a programming error.
55-
List<Object> arguments = ((QuickassistActionEvent) e).getArguments();
56-
for (Object arg : arguments) {
57-
if ((arg instanceof JSONValue)) {
58-
TextEdit edit = dtoFactory.createDtoFromJson(arg.toString(), TextEdit.class);
59-
Range range = edit.getRange();
60-
Position start = range.getStart();
61-
Position end = range.getEnd();
62-
document.replace(
63-
start.getLine(),
64-
start.getCharacter(),
65-
end.getLine(),
66-
end.getCharacter(),
67-
edit.getNewText());
68-
}
69-
}
56+
// We expect the arguments to be of the correct type: static misconfiguration is
57+
// a programming error.
58+
List<Object> arguments = ((QuickassistActionEvent) evt).getArguments();
59+
arguments
60+
.stream()
61+
.map(arg -> dtoFactory.createDtoFromJson(arg.toString(), TextEdit.class))
62+
.sorted(COMPARATOR)
63+
.forEach(
64+
e -> {
65+
Range r = e.getRange();
66+
Position start = r.getStart();
67+
Position end = r.getEnd();
68+
document.replace(
69+
start.getLine(),
70+
start.getCharacter(),
71+
end.getLine(),
72+
end.getCharacter(),
73+
e.getNewText());
74+
});
7075
}
7176
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Copyright (c) 2012-2017 Red Hat, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
*/
11+
package org.eclipse.che.plugin.languageserver.ide.editor.quickassist;
12+
13+
import com.google.inject.Inject;
14+
import java.util.ArrayList;
15+
import java.util.Comparator;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.function.Supplier;
19+
import java.util.stream.Collectors;
20+
import org.eclipse.che.api.languageserver.shared.dto.DtoClientImpls.FileEditParamsDto;
21+
import org.eclipse.che.api.languageserver.shared.model.FileEditParams;
22+
import org.eclipse.che.api.languageserver.shared.util.RangeComparator;
23+
import org.eclipse.che.api.promises.client.Function;
24+
import org.eclipse.che.api.promises.client.Promise;
25+
import org.eclipse.che.api.promises.client.PromiseError;
26+
import org.eclipse.che.api.promises.client.PromiseProvider;
27+
import org.eclipse.che.api.promises.client.js.Executor;
28+
import org.eclipse.che.api.promises.client.js.RejectFunction;
29+
import org.eclipse.che.api.promises.client.js.ResolveFunction;
30+
import org.eclipse.che.ide.api.action.Action;
31+
import org.eclipse.che.ide.api.action.ActionEvent;
32+
import org.eclipse.che.ide.api.editor.EditorAgent;
33+
import org.eclipse.che.ide.api.editor.EditorPartPresenter;
34+
import org.eclipse.che.ide.api.editor.document.Document;
35+
import org.eclipse.che.ide.api.editor.texteditor.HandlesUndoRedo;
36+
import org.eclipse.che.ide.api.editor.texteditor.TextEditor;
37+
import org.eclipse.che.ide.api.notification.Notification;
38+
import org.eclipse.che.ide.api.notification.NotificationManager;
39+
import org.eclipse.che.ide.api.notification.StatusNotification;
40+
import org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode;
41+
import org.eclipse.che.ide.api.notification.StatusNotification.Status;
42+
import org.eclipse.che.ide.dto.DtoFactory;
43+
import org.eclipse.che.ide.util.loging.Log;
44+
import org.eclipse.che.plugin.languageserver.ide.LanguageServerLocalization;
45+
import org.eclipse.che.plugin.languageserver.ide.service.WorkspaceServiceClient;
46+
import org.eclipse.che.plugin.languageserver.ide.util.PromiseHelper;
47+
import org.eclipse.lsp4j.Position;
48+
import org.eclipse.lsp4j.Range;
49+
import org.eclipse.lsp4j.TextDocumentEdit;
50+
import org.eclipse.lsp4j.TextEdit;
51+
import org.eclipse.lsp4j.WorkspaceEdit;
52+
53+
public class ApplyWorkspaceEditAction extends Action {
54+
private static final Comparator<TextEdit> COMPARATOR =
55+
RangeComparator.transform(new RangeComparator().reversed(), TextEdit::getRange);
56+
57+
private EditorAgent editorAgent;
58+
private DtoFactory dtoFactory;
59+
private WorkspaceServiceClient workspaceService;
60+
private PromiseHelper promiseHelper;
61+
private LanguageServerLocalization localization;
62+
private NotificationManager notificationManager;
63+
private PromiseProvider promiseProvider;
64+
65+
@Inject
66+
public ApplyWorkspaceEditAction(
67+
EditorAgent editorAgent,
68+
DtoFactory dtoFactory,
69+
WorkspaceServiceClient workspaceService,
70+
PromiseHelper promiseHelper,
71+
LanguageServerLocalization localization,
72+
NotificationManager notificationManager,
73+
PromiseProvider promiseProvider) {
74+
this.editorAgent = editorAgent;
75+
this.dtoFactory = dtoFactory;
76+
this.workspaceService = workspaceService;
77+
this.promiseHelper = promiseHelper;
78+
this.localization = localization;
79+
this.notificationManager = notificationManager;
80+
this.promiseProvider = promiseProvider;
81+
}
82+
83+
@Override
84+
public void actionPerformed(ActionEvent evt) {
85+
QuickassistActionEvent qaEvent = (QuickassistActionEvent) evt;
86+
List<Object> arguments = qaEvent.getArguments();
87+
WorkspaceEdit edit =
88+
dtoFactory.createDtoFromJson(arguments.get(0).toString(), WorkspaceEdit.class);
89+
List<Supplier<Promise<Void>>> undos = new ArrayList<>();
90+
91+
StatusNotification notification =
92+
notificationManager.notify(
93+
localization.applyWorkspaceActionNotificationTitle(),
94+
Status.PROGRESS,
95+
DisplayMode.FLOAT_MODE);
96+
97+
Map<String, List<TextEdit>> changes = null;
98+
if (edit.getChanges() != null) {
99+
changes = edit.getChanges();
100+
} else if (edit.getDocumentChanges() != null) {
101+
changes =
102+
edit.getDocumentChanges()
103+
.stream()
104+
.collect(
105+
Collectors.toMap(
106+
(TextDocumentEdit e) -> e.getTextDocument().getUri(),
107+
TextDocumentEdit::getEdits));
108+
}
109+
110+
Promise<Void> done =
111+
promiseHelper.forEach(
112+
changes.entrySet().iterator(),
113+
(entry) -> handleFileChange(notification, entry.getKey(), entry.getValue()),
114+
undos::add);
115+
116+
done.then(
117+
(Void v) -> {
118+
Log.debug(getClass(), "done applying changes");
119+
notification.setStatus(Status.SUCCESS);
120+
notification.setContent(localization.applyWorkspaceActionNotificationDone());
121+
})
122+
.catchError(
123+
(error) -> {
124+
Log.info(getClass(), "caught error applying changes", error);
125+
notification.setStatus(Status.FAIL);
126+
notification.setContent(localization.applyWorkspaceActionNotificationUndoing());
127+
promiseHelper
128+
.forEach(undos.iterator(), (d) -> d.get(), (Void v) -> {})
129+
.then(
130+
(Void v) -> {
131+
notification.setContent(
132+
localization.applyWorkspaceActionNotificationUndone());
133+
})
134+
.catchError(
135+
e -> {
136+
Log.info(getClass(), "Error undoing changes", e);
137+
notification.setContent(
138+
localization.applyWorkspaceActionNotificationUndoFailed());
139+
});
140+
});
141+
}
142+
143+
private Promise<Supplier<Promise<Void>>> handleFileChange(
144+
Notification notification, String uri, List<TextEdit> edits) {
145+
for (EditorPartPresenter editor : editorAgent.getOpenedEditors()) {
146+
if (editor instanceof TextEditor
147+
&& uri.endsWith(editor.getEditorInput().getFile().getLocation().toString())) {
148+
notification.setContent(localization.applyWorkspaceActionNotificationModifying(uri));
149+
TextEditor textEditor = (TextEditor) editor;
150+
HandlesUndoRedo undoRedo = textEditor.getEditorWidget().getUndoRedo();
151+
undoRedo.beginCompoundChange();
152+
Document document = textEditor.getDocument();
153+
edits
154+
.stream()
155+
.sorted(COMPARATOR)
156+
.forEach(
157+
e -> {
158+
Range r = e.getRange();
159+
Position start = r.getStart();
160+
Position end = r.getEnd();
161+
document.replace(
162+
start.getLine(),
163+
start.getCharacter(),
164+
end.getLine(),
165+
end.getCharacter(),
166+
e.getNewText());
167+
});
168+
undoRedo.endCompoundChange();
169+
Supplier<Promise<Void>> value =
170+
() -> {
171+
return promiseProvider.create(
172+
Executor.create(
173+
(ResolveFunction<Void> resolve, RejectFunction reject) -> {
174+
try {
175+
undoRedo.undo();
176+
resolve.apply(null);
177+
} catch (Exception e) {
178+
reject.apply(
179+
new PromiseError() {
180+
public String getMessage() {
181+
return "Error during undo";
182+
}
183+
184+
public Throwable getCause() {
185+
return e;
186+
}
187+
});
188+
}
189+
}));
190+
};
191+
return promiseProvider.resolve(value);
192+
}
193+
}
194+
Promise<List<TextEdit>> undoPromise =
195+
workspaceService.editFile(new FileEditParamsDto(new FileEditParams(uri, edits)));
196+
return undoPromise.then(
197+
(Function<List<TextEdit>, Supplier<Promise<Void>>>)
198+
(List<TextEdit> undoEdits) -> {
199+
return () -> {
200+
Promise<List<TextEdit>> redoPromise =
201+
workspaceService.editFile(
202+
new FileEditParamsDto(new FileEditParams(uri, undoEdits)));
203+
return redoPromise.then(
204+
(List<TextEdit> redo) -> {
205+
return null;
206+
});
207+
};
208+
});
209+
}
210+
}

plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/quickassist/LanguageServerQuickAssistProcessor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public void computeQuickAssistProposals(
120120
QueryAnnotationsEvent.QueryCallback annotationCallback =
121121
new QueryAnnotationsEvent.QueryCallback() {
122122

123+
@SuppressWarnings("ReturnValueIgnored")
123124
@Override
124125
public void respond(
125126
Map<Annotation, org.eclipse.che.ide.api.editor.text.Position> annotations) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (c) 2012-2017 Red Hat, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
*/
11+
package org.eclipse.che.plugin.languageserver.ide.service;
12+
13+
import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcError;
14+
import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcException;
15+
import org.eclipse.che.api.promises.client.PromiseError;
16+
17+
public class ServiceUtil {
18+
private ServiceUtil() {}
19+
20+
public static PromiseError getPromiseError(JsonRpcError jsonRpcError) {
21+
return new PromiseError() {
22+
@Override
23+
public String getMessage() {
24+
return jsonRpcError.getMessage();
25+
}
26+
27+
@Override
28+
public Throwable getCause() {
29+
return new JsonRpcException(jsonRpcError.getCode(), jsonRpcError.getMessage());
30+
}
31+
};
32+
}
33+
}

0 commit comments

Comments
 (0)