diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/texteditor/TextEditor.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/texteditor/TextEditor.java index fb9fa086b38..a58b380848f 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/texteditor/TextEditor.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/texteditor/TextEditor.java @@ -11,6 +11,7 @@ package org.eclipse.che.ide.api.editor.texteditor; import javax.validation.constraints.NotNull; +import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.editor.document.Document; import org.eclipse.che.ide.api.editor.editorconfig.EditorUpdateAction; @@ -18,6 +19,7 @@ import org.eclipse.che.ide.api.editor.keymap.KeyBinding; import org.eclipse.che.ide.api.editor.position.PositionConverter; import org.eclipse.che.ide.api.editor.text.LinearRange; +import org.eclipse.che.ide.api.editor.text.Position; import org.eclipse.che.ide.api.editor.text.TextPosition; import org.eclipse.che.ide.api.editor.text.TextRange; @@ -175,4 +177,13 @@ public interface TextEditor extends EditorPartPresenter { * @param action the action to add */ void addEditorUpdateAction(EditorUpdateAction action); + + /** + * Get word position under offset. + * + * @param offset the offset for look for a word + * @return the word position + */ + @Nullable + Position getWordAtOffset(int offset); } diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/Perspective.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/Perspective.java index 364eb0e9b82..defe166a354 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/Perspective.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/Perspective.java @@ -113,4 +113,11 @@ void addPart( * @param container container in which need expose view */ void go(@NotNull AcceptsOneWidget container); + + /** + * Get current active part + * + * @return the active part + */ + PartPresenter getActivePart(); } diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/WorkspaceAgent.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/WorkspaceAgent.java index e670a994a74..35c8a971088 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/WorkspaceAgent.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/WorkspaceAgent.java @@ -22,13 +22,6 @@ @SDK(title = "ide.api.ui.workspace") public interface WorkspaceAgent { - /** - * Activate given part - * - * @param part - */ - void setActivePart(PartPresenter part); - void setActivePart(@NotNull PartPresenter part, PartStackType type); /** @@ -69,4 +62,18 @@ public interface WorkspaceAgent { * @return the part stack found, else null */ PartStack getPartStack(PartStackType type); + + /** + * Get current active part + * + * @return the active part + */ + PartPresenter getActivePart(); + + /** + * Activate given part + * + * @param part + */ + void setActivePart(PartPresenter part); } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/RenameItemAction.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/RenameItemAction.java index 2b9c8cad93a..d7729f5485d 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/RenameItemAction.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/RenameItemAction.java @@ -40,6 +40,7 @@ import org.eclipse.che.ide.api.editor.EditorAgent; import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.notification.NotificationManager; +import org.eclipse.che.ide.api.parts.WorkspaceAgent; import org.eclipse.che.ide.api.resources.RenamingSupport; import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.resource.Path; @@ -60,6 +61,7 @@ public class RenameItemAction extends AbstractPerspectiveAction { private final NotificationManager notificationManager; private final DialogFactory dialogFactory; private final AppContext appContext; + private final WorkspaceAgent workspaceAgent; @Inject public RenameItemAction( @@ -69,7 +71,8 @@ public RenameItemAction( EditorAgent editorAgent, NotificationManager notificationManager, DialogFactory dialogFactory, - AppContext appContext) { + AppContext appContext, + WorkspaceAgent workspaceAgent) { super( singletonList(PROJECT_PERSPECTIVE_ID), localization.renameItemActionText(), @@ -82,6 +85,7 @@ public RenameItemAction( this.notificationManager = notificationManager; this.dialogFactory = dialogFactory; this.appContext = appContext; + this.workspaceAgent = workspaceAgent; } /** {@inheritDoc} */ @@ -115,7 +119,7 @@ public void actionPerformed(ActionEvent e) { new InputCallback() { @Override public void accepted(final String value) { - //we shouldn't perform renaming file with the same name + // we shouldn't perform renaming file with the same name if (!value.trim().equals(resourceName)) { closeRelatedEditors(resource); @@ -165,6 +169,13 @@ private void closeRelatedEditors(Resource resource) { /** {@inheritDoc} */ @Override public void updateInPerspective(@NotNull ActionEvent e) { + + if (workspaceAgent.getActivePart() == null + || workspaceAgent.getActivePart() instanceof EditorPartPresenter) { + e.getPresentation().setEnabledAndVisible(false); + return; + } + final Resource[] resources = appContext.getResources(); e.getPresentation().setVisible(true); diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/processes/panel/ProcessesPanelViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/processes/panel/ProcessesPanelViewImpl.java index 3206d35c35f..adca50a5850 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/processes/panel/ProcessesPanelViewImpl.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/processes/panel/ProcessesPanelViewImpl.java @@ -13,11 +13,6 @@ import static org.eclipse.che.ide.processes.ProcessTreeNode.ProcessNodeType.MACHINE_NODE; import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.FlowPanel; @@ -37,13 +32,13 @@ import org.eclipse.che.ide.CoreLocalizationConstant; import org.eclipse.che.ide.api.parts.PartStackUIResources; import org.eclipse.che.ide.api.parts.base.BaseView; -import org.eclipse.che.ide.api.theme.Style; import org.eclipse.che.ide.machine.MachineResources; import org.eclipse.che.ide.processes.ProcessDataAdapter; import org.eclipse.che.ide.processes.ProcessTreeNode; import org.eclipse.che.ide.processes.ProcessTreeRenderer; import org.eclipse.che.ide.processes.StopProcessHandler; import org.eclipse.che.ide.terminal.TerminalOptionsJso; +import org.eclipse.che.ide.ui.SplitterFancyUtil; import org.eclipse.che.ide.ui.multisplitpanel.SubPanel; import org.eclipse.che.ide.ui.multisplitpanel.SubPanelFactory; import org.eclipse.che.ide.ui.multisplitpanel.WidgetToShow; @@ -98,7 +93,8 @@ public ProcessesPanelViewImpl( ProcessDataAdapter adapter, ProcessesPartViewImplUiBinder uiBinder, SubPanelFactory subPanelFactory, - CoreLocalizationConstant localizationConstants) { + CoreLocalizationConstant localizationConstants, + SplitterFancyUtil splitterFancyUtil) { super(partStackUIResources); setTitle(localizationConstants.viewProcessesTitle()); this.machineResources = machineResources; @@ -190,65 +186,11 @@ public void onKeyboard(KeyboardEvent event) {} splitLayoutPanel.add(subPanel.getView()); focusedSubPanel = subPanel; - tuneSplitter(); + splitterFancyUtil.tuneSplitter(splitLayoutPanel); splitLayoutPanel.setWidgetHidden(navigationPanel, true); navigationPanelVisible = false; } - /** Improves splitter visibility. */ - private void tuneSplitter() { - NodeList nodes = splitLayoutPanel.getElement().getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node node = nodes.getItem(i); - if (node.hasChildNodes()) { - com.google.gwt.dom.client.Element el = node.getFirstChild().cast(); - if ("gwt-SplitLayoutPanel-HDragger".equals(el.getClassName())) { - tuneSplitter(el); - return; - } - } - } - } - - /** - * Tunes splitter. Makes it wider and adds double border to seem rich. - * - * @param el element to tune - */ - private void tuneSplitter(Element el) { - /** Add Z-Index to move the splitter on the top and make content visible */ - el.getParentElement().getStyle().setProperty("zIndex", "1000"); - el.getParentElement().getStyle().setProperty("overflow", "visible"); - - /** Tune splitter catch panel */ - el.getStyle().setProperty("boxSizing", "border-box"); - el.getStyle().setProperty("width", "5px"); - el.getStyle().setProperty("overflow", "hidden"); - el.getStyle().setProperty("marginLeft", "-3px"); - el.getStyle().setProperty("backgroundColor", "transparent"); - - /** Add small border */ - DivElement smallBorder = Document.get().createDivElement(); - smallBorder.getStyle().setProperty("position", "absolute"); - smallBorder.getStyle().setProperty("width", "1px"); - smallBorder.getStyle().setProperty("height", "100%"); - smallBorder.getStyle().setProperty("left", "3px"); - smallBorder.getStyle().setProperty("top", "0px"); - smallBorder.getStyle().setProperty("backgroundColor", Style.getSplitterSmallBorderColor()); - el.appendChild(smallBorder); - - /** Add large border */ - DivElement largeBorder = Document.get().createDivElement(); - largeBorder.getStyle().setProperty("position", "absolute"); - largeBorder.getStyle().setProperty("width", "2px"); - largeBorder.getStyle().setProperty("height", "100%"); - largeBorder.getStyle().setProperty("left", "1px"); - largeBorder.getStyle().setProperty("top", "0px"); - largeBorder.getStyle().setProperty("opacity", "0.4"); - largeBorder.getStyle().setProperty("backgroundColor", Style.getSplitterLargeBorderColor()); - el.appendChild(largeBorder); - } - @Override public void addWidget( final String processId, diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/WorkspacePresenter.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/WorkspacePresenter.java index 420701de031..741c2d4da1c 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/WorkspacePresenter.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/WorkspacePresenter.java @@ -157,6 +157,11 @@ public PartStack getPartStack(PartStackType type) { return activePerspective.getPartStack(type); } + @Override + public PartPresenter getActivePart() { + return activePerspective.getActivePart(); + } + @Override public JsonObject getState() { JsonObject state = Json.createObject(); diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/perspectives/general/AbstractPerspective.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/perspectives/general/AbstractPerspective.java index dcc538eae85..2e642d428be 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/perspectives/general/AbstractPerspective.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/perspectives/general/AbstractPerspective.java @@ -310,6 +310,11 @@ public PartStack getPartStack(@NotNull PartStackType type) { return partStacks.get(type); } + @Override + public PartPresenter getActivePart() { + return activePart; + } + @Override public JsonObject getState() { JsonObject state = Json.createObject(); diff --git a/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/actions/RenameItemActionTest.java b/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/actions/RenameItemActionTest.java index cdaa9b762a3..81a316d4e39 100644 --- a/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/actions/RenameItemActionTest.java +++ b/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/actions/RenameItemActionTest.java @@ -11,6 +11,7 @@ package org.eclipse.che.ide.actions; import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -24,7 +25,10 @@ import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.dialogs.DialogFactory; import org.eclipse.che.ide.api.editor.EditorAgent; +import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.notification.NotificationManager; +import org.eclipse.che.ide.api.parts.PartPresenter; +import org.eclipse.che.ide.api.parts.WorkspaceAgent; import org.eclipse.che.ide.api.project.ProjectServiceClient; import org.eclipse.che.ide.api.resources.RenamingSupport; import org.eclipse.che.ide.api.resources.Resource; @@ -54,6 +58,9 @@ public class RenameItemActionTest { @Mock private ActionEvent event; @Mock private Presentation presentation; + @Mock private WorkspaceAgent workspaceAgent; + @Mock private PartPresenter partPresenter; + private Resource[] selectedResources = new Resource[1]; private Set renamingSupport = new HashSet<>(); @@ -63,6 +70,7 @@ public void setUp() throws Exception { when(appContext.getResources()).thenReturn(selectedResources); when(event.getPresentation()).thenReturn(presentation); + when(workspaceAgent.getActivePart()).thenReturn(partPresenter); } @Test @@ -102,7 +110,8 @@ public void actionShouldBeDisabledIfSomeRenameValidatorForbidsRenameOperation() editorAgent, notificationManager, dialogFactory, - appContext); + appContext, + workspaceAgent); action.updateInPerspective(event); verify(presentation).setVisible(true); @@ -122,10 +131,20 @@ public void actionShouldBeEnabled() throws Exception { editorAgent, notificationManager, dialogFactory, - appContext); + appContext, + workspaceAgent); action.updateInPerspective(event); verify(presentation).setVisible(true); verify(presentation).setEnabled(true); } + + @Test + public void actionShouldBeDisabledIfEditorActive() { + when(workspaceAgent.getActivePart()).thenReturn(mock(EditorPartPresenter.class)); + + action.updateInPerspective(event); + + verify(presentation).setEnabledAndVisible(false); + } } diff --git a/ide/che-core-ide-ui/pom.xml b/ide/che-core-ide-ui/pom.xml index df9df5e59f4..ac9877849e4 100644 --- a/ide/che-core-ide-ui/pom.xml +++ b/ide/che-core-ide-ui/pom.xml @@ -37,6 +37,10 @@ com.google.inject.extensions guice-assistedinject + + javax.inject + javax.inject + javax.validation validation-api diff --git a/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/SplitterFancyUtil.java b/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/SplitterFancyUtil.java new file mode 100644 index 00000000000..e89c7cfbeeb --- /dev/null +++ b/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/SplitterFancyUtil.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.ide.ui; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.user.client.ui.SplitLayoutPanel; +import javax.inject.Singleton; +import org.eclipse.che.ide.api.theme.Style; + +/** Tune {@link SplitLayoutPanel} splitter visibility */ +@Singleton +public class SplitterFancyUtil { + + /** Improves splitter visibility. */ + public void tuneSplitter(SplitLayoutPanel splitLayoutPanel) { + NodeList nodes = splitLayoutPanel.getElement().getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.getItem(i); + if (node.hasChildNodes()) { + com.google.gwt.dom.client.Element el = node.getFirstChild().cast(); + if ("gwt-SplitLayoutPanel-HDragger".equals(el.getClassName())) { + tuneSplitter(el); + return; + } + } + } + } + + /** + * Tunes splitter. Makes it wider and adds double border to seem rich. + * + * @param el element to tune + */ + private void tuneSplitter(Element el) { + /** Add Z-Index to move the splitter on the top and make content visible */ + el.getParentElement().getStyle().setProperty("zIndex", "1000"); + el.getParentElement().getStyle().setProperty("overflow", "visible"); + + /** Tune splitter catch panel */ + el.getStyle().setProperty("boxSizing", "border-box"); + el.getStyle().setProperty("width", "5px"); + el.getStyle().setProperty("overflow", "hidden"); + el.getStyle().setProperty("marginLeft", "-3px"); + el.getStyle().setProperty("backgroundColor", "transparent"); + + /** Add small border */ + DivElement smallBorder = Document.get().createDivElement(); + smallBorder.getStyle().setProperty("position", "absolute"); + smallBorder.getStyle().setProperty("width", "1px"); + smallBorder.getStyle().setProperty("height", "100%"); + smallBorder.getStyle().setProperty("left", "3px"); + smallBorder.getStyle().setProperty("top", "0px"); + smallBorder.getStyle().setProperty("backgroundColor", Style.getSplitterSmallBorderColor()); + el.appendChild(smallBorder); + + /** Add large border */ + DivElement largeBorder = Document.get().createDivElement(); + largeBorder.getStyle().setProperty("position", "absolute"); + largeBorder.getStyle().setProperty("width", "2px"); + largeBorder.getStyle().setProperty("height", "100%"); + largeBorder.getStyle().setProperty("left", "1px"); + largeBorder.getStyle().setProperty("top", "0px"); + largeBorder.getStyle().setProperty("opacity", "0.4"); + largeBorder.getStyle().setProperty("backgroundColor", Style.getSplitterLargeBorderColor()); + el.appendChild(largeBorder); + } +} diff --git a/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/dialogs/input/InputDialogFooter.java b/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/dialogs/input/InputDialogFooter.java index fd7b73ce43c..3479b3eeff7 100644 --- a/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/dialogs/input/InputDialogFooter.java +++ b/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/dialogs/input/InputDialogFooter.java @@ -34,7 +34,7 @@ */ public class InputDialogFooter implements IsWidget { - private static final Resources resources = GWT.create(Resources.class); + protected static final Resources resources = GWT.create(Resources.class); /** The UI binder instance. */ private static ConfirmWindowFooterUiBinder uiBinder = GWT.create(ConfirmWindowFooterUiBinder.class); @@ -42,10 +42,10 @@ public class InputDialogFooter implements IsWidget { @UiField(provided = true) UILocalizationConstant messages; - @UiField Button okButton; - @UiField Button cancelButton; + @UiField protected Button okButton; + @UiField protected Button cancelButton; - HTMLPanel rootPanel; + protected HTMLPanel rootPanel; /** The action delegate. */ private ActionDelegate actionDelegate; diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/AbstractPresentationNode.java b/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/smartTree/presentation/AbstractPresentationNode.java similarity index 79% rename from plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/AbstractPresentationNode.java rename to ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/smartTree/presentation/AbstractPresentationNode.java index 60fdfa5d38a..b8bf1b0bb43 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/AbstractPresentationNode.java +++ b/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/smartTree/presentation/AbstractPresentationNode.java @@ -8,13 +8,15 @@ * Contributors: * Red Hat, Inc. - initial API and implementation */ -package org.eclipse.che.ide.ext.java.client.search.node; + +package org.eclipse.che.ide.ui.smartTree.presentation; import org.eclipse.che.ide.api.data.tree.AbstractTreeNode; -import org.eclipse.che.ide.ui.smartTree.presentation.HasPresentation; -import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; -/** @author Evgen Vidolob */ +/** + * Abstract class for utilize holding and creating {@link NodePresentation}, implements {@link + * HasPresentation#getPresentation(boolean)} + */ public abstract class AbstractPresentationNode extends AbstractTreeNode implements HasPresentation { private NodePresentation nodePresentation; diff --git a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorPresenter.java b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorPresenter.java index 95445c0775b..16f66f43803 100644 --- a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorPresenter.java +++ b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorPresenter.java @@ -101,6 +101,7 @@ import org.eclipse.che.ide.api.editor.signature.SignatureHelp; import org.eclipse.che.ide.api.editor.signature.SignatureHelpProvider; import org.eclipse.che.ide.api.editor.text.LinearRange; +import org.eclipse.che.ide.api.editor.text.Position; import org.eclipse.che.ide.api.editor.text.TextPosition; import org.eclipse.che.ide.api.editor.text.TextRange; import org.eclipse.che.ide.api.editor.texteditor.CanWrapLines; @@ -199,6 +200,7 @@ public class OrionEditorPresenter extends AbstractEditorPresenter private final PromiseProvider promises; private final ClientServerEventService clientServerEventService; private final EditorFileStatusNotificationOperation editorFileStatusNotificationOperation; + private final WordDetectionUtil wordDetectionUtil; private final AnnotationRendering rendering = new AnnotationRendering(); private HasKeyBindings keyBindingsManager; @@ -247,7 +249,8 @@ public OrionEditorPresenter( final AutoSaveMode autoSaveMode, final PromiseProvider promises, final ClientServerEventService clientServerEventService, - final EditorFileStatusNotificationOperation editorFileStatusNotificationOperation) { + final EditorFileStatusNotificationOperation editorFileStatusNotificationOperation, + final WordDetectionUtil wordDetectionUtil) { this.codeAssistantFactory = codeAssistantFactory; this.deletedFilesController = deletedFilesController; this.breakpointManager = breakpointManager; @@ -273,6 +276,7 @@ public OrionEditorPresenter( this.promises = promises; this.clientServerEventService = clientServerEventService; this.editorFileStatusNotificationOperation = editorFileStatusNotificationOperation; + this.wordDetectionUtil = wordDetectionUtil; keyBindingsManager = new TemporaryKeyBindingsManager(); @@ -805,6 +809,11 @@ public void addEditorUpdateAction(EditorUpdateAction action) { this.updateActions.add(action); } + @Override + public Position getWordAtOffset(int offset) { + return wordDetectionUtil.getWordAtOffset(getDocument(), offset); + } + @Override public void addKeybinding(KeyBinding keyBinding) { // the actual HasKeyBindings object can change, so use indirection diff --git a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/WordDetectionUtil.java b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/WordDetectionUtil.java new file mode 100644 index 00000000000..8af33765c99 --- /dev/null +++ b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/WordDetectionUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.ide.editor.orion.client; + +import com.google.gwt.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; +import org.eclipse.che.ide.api.editor.document.Document; +import org.eclipse.che.ide.api.editor.text.Position; + +/** */ +public class WordDetectionUtil { + + private static final String COMMON_WORD_REGEXP = + "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)"; + + public Position getWordAtOffset(Document document, int offset) { + if (document == null) { + return null; + } + + RegExp regExp = RegExp.compile(COMMON_WORD_REGEXP, "g"); + int line = document.getLineAtOffset(offset); + String lineContent = document.getLineContent(line); + int lineStart = document.getLineStart(line); + + int pos = offset - lineStart; + int start = lineContent.lastIndexOf(' ', pos - 1) + 1; + + regExp.setLastIndex(start); + MatchResult matchResult; + while ((matchResult = regExp.exec(lineContent)) != null) { + if (matchResult.getIndex() <= pos && regExp.getLastIndex() >= pos) { + return new Position(matchResult.getIndex() + lineStart, matchResult.getGroup(0).length()); + } + } + return null; + } +} diff --git a/plugins/plugin-csharp/che-plugin-csharp-lang-server/src/main/java/org/eclipse/che/plugin/csharp/languageserver/CSharpLanguageServerLauncher.java b/plugins/plugin-csharp/che-plugin-csharp-lang-server/src/main/java/org/eclipse/che/plugin/csharp/languageserver/CSharpLanguageServerLauncher.java index 8c0a284323f..396bae75684 100644 --- a/plugins/plugin-csharp/che-plugin-csharp-lang-server/src/main/java/org/eclipse/che/plugin/csharp/languageserver/CSharpLanguageServerLauncher.java +++ b/plugins/plugin-csharp/che-plugin-csharp-lang-server/src/main/java/org/eclipse/che/plugin/csharp/languageserver/CSharpLanguageServerLauncher.java @@ -17,7 +17,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; +import java.util.Collections; import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncherTemplate; import org.eclipse.che.api.languageserver.registry.DocumentFilter; @@ -98,7 +98,7 @@ private static LanguageServerDescription createServerDescription() { new LanguageServerDescription( "org.eclipse.che.plugin.csharp.languageserver", null, - Arrays.asList(new DocumentFilter(CSharpModule.LANGUAGE_ID, REGEX, null))); + Collections.singletonList(new DocumentFilter(CSharpModule.LANGUAGE_ID, REGEX, null))); return description; } diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/rename/RenameRefactoringAction.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/rename/RenameRefactoringAction.java index 68523fc0e9c..73f52efbea3 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/rename/RenameRefactoringAction.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/rename/RenameRefactoringAction.java @@ -169,7 +169,7 @@ public void updateInPerspective(ActionEvent event) { if (editorInFocus) { final EditorPartPresenter editorPart = editorAgent.getActiveEditor(); if (editorPart == null || !(editorPart instanceof TextEditor)) { - event.getPresentation().setEnabled(false); + event.getPresentation().setEnabledAndVisible(false); return; } @@ -179,22 +179,22 @@ public void updateInPerspective(ActionEvent event) { final Optional project = ((File) file).getRelatedProject(); if (!project.isPresent()) { - event.getPresentation().setEnabled(false); + event.getPresentation().setEnabledAndVisible(false); return; } event .getPresentation() - .setEnabled(JavaUtil.isJavaProject(project.get()) && isJavaFile(file)); + .setEnabledAndVisible(JavaUtil.isJavaProject(project.get()) && isJavaFile(file)); } else { - event.getPresentation().setEnabled(isJavaFile(file)); + event.getPresentation().setEnabledAndVisible(isJavaFile(file)); } } else { final Resource[] resources = appContext.getResources(); if (resources == null || resources.length > 1) { - event.getPresentation().setEnabled(false); + event.getPresentation().setEnabledAndVisible(false); return; } @@ -203,7 +203,7 @@ public void updateInPerspective(ActionEvent event) { final Optional project = resource.getRelatedProject(); if (!project.isPresent()) { - event.getPresentation().setEnabled(false); + event.getPresentation().setEnabledAndVisible(false); return; } @@ -212,14 +212,14 @@ public void updateInPerspective(ActionEvent event) { if (resource.getResourceType() == FILE) { event .getPresentation() - .setEnabled( + .setEnabledAndVisible( JavaUtil.isJavaProject(project.get()) && srcFolder.isPresent() && isJavaFile((File) resource)); } else if (resource instanceof Container) { event .getPresentation() - .setEnabled(JavaUtil.isJavaProject(project.get()) && srcFolder.isPresent()); + .setEnabledAndVisible(JavaUtil.isJavaProject(project.get()) && srcFolder.isPresent()); } } } diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/JavaProjectNode.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/JavaProjectNode.java index 9eec26033ff..054cbe6adc9 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/JavaProjectNode.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/JavaProjectNode.java @@ -26,6 +26,7 @@ import org.eclipse.che.ide.ext.java.shared.dto.model.PackageFragmentRoot; import org.eclipse.che.ide.ext.java.shared.dto.search.Match; import org.eclipse.che.ide.project.shared.NodesResources; +import org.eclipse.che.ide.ui.smartTree.presentation.AbstractPresentationNode; import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; /** @author Evgen Vidolob */ diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/MatchNode.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/MatchNode.java index ed093b45e4a..86aa5265912 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/MatchNode.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/MatchNode.java @@ -44,6 +44,7 @@ import org.eclipse.che.ide.ext.java.shared.dto.search.Match; import org.eclipse.che.ide.resource.Path; import org.eclipse.che.ide.ui.smartTree.TreeStyles; +import org.eclipse.che.ide.ui.smartTree.presentation.AbstractPresentationNode; import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; import org.eclipse.che.ide.util.dom.Elements; diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/MethodNode.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/MethodNode.java index 3d6a2bb72f4..7d0e56ba89c 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/MethodNode.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/MethodNode.java @@ -28,6 +28,7 @@ import org.eclipse.che.ide.ext.java.shared.dto.model.CompilationUnit; import org.eclipse.che.ide.ext.java.shared.dto.model.Method; import org.eclipse.che.ide.ext.java.shared.dto.search.Match; +import org.eclipse.che.ide.ui.smartTree.presentation.AbstractPresentationNode; import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; import org.vectomatic.dom.svg.ui.SVGResource; diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/PackageFragmentNode.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/PackageFragmentNode.java index 5d7680ff9ec..05477852aa4 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/PackageFragmentNode.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/PackageFragmentNode.java @@ -27,6 +27,7 @@ import org.eclipse.che.ide.ext.java.shared.dto.model.PackageFragmentRoot; import org.eclipse.che.ide.ext.java.shared.dto.model.Type; import org.eclipse.che.ide.ext.java.shared.dto.search.Match; +import org.eclipse.che.ide.ui.smartTree.presentation.AbstractPresentationNode; import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; import org.eclipse.che.ide.util.Pair; diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/ResultNode.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/ResultNode.java index b5898fa9c64..8c3b5cb3acb 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/ResultNode.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/ResultNode.java @@ -28,6 +28,7 @@ import org.eclipse.che.ide.ext.java.shared.dto.search.FindUsagesResponse; import org.eclipse.che.ide.ext.java.shared.dto.search.Match; import org.eclipse.che.ide.ui.smartTree.TreeStyles; +import org.eclipse.che.ide.ui.smartTree.presentation.AbstractPresentationNode; import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; import org.eclipse.che.ide.util.dom.Elements; diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/TypeNode.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/TypeNode.java index e3274b89fc5..41d6d562164 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/TypeNode.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/search/node/TypeNode.java @@ -32,6 +32,7 @@ import org.eclipse.che.ide.ext.java.shared.dto.model.Initializer; import org.eclipse.che.ide.ext.java.shared.dto.model.Type; import org.eclipse.che.ide.ext.java.shared.dto.search.Match; +import org.eclipse.che.ide.ui.smartTree.presentation.AbstractPresentationNode; import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; import org.vectomatic.dom.svg.ui.SVGResource; diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/pom.xml b/plugins/plugin-languageserver/che-plugin-languageserver-ide/pom.xml index 1cde37bd5d3..df0e4747992 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/pom.xml +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/pom.xml @@ -100,6 +100,10 @@ org.eclipse.xtext org.eclipse.xtext.xbase.lib.gwt + + org.slf4j + slf4j-api + org.vectomatic lib-gwt-svg diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerExtension.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerExtension.java index 3cfd301ac02..4e722d65631 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerExtension.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerExtension.java @@ -35,6 +35,7 @@ import org.eclipse.che.plugin.languageserver.ide.navigation.symbol.GoToSymbolAction; import org.eclipse.che.plugin.languageserver.ide.navigation.workspace.FindSymbolAction; import org.eclipse.che.plugin.languageserver.ide.registry.LanguageServerRegistry; +import org.eclipse.che.plugin.languageserver.ide.rename.LSRenameAction; import org.eclipse.che.plugin.languageserver.ide.service.TextDocumentServiceClient; import org.eclipse.lsp4j.DidCloseTextDocumentParams; import org.eclipse.lsp4j.DidOpenTextDocumentParams; @@ -63,13 +64,15 @@ protected void registerAction( FindDefinitionAction findDefinitionAction, FindReferencesAction findReferencesAction, ApplyTextEditAction applyTextEditAction, - ApplyWorkspaceEditAction applyWorkspaceEditAction) { + ApplyWorkspaceEditAction applyWorkspaceEditAction, + LSRenameAction renameAction) { actionManager.registerAction("LSGoToSymbolAction", goToSymbolAction); actionManager.registerAction("LSFindSymbolAction", findSymbolAction); actionManager.registerAction("LSFindDefinitionAction", findDefinitionAction); actionManager.registerAction("LSFindReferencesAction", findReferencesAction); actionManager.registerAction("lsp.applyTextEdit", applyTextEditAction); actionManager.registerAction("lsp.applyWorkspaceEdit", applyWorkspaceEditAction); + actionManager.registerAction("LS.rename", renameAction); DefaultActionGroup assistantGroup = (DefaultActionGroup) actionManager.getAction(GROUP_ASSISTANT); @@ -82,6 +85,15 @@ protected void registerAction( assistantGroup.add( findReferencesAction, new Constraints(Anchor.BEFORE, GROUP_ASSISTANT_REFACTORING)); + DefaultActionGroup refactoringGroup = + (DefaultActionGroup) actionManager.getAction(GROUP_ASSISTANT_REFACTORING); + if (refactoringGroup == null) { + refactoringGroup = new DefaultActionGroup("Refactoring", true, actionManager); + actionManager.registerAction(GROUP_ASSISTANT_REFACTORING, refactoringGroup); + } + + refactoringGroup.add(renameAction, new Constraints(Anchor.AFTER, "javaRenameRefactoring")); + if (UserAgent.isMac()) { keyBindingManager .getGlobal() @@ -101,6 +113,10 @@ protected void registerAction( keyBindingManager .getGlobal() .addKey(new KeyBuilder().charCode(KeyCodeMap.F4).build(), "LSFindDefinitionAction"); + + keyBindingManager + .getGlobal() + .addKey(new KeyBuilder().shift().charCode(KeyCodeMap.F6).build(), "LS.rename"); } @Inject @@ -111,37 +127,28 @@ protected void registerFileEventHandler( final LanguageServerRegistry lsRegistry) { eventBus.addHandler( FileEvent.TYPE, - new FileEvent.FileEventHandler() { - - @Override - public void onFileOperation(final FileEvent event) { - Path location = event.getFile().getLocation(); - if (lsRegistry.getLanguageDescription(event.getFile()) == null) { - return; - } - final TextDocumentIdentifier documentId = - dtoFactory.createDto(TextDocumentIdentifier.class); - documentId.setUri(location.toString()); - switch (event.getOperationType()) { - case OPEN: - onOpen(event, dtoFactory, serviceClient, lsRegistry); - break; - case CLOSE: - onClose(documentId, dtoFactory, serviceClient); - break; - case SAVE: - onSave(documentId, dtoFactory, serviceClient); - break; - } + event -> { + Path location = event.getFile().getLocation(); + if (lsRegistry.getLanguageDescription(event.getFile()) == null) { + return; + } + final TextDocumentIdentifier documentId = + dtoFactory.createDto(TextDocumentIdentifier.class); + documentId.setUri(location.toString()); + switch (event.getOperationType()) { + case OPEN: + onOpen(event, dtoFactory, serviceClient, lsRegistry); + break; + case CLOSE: + onClose(documentId, dtoFactory, serviceClient); + break; + case SAVE: + onSave(documentId, dtoFactory, serviceClient); + break; } - // onOpen(event.getEditor(), event.getFile(), dtoFactory, serviceClient, fileTypeRegister); }); } - // private boolean checkIsLSExist(Path location, LanguageServerFileTypeRegister fileTypeRegister){ - // return !(location.getFileExtension() == null || !fileTypeRegister.hasLSForExtension(location.getFileExtension())); - // } - private void onSave( TextDocumentIdentifier documentId, DtoFactory dtoFactory, diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerLocalization.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerLocalization.java index 4ff0b2c14d5..9a905703a9b 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerLocalization.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerLocalization.java @@ -68,4 +68,25 @@ public interface LanguageServerLocalization extends Messages { @Key("find.symbol.action.title") String findSymbolActionTitle(); + + @Key("rename.action.title") + String renameActionTitle(); + + @Key("rename.view.title") + String renameViewTitle(); + + @Key("rename.view.tooltip") + String renameViewTooltip(); + + @Key("rename.dialog.label") + String renameDialogLabel(); + + @Key("rename.dialog.preview.label") + String renameDialogPreviewLabel(); + + @Key("rename.view.cancel.label") + String renameViewCancelLabel(); + + @Key("rename.view.do.rename.label") + String renameViewDoRenameLabel(); } diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/quickassist/ApplyWorkspaceEditAction.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/quickassist/ApplyWorkspaceEditAction.java index 3948518bce1..542356de897 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/quickassist/ApplyWorkspaceEditAction.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/quickassist/ApplyWorkspaceEditAction.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.function.Supplier; import java.util.stream.Collectors; +import javax.inject.Singleton; import org.eclipse.che.api.languageserver.shared.dto.DtoClientImpls.FileEditParamsDto; import org.eclipse.che.api.languageserver.shared.model.FileEditParams; import org.eclipse.che.api.languageserver.shared.util.RangeComparator; @@ -50,6 +51,7 @@ import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.WorkspaceEdit; +@Singleton public class ApplyWorkspaceEditAction extends Action { private static final Comparator COMPARATOR = RangeComparator.transform(new RangeComparator().reversed(), TextEdit::getRange); @@ -86,6 +88,10 @@ public void actionPerformed(ActionEvent evt) { List arguments = qaEvent.getArguments(); WorkspaceEdit edit = dtoFactory.createDtoFromJson(arguments.get(0).toString(), WorkspaceEdit.class); + applyWorkspaceEdit(edit); + } + + public void applyWorkspaceEdit(WorkspaceEdit edit) { List>> undos = new ArrayList<>(); StatusNotification notification = @@ -125,7 +131,7 @@ public void actionPerformed(ActionEvent evt) { notification.setStatus(Status.FAIL); notification.setContent(localization.applyWorkspaceActionNotificationUndoing()); promiseHelper - .forEach(undos.iterator(), (d) -> d.get(), (Void v) -> {}) + .forEach(undos.iterator(), Supplier::get, (Void v) -> {}) .then( (Void v) -> { notification.setContent( diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/inject/LanguageServerGinModule.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/inject/LanguageServerGinModule.java index 0b00c534f6f..84009a2089d 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/inject/LanguageServerGinModule.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/inject/LanguageServerGinModule.java @@ -24,6 +24,7 @@ import org.eclipse.che.plugin.languageserver.ide.editor.quickassist.LanguageServerQuickAssistProcessorFactory; import org.eclipse.che.plugin.languageserver.ide.editor.signature.LanguageServerSignatureHelpFactory; import org.eclipse.che.plugin.languageserver.ide.location.OpenLocationPresenterFactory; +import org.eclipse.che.plugin.languageserver.ide.rename.node.RenameNodeFactory; import org.eclipse.che.plugin.languageserver.ide.service.PublishDiagnosticsReceiver; import org.eclipse.che.plugin.languageserver.ide.service.ShowMessageJsonRpcReceiver; @@ -41,6 +42,7 @@ protected void configure() { install(new GinFactoryModuleBuilder().build(LanguageServerQuickAssistProcessorFactory.class)); install(new GinFactoryModuleBuilder().build(LanguageServerReconcileStrategyFactory.class)); install(new GinFactoryModuleBuilder().build(LanguageServerSignatureHelpFactory.class)); + install(new GinFactoryModuleBuilder().build(RenameNodeFactory.class)); GinMapBinder wsAgentComponentsBinder = GinMapBinder.newMapBinder(binder(), String.class, WsAgentComponent.class); diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/LSRenameAction.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/LSRenameAction.java new file mode 100644 index 00000000000..e47a6b06a58 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/LSRenameAction.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename; + +import static java.util.Collections.singletonList; +import static org.eclipse.che.ide.workspace.perspectives.project.ProjectPerspective.PROJECT_PERSPECTIVE_ID; + +import java.util.Objects; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.ide.api.action.AbstractPerspectiveAction; +import org.eclipse.che.ide.api.action.ActionEvent; +import org.eclipse.che.ide.api.action.Presentation; +import org.eclipse.che.ide.api.editor.EditorAgent; +import org.eclipse.che.ide.api.editor.EditorPartPresenter; +import org.eclipse.che.ide.api.editor.editorconfig.TextEditorConfiguration; +import org.eclipse.che.ide.api.editor.texteditor.TextEditor; +import org.eclipse.che.ide.api.parts.WorkspaceAgent; +import org.eclipse.che.plugin.languageserver.ide.LanguageServerLocalization; +import org.eclipse.che.plugin.languageserver.ide.editor.LanguageServerEditorConfiguration; +import org.eclipse.lsp4j.ServerCapabilities; + +/** Action for rename feature */ +@Singleton +public class LSRenameAction extends AbstractPerspectiveAction { + + private final EditorAgent editorAgent; + private final RenamePresenter renamePresenter; + private final WorkspaceAgent workspaceAgent; + + @Inject + public LSRenameAction( + LanguageServerLocalization localization, + EditorAgent editorAgent, + RenamePresenter renamePresenter, + WorkspaceAgent workspaceAgent) { + super( + singletonList(PROJECT_PERSPECTIVE_ID), + localization.renameActionTitle(), + localization.renameActionTitle()); + this.editorAgent = editorAgent; + this.renamePresenter = renamePresenter; + this.workspaceAgent = workspaceAgent; + } + + @Override + public void updateInPerspective(ActionEvent event) { + EditorPartPresenter activeEditor = editorAgent.getActiveEditor(); + Presentation presentation = event.getPresentation(); + if (activeEditor != workspaceAgent.getActivePart()) { + presentation.setEnabledAndVisible(false); + return; + } + if (Objects.nonNull(activeEditor) && activeEditor instanceof TextEditor) { + TextEditorConfiguration configuration = ((TextEditor) activeEditor).getConfiguration(); + if (configuration instanceof LanguageServerEditorConfiguration) { + ServerCapabilities capabilities = + ((LanguageServerEditorConfiguration) configuration).getServerCapabilities(); + presentation.setEnabledAndVisible( + capabilities.getRenameProvider() != null && capabilities.getRenameProvider()); + return; + } + } + presentation.setEnabledAndVisible(false); + } + + @Override + public void actionPerformed(ActionEvent e) { + EditorPartPresenter activeEditor = editorAgent.getActiveEditor(); + if (Objects.nonNull(activeEditor) && activeEditor instanceof TextEditor) { + renamePresenter.rename(((TextEditor) activeEditor)); + } + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameDialog.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameDialog.java new file mode 100644 index 00000000000..b819ed73355 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameDialog.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename; + +import java.util.function.Consumer; +import javax.inject.Inject; +import org.eclipse.che.ide.api.dialogs.CancelCallback; +import org.eclipse.che.ide.ui.dialogs.input.InputDialogViewImpl; +import org.eclipse.che.plugin.languageserver.ide.LanguageServerLocalization; + +/** Rename dialog form, extends {@link InputDialogViewImpl} has additional 'Preview' button */ +public class RenameDialog extends InputDialogViewImpl { + + private final RenameDialogFooter dialogFooter; + private final LanguageServerLocalization localization; + + @Inject + public RenameDialog(RenameDialogFooter dialogFooter, LanguageServerLocalization localization) { + super(dialogFooter); + this.dialogFooter = dialogFooter; + this.localization = localization; + } + + public void show( + String value, + String oldName, + Consumer rename, + Consumer preview, + CancelCallback cancelCallback) { + setTitle(localization.renameViewTitle()); + setContent(localization.renameDialogLabel()); + setValue(value); + setSelectionStartIndex(0); + setSelectionLength(value.length()); + dialogFooter.addPreviewClickHandler(() -> preview.accept(getValue())); + dialogFooter.setEnabledProceedButtons(false); + setDelegate( + new ActionDelegate() { + @Override + public void cancelled() { + cancelCallback.cancelled(); + } + + @Override + public void accepted() { + rename.accept(getValue()); + } + + @Override + public void inputValueChanged() { + if (getValue().equals(oldName)) { + dialogFooter.setEnabledProceedButtons(false); + } else { + dialogFooter.setEnabledProceedButtons(true); + } + } + + @Override + public void onEnterClicked() { + rename.accept(getValue()); + } + }); + showDialog(); + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameDialogFooter.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameDialogFooter.java new file mode 100644 index 00000000000..4a02f816f98 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameDialogFooter.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.Button; +import javax.inject.Inject; +import org.eclipse.che.ide.ui.UILocalizationConstant; +import org.eclipse.che.ide.ui.dialogs.input.InputDialogFooter; +import org.eclipse.che.plugin.languageserver.ide.LanguageServerLocalization; + +/** Rename dialog footer with 'Preview' button */ +public class RenameDialogFooter extends InputDialogFooter { + + private final Button previewButton; + + @Inject + public RenameDialogFooter( + UILocalizationConstant messages, LanguageServerLocalization localization) { + super(messages); + previewButton = new Button(); + previewButton.setText(localization.renameDialogPreviewLabel()); + previewButton.getElement().getStyle().setMarginRight(1, Unit.EM); + previewButton.addStyleName(resources.windowCss().button()); + rootPanel.clear(); + rootPanel.add(cancelButton); + rootPanel.add(previewButton); + rootPanel.add(okButton); + } + + void addPreviewClickHandler(Runnable clickHandler) { + previewButton.addClickHandler(e -> clickHandler.run()); + } + + void setEnabledProceedButtons(boolean enabled) { + previewButton.setEnabled(enabled); + okButton.setEnabled(enabled); + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameInputBox.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameInputBox.java new file mode 100644 index 00000000000..204632c98c3 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameInputBox.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.TextBox; +import elemental.events.Event; +import elemental.events.KeyboardEvent; +import javax.inject.Inject; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.api.promises.client.PromiseProvider; +import org.eclipse.che.ide.api.keybinding.KeyBindingAgent; +import org.eclipse.che.ide.runtime.OperationCanceledException; +import org.eclipse.che.ide.util.input.CharCodeWithModifiers; +import org.eclipse.che.ide.util.input.SignalEvent; +import org.eclipse.che.ide.util.input.SignalEventUtils; + +/** Input box overlay for entering new name */ +class RenameInputBox extends PopupPanel { + + private final PromiseProvider promiseProvider; + private final int keyDigest; + private TextBox valueTextBox; + + @Inject + public RenameInputBox(PromiseProvider promiseProvider, KeyBindingAgent keyBindingAgent) { + super(true, true); + this.promiseProvider = promiseProvider; + valueTextBox = new TextBox(); + CharCodeWithModifiers keyBinding = keyBindingAgent.getKeyBinding("LS.rename"); + keyDigest = keyBinding.getKeyDigest(); + valueTextBox.addStyleName("orionCodenvy"); + setWidget(valueTextBox); + } + + Promise setPositionAndShow(int x, int y, String value, Runnable openWindow) { + setPopupPosition(x, y); + valueTextBox.setValue(value); + return promiseProvider.create( + callback -> { + show(); + + HandlerRegistration registration = + addCloseHandler( + event -> { + if (event.isAutoClosed()) { + callback.onFailure(new OperationCanceledException()); + } + }); + + KeyDownHandler handler = + event -> { + if (KeyboardEvent.KeyCode.ESC == event.getNativeEvent().getKeyCode()) { + event.stopPropagation(); + event.preventDefault(); + callback.onFailure(new OperationCanceledException()); + registration.removeHandler(); + hide(false); + } else if (KeyboardEvent.KeyCode.ENTER == event.getNativeEvent().getKeyCode()) { + event.stopPropagation(); + event.preventDefault(); + registration.removeHandler(); + hide(false); + callback.onSuccess(valueTextBox.getValue()); + } else { + SignalEvent signalEvent = SignalEventUtils.create((Event) event.getNativeEvent()); + if (keyDigest == CharCodeWithModifiers.computeKeyDigest(signalEvent)) { + event.preventDefault(); + event.stopPropagation(); + openWindow.run(); + } + } + }; + valueTextBox.addDomHandler(handler, KeyDownEvent.getType()); + }); + } + + String getInputValue() { + return valueTextBox.getValue(); + } + + @Override + public void show() { + super.show(); + Scheduler.get() + .scheduleDeferred( + () -> { + valueTextBox.selectAll(); + valueTextBox.setFocus(true); + }); + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenamePresenter.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenamePresenter.java new file mode 100644 index 00000000000..47d7261c060 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenamePresenter.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename; + +import static org.slf4j.LoggerFactory.getLogger; + +import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.google.gwt.user.client.ui.IsWidget; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import org.eclipse.che.api.languageserver.shared.model.ExtendedTextDocumentEdit; +import org.eclipse.che.api.languageserver.shared.model.ExtendedTextEdit; +import org.eclipse.che.api.languageserver.shared.model.ExtendedWorkspaceEdit; +import org.eclipse.che.api.languageserver.shared.model.RenameResult; +import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.api.editor.position.PositionConverter.PixelCoordinates; +import org.eclipse.che.ide.api.editor.text.Position; +import org.eclipse.che.ide.api.editor.text.TextPosition; +import org.eclipse.che.ide.api.editor.texteditor.TextEditor; +import org.eclipse.che.ide.api.parts.PartStackType; +import org.eclipse.che.ide.api.parts.WorkspaceAgent; +import org.eclipse.che.ide.api.parts.base.BasePresenter; +import org.eclipse.che.ide.api.resources.Project; +import org.eclipse.che.ide.dto.DtoFactory; +import org.eclipse.che.ide.runtime.OperationCanceledException; +import org.eclipse.che.plugin.languageserver.ide.LanguageServerLocalization; +import org.eclipse.che.plugin.languageserver.ide.editor.quickassist.ApplyWorkspaceEditAction; +import org.eclipse.che.plugin.languageserver.ide.rename.RenameView.ActionDelegate; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameChange; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameFile; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameFolder; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameProject; +import org.eclipse.che.plugin.languageserver.ide.service.TextDocumentServiceClient; +import org.eclipse.lsp4j.RenameParams; +import org.eclipse.lsp4j.TextDocumentEdit; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.slf4j.Logger; + +/** Main controller for rename feature, calls rename and shows rename edits */ +@Singleton +public class RenamePresenter extends BasePresenter implements ActionDelegate { + + private static final Logger LOG = getLogger(RenamePresenter.class); + + private final LanguageServerLocalization localization; + private final TextDocumentServiceClient client; + private final DtoFactory dtoFactory; + private final Provider renameInputBoxProvider; + private final RenameView view; + private final WorkspaceAgent workspaceAgent; + private final AppContext appContext; + private final ApplyWorkspaceEditAction workspaceEditAction; + private final Provider renameWindow; + private final Map, List> projectCache = + new HashMap<>(); + private RenameInputBox inputBox; + private boolean showPreview; + private TextEditor textEditor; + + @Inject + public RenamePresenter( + LanguageServerLocalization localization, + TextDocumentServiceClient client, + DtoFactory dtoFactory, + Provider renameInputBoxProvider, + RenameView view, + WorkspaceAgent workspaceAgent, + AppContext appContext, + ApplyWorkspaceEditAction workspaceEditAction, + Provider renameWindow) { + this.localization = localization; + this.client = client; + this.dtoFactory = dtoFactory; + this.renameInputBoxProvider = renameInputBoxProvider; + this.view = view; + this.workspaceAgent = workspaceAgent; + this.appContext = appContext; + this.workspaceEditAction = workspaceEditAction; + this.renameWindow = renameWindow; + view.setDelegate(this); + } + + @Override + public String getTitle() { + return localization.renameViewTitle(); + } + + @Override + public IsWidget getView() { + return view; + } + + @Override + public String getTitleToolTip() { + return localization.renameViewTooltip(); + } + + @Override + public void go(AcceptsOneWidget container) { + container.setWidget(view); + } + + public void rename(TextEditor editor) { + textEditor = editor; + TextPosition cursorPosition = textEditor.getCursorPosition(); + int cursorOffset = editor.getCursorOffset(); + Position wordAtOffset = editor.getWordAtOffset(cursorOffset); + if (wordAtOffset == null) { + LOG.debug("Can't find word to rename"); + return; + } + PixelCoordinates pixelCoordinates = + editor.getPositionConverter().offsetToPixel(wordAtOffset.offset); + String oldName = editor.getDocument().getContentRange(wordAtOffset.offset, wordAtOffset.length); + + if (inputBox != null) { + showWindow(cursorPosition, editor, oldName); + return; + } + projectCache.clear(); + + inputBox = renameInputBoxProvider.get(); + inputBox + .setPositionAndShow( + pixelCoordinates.getX(), + pixelCoordinates.getY(), + oldName, + () -> showWindow(cursorPosition, editor, oldName)) + .then( + newName -> { + editor.setFocus(); + inputBox = null; + if (!oldName.equals(newName)) { + callRename(newName, cursorPosition, editor); + } + }) + .catchError( + err -> { + editor.setFocus(); + inputBox = null; + if (!(err.getCause() instanceof OperationCanceledException)) { + LOG.error(err.getMessage()); + } + }); + } + + private void showWindow(TextPosition cursorPosition, TextEditor editor, String oldName) { + String value = inputBox.getInputValue(); + inputBox.hide(false); + inputBox = null; + + showPreview = false; + RenameDialog renameDialog = this.renameWindow.get(); + renameDialog.show( + value, + oldName, + newName -> { + renameDialog.closeDialog(); + callRename(newName, cursorPosition, editor); + }, + newName -> { + showPreview = true; + renameDialog.closeDialog(); + callRename(newName, cursorPosition, editor); + }, + () -> { + renameDialog.closeDialog(); + editor.setFocus(); + }); + } + + private void callRename(String newName, TextPosition cursorPosition, TextEditor editor) { + RenameParams dto = dtoFactory.createDto(RenameParams.class); + + TextDocumentIdentifier identifier = dtoFactory.createDto(TextDocumentIdentifier.class); + identifier.setUri(editor.getEditorInput().getFile().getLocation().toString()); + + dto.setNewName(newName); + dto.setTextDocument(identifier); + + org.eclipse.lsp4j.Position position = dtoFactory.createDto(org.eclipse.lsp4j.Position.class); + position.setCharacter(cursorPosition.getCharacter()); + position.setLine(cursorPosition.getLine()); + dto.setPosition(position); + client + .rename(dto) + .then(this::handleRename) + .catchError( + arg -> { + LOG.error(arg.getMessage()); + }); + } + + private void handleRename(RenameResult renameResult) { + if (!renameResult.getRenameResults().isEmpty()) { + ExtendedWorkspaceEdit workspaceEdit = + renameResult + .getRenameResults() + .get(renameResult.getRenameResults().keySet().iterator().next()); + if (renameResult.getRenameResults().size() == 1 + && workspaceEdit.getDocumentChanges().size() == 1 + && !showPreview) { + List renameProjects = convert(workspaceEdit.getDocumentChanges()); + applyRename(renameProjects); + } else { + workspaceAgent.openPart(this, PartStackType.INFORMATION); + workspaceAgent.setActivePart(this); + view.showRenameResult(renameResult.getRenameResults()); + } + } + } + + @Override + public List convert(List documentChanges) { + if (projectCache.containsKey(documentChanges)) { + return projectCache.get(documentChanges); + } + Project[] projects = appContext.getProjects(); + Map> projectMap = new HashMap<>(); + for (ExtendedTextDocumentEdit documentChange : documentChanges) { + String filePath = documentChange.getTextDocument().getUri(); + Project project = getProject(filePath, projects); + if (project == null) { + continue; + } + if (!projectMap.containsKey(project)) { + projectMap.put(project, new ArrayList<>()); + } + projectMap.get(project).add(documentChange); + } + + List result = new ArrayList<>(); + for (Project project : projectMap.keySet()) { + result.add(new RenameProject(project, getRenameFolders(project, projectMap.get(project)))); + } + + return result; + } + + @Override + public void cancel() { + workspaceAgent.hidePart(this); + workspaceAgent.removePart(this); + textEditor.setFocus(); + } + + @Override + public void applyRename() { + List projects = view.getRenameProjects(); + applyRename(projects); + workspaceAgent.hidePart(this); + workspaceAgent.removePart(this); + textEditor.setFocus(); + } + + private void applyRename(List projects) { + List edits = new ArrayList<>(); + for (RenameProject project : projects) { + edits.addAll(project.getTextDocumentEdits()); + } + workspaceEditAction.applyWorkspaceEdit(new WorkspaceEdit(edits)); + } + + private List getRenameFolders( + Project project, List edits) { + + List result = new ArrayList<>(); + + Map> files = new HashMap<>(); + for (ExtendedTextDocumentEdit edit : edits) { + String uri = edit.getTextDocument().getUri(); + String filePath = uri; + filePath = filePath.substring(project.getPath().length() + 1); + String folderPath = filePath.substring(0, filePath.lastIndexOf('/')); + String fileName = filePath.substring(filePath.lastIndexOf('/') + 1, filePath.length()); + if (!files.containsKey(folderPath)) { + files.put(folderPath, new ArrayList<>()); + } + files + .get(folderPath) + .add(new RenameFile(fileName, uri, getRenameChanges(edit.getEdits(), uri))); + } + + for (String folderPath : files.keySet()) { + result.add(new RenameFolder(folderPath, files.get(folderPath))); + } + + return result; + } + + private List getRenameChanges(List edits, String filePath) { + return edits + .stream() + .map(edit -> new RenameChange(edit, filePath)) + .collect(Collectors.toList()); + } + + private Project getProject(String filePath, Project[] projects) { + Project selectedProject = null; + for (Project project : projects) { + if (filePath.startsWith(project.getPath())) { + if (selectedProject == null) { + selectedProject = project; + } else if (selectedProject.getPath().length() < project.getPath().length()) { + selectedProject = project; + } + } + } + return selectedProject; + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameView.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameView.java new file mode 100644 index 00000000000..491b6e24fe6 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameView.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename; + +import com.google.inject.ImplementedBy; +import java.util.List; +import java.util.Map; +import org.eclipse.che.api.languageserver.shared.model.ExtendedTextDocumentEdit; +import org.eclipse.che.api.languageserver.shared.model.ExtendedWorkspaceEdit; +import org.eclipse.che.ide.api.mvp.View; +import org.eclipse.che.ide.api.parts.base.BaseActionDelegate; +import org.eclipse.che.plugin.languageserver.ide.rename.RenameView.ActionDelegate; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameProject; + +/** View for rename edits tree */ +@ImplementedBy(RenameViewImpl.class) +public interface RenameView extends View { + + void showRenameResult(Map results); + + List getRenameProjects(); + + interface ActionDelegate extends BaseActionDelegate { + + List convert(List documentChanges); + + void cancel(); + + void applyRename(); + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameViewImpl.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameViewImpl.java new file mode 100644 index 00000000000..89efdfd4b9d --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/RenameViewImpl.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.DockLayoutPanel; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.SplitLayoutPanel; +import elemental.dom.Element; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.inject.Inject; +import org.eclipse.che.api.languageserver.shared.model.ExtendedTextDocumentEdit; +import org.eclipse.che.api.languageserver.shared.model.ExtendedWorkspaceEdit; +import org.eclipse.che.ide.Resources; +import org.eclipse.che.ide.api.autocomplete.AutoCompleteResources; +import org.eclipse.che.ide.api.data.tree.Node; +import org.eclipse.che.ide.api.parts.PartStackUIResources; +import org.eclipse.che.ide.api.parts.base.BaseView; +import org.eclipse.che.ide.api.theme.Style; +import org.eclipse.che.ide.ui.SplitterFancyUtil; +import org.eclipse.che.ide.ui.list.SimpleList; +import org.eclipse.che.ide.ui.list.SimpleList.ListEventDelegate; +import org.eclipse.che.ide.ui.list.SimpleList.ListItemRenderer; +import org.eclipse.che.ide.ui.smartTree.NodeLoader; +import org.eclipse.che.ide.ui.smartTree.NodeStorage; +import org.eclipse.che.ide.ui.smartTree.NodeUniqueKeyProvider; +import org.eclipse.che.ide.ui.smartTree.Tree; +import org.eclipse.che.ide.util.dom.Elements; +import org.eclipse.che.plugin.languageserver.ide.LanguageServerLocalization; +import org.eclipse.che.plugin.languageserver.ide.rename.RenameView.ActionDelegate; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameProject; +import org.eclipse.che.plugin.languageserver.ide.rename.node.ProjectNode; +import org.eclipse.che.plugin.languageserver.ide.rename.node.RenameNodeFactory; + +/** + * Implementation of the rename view. Presents rename edits as tree of changes: Project-> Folder-> + * File-> Edits + */ +public class RenameViewImpl extends BaseView implements RenameView { + + private final Tree tree; + private final RenameNodeFactory nodeFactory; + private SplitLayoutPanel splitLayoutPanel; + private DockLayoutPanel treeDock; + + private FlowPanel editPanel; + + private SimpleList lsList; + private Map editMap; + private String currentLSId; + + @Inject + public RenameViewImpl( + PartStackUIResources resources, + SplitterFancyUtil splitterFancyUtil, + Resources coreRes, + AutoCompleteResources autoCompleteResources, + RenameNodeFactory nodeFactory, + LanguageServerLocalization localization) { + super(resources); + this.nodeFactory = nodeFactory; + splitLayoutPanel = new SplitLayoutPanel(1); + splitLayoutPanel.setSize("100%", "100%"); + editPanel = new FlowPanel(); + splitLayoutPanel.addWest(editPanel, 185); + setContentWidget(splitLayoutPanel); + + Element itemHolder = Elements.createDivElement(); + itemHolder.setClassName(autoCompleteResources.autocompleteComponentCss().items()); + editPanel.getElement().appendChild(((com.google.gwt.dom.client.Element) itemHolder)); + + lsList = + SimpleList.create( + editPanel.getElement().cast(), + editPanel.getElement().cast(), + itemHolder, + coreRes.defaultSimpleListCss(), + new ListItemRenderer() { + @Override + public void render(Element listItemBase, String itemData) { + listItemBase.setInnerText(itemData); + } + }, + new ListEventDelegate() { + @Override + public void onListItemClicked(Element listItemBase, String itemData) { + selectedEditSet(itemData); + } + + @Override + public void onListItemDoubleClicked(Element listItemBase, String itemData) {} + }); + splitterFancyUtil.tuneSplitter(splitLayoutPanel); + splitLayoutPanel.setWidgetHidden(editPanel, true); + + treeDock = new DockLayoutPanel(Unit.PX); + FlowPanel buttonPanel = new FlowPanel(); + treeDock.addSouth(buttonPanel, 25); + splitLayoutPanel.add(treeDock); + + Button refactorButton = new Button(); + refactorButton.addClickHandler(event -> delegate.applyRename()); + refactorButton.setText(localization.renameViewDoRenameLabel()); + refactorButton.getElement().getStyle().setMarginLeft(1, Unit.EM); + refactorButton + .getElement() + .getStyle() + .setBackgroundColor(Style.theme.getPrimaryButtonBackground()); + + Button cancelButton = new Button(); + cancelButton.addClickHandler(event -> delegate.cancel()); + cancelButton.setText(localization.renameViewCancelLabel()); + cancelButton.getElement().getStyle().setMarginLeft(1, Unit.EM); + + buttonPanel.add(refactorButton); + buttonPanel.add(cancelButton); + + NodeStorage storage = + new NodeStorage((NodeUniqueKeyProvider) item -> String.valueOf(item.hashCode())); + NodeLoader loader = new NodeLoader(Collections.emptySet()); + tree = new Tree(storage, loader); + treeDock.add(tree); + } + + private void selectedEditSet(String editSetId) { + if (editSetId.equals(currentLSId)) { + return; + } + if (editMap.containsKey(editSetId)) { + ExtendedWorkspaceEdit workspaceEdit = editMap.get(editSetId); + tree.getNodeStorage().clear(); + List documentChanges = workspaceEdit.getDocumentChanges(); + List projects = delegate.convert(documentChanges); + for (RenameProject project : projects) { + tree.getNodeStorage().add(nodeFactory.create(project)); + } + tree.expandAll(); + } + } + + @Override + public void showRenameResult(Map editMap) { + if (editMap.size() == 1) { + splitLayoutPanel.setWidgetHidden(editPanel, true); + } else { + splitLayoutPanel.setWidgetHidden(editPanel, false); + } + this.editMap = editMap; + currentLSId = null; + lsList.render(new ArrayList<>(editMap.keySet())); + lsList.getSelectionModel().selectNext(); + selectedEditSet(lsList.getSelectionModel().getSelectedItem()); + } + + @Override + public List getRenameProjects() { + List result = new ArrayList<>(); + List rootNodes = tree.getRootNodes(); + for (Node node : rootNodes) { + if (node instanceof ProjectNode) { + result.add(((ProjectNode) node).getProject()); + } + } + return result; + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameChange.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameChange.java new file mode 100644 index 00000000000..33551ce8370 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameChange.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename.model; + +import org.eclipse.che.api.languageserver.shared.model.ExtendedTextEdit; + +/** Holds {@link ExtendedTextEdit} and file path */ +public class RenameChange { + + private final ExtendedTextEdit textEdit; + private final String filePath; + + public RenameChange(ExtendedTextEdit textEdit, String filePath) { + this.textEdit = textEdit; + this.filePath = filePath; + } + + public ExtendedTextEdit getTextEdit() { + return textEdit; + } + + public String getFilePath() { + return filePath; + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameFile.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameFile.java new file mode 100644 index 00000000000..0668ea6e4c1 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameFile.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename.model; + +import java.util.ArrayList; +import java.util.List; +import org.eclipse.lsp4j.TextDocumentEdit; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; + +/** + * Synthetic object for tree, represent file where edits should apply, hods file name and list of + * the edits + */ +public class RenameFile { + + private final String fileName; + private final String filePath; + private final List changes; + + public RenameFile(String fileName, String filePath, List changes) { + this.fileName = fileName; + this.filePath = filePath; + this.changes = changes; + } + + public String getFileName() { + return fileName; + } + + public List getChanges() { + return changes; + } + + TextDocumentEdit getTextDocumentEdit() { + VersionedTextDocumentIdentifier identifier = new VersionedTextDocumentIdentifier(-1); + identifier.setUri(filePath); + List edits = new ArrayList<>(); + for (RenameChange change : changes) { + edits.add(new TextEdit(change.getTextEdit().getRange(), change.getTextEdit().getNewText())); + } + return new TextDocumentEdit(identifier, edits); + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameFolder.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameFolder.java new file mode 100644 index 00000000000..a5f120ffdc1 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameFolder.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename.model; + +import static java.util.stream.Collectors.toList; + +import java.util.List; +import org.eclipse.lsp4j.TextDocumentEdit; + +/** Synthetic object for tree, represent folder with files. */ +public class RenameFolder { + private final String name; + private final List files; + + public RenameFolder(String name, List files) { + this.name = name; + this.files = files; + } + + public String getName() { + return name; + } + + public List getFiles() { + return files; + } + + public List getTextDocumentEdit() { + return files.stream().map(RenameFile::getTextDocumentEdit).collect(toList()); + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameProject.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameProject.java new file mode 100644 index 00000000000..1b8b803f318 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/model/RenameProject.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename.model; + +import static java.util.stream.Collectors.toList; + +import java.util.Collection; +import java.util.List; +import org.eclipse.che.ide.api.resources.Project; +import org.eclipse.lsp4j.TextDocumentEdit; + +/** Synthetic object for tree, represent project with folders. */ +public class RenameProject { + private final Project project; + private final List folders; + + public RenameProject(Project project, List folders) { + this.project = project; + this.folders = folders; + } + + public String getName() { + return project.getName(); + } + + public List getFolders() { + return folders; + } + + public List getTextDocumentEdits() { + return folders + .stream() + .map(RenameFolder::getTextDocumentEdit) + .flatMap(Collection::stream) + .collect(toList()); + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/ChangeNode.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/ChangeNode.java new file mode 100644 index 00000000000..863386745a3 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/ChangeNode.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename.node; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; +import com.google.inject.assistedinject.Assisted; +import elemental.html.SpanElement; +import java.util.List; +import javax.inject.Inject; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.api.data.tree.HasAction; +import org.eclipse.che.ide.api.data.tree.Node; +import org.eclipse.che.ide.api.editor.EditorAgent; +import org.eclipse.che.ide.api.editor.EditorPartPresenter; +import org.eclipse.che.ide.api.editor.OpenEditorCallbackImpl; +import org.eclipse.che.ide.api.editor.text.TextPosition; +import org.eclipse.che.ide.api.editor.text.TextRange; +import org.eclipse.che.ide.api.editor.texteditor.TextEditor; +import org.eclipse.che.ide.resource.Path; +import org.eclipse.che.ide.ui.smartTree.TreeStyles; +import org.eclipse.che.ide.ui.smartTree.presentation.AbstractPresentationNode; +import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; +import org.eclipse.che.ide.util.dom.Elements; +import org.eclipse.che.plugin.languageserver.ide.LanguageServerResources; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameChange; +import org.eclipse.lsp4j.Range; + +/** Tree node, represent edit in file */ +public class ChangeNode extends AbstractPresentationNode implements HasAction { + + private final TreeStyles styles; + private final LanguageServerResources resources; + private final EditorAgent editorAgent; + private final AppContext appContext; + private final RenameChange change; + + @Inject + public ChangeNode( + TreeStyles styles, + LanguageServerResources resources, + EditorAgent editorAgent, + AppContext appContext, + @Assisted RenameChange change) { + this.styles = styles; + this.resources = resources; + this.editorAgent = editorAgent; + this.appContext = appContext; + this.change = change; + } + + @Override + protected Promise> getChildrenImpl() { + return null; + } + + @Override + public String getName() { + return change.getTextEdit().getLineText(); + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public void updatePresentation(NodePresentation presentation) { + SpanElement spanElement = + Elements.createSpanElement(styles.styles().presentableTextContainer()); + + SpanElement lineNumberElement = Elements.createSpanElement(); + lineNumberElement.setInnerHTML( + String.valueOf(change.getTextEdit().getRange().getStart().getLine() + 1) + + ":   "); + spanElement.appendChild(lineNumberElement); + + SpanElement textElement = Elements.createSpanElement(); + + String matchedLine = change.getTextEdit().getLineText(); + int startOffset = change.getTextEdit().getInLineStart(); + int endOffset = change.getTextEdit().getInLineEnd(); + if (matchedLine != null) { + String startLine = matchedLine.substring(0, startOffset); + textElement.appendChild(Elements.createTextNode(startLine)); + SpanElement highlightElement = + Elements.createSpanElement(resources.quickOpenListCss().searchMatch()); + highlightElement.setInnerText(matchedLine.substring(startOffset, endOffset)); + textElement.appendChild(highlightElement); + + textElement.appendChild(Elements.createTextNode(matchedLine.substring(endOffset))); + } else { + textElement.appendChild(Elements.createTextNode("Can't find sources")); + } + spanElement.appendChild(textElement); + + presentation.setUserElement((Element) spanElement); + } + + @Override + public void actionPerformed() { + final EditorPartPresenter editorPartPresenter = + editorAgent.getOpenedEditor(Path.valueOf(change.getFilePath())); + if (editorPartPresenter != null) { + selectRange(editorPartPresenter); + Scheduler.get().scheduleDeferred(() -> editorAgent.activateEditor(editorPartPresenter)); + return; + } + + appContext + .getWorkspaceRoot() + .getFile(change.getFilePath()) + .then( + file -> { + if (file.isPresent()) { + editorAgent.openEditor( + file.get(), + new OpenEditorCallbackImpl() { + @Override + public void onEditorOpened(EditorPartPresenter editor) { + selectRange(editor); + } + }); + } + }); + } + + private void selectRange(EditorPartPresenter editor) { + if (editor instanceof TextEditor) { + Range range = change.getTextEdit().getRange(); + ((TextEditor) editor) + .getDocument() + .setSelectedRange( + new TextRange( + new TextPosition(range.getStart().getLine(), range.getStart().getCharacter()), + new TextPosition(range.getEnd().getLine(), range.getEnd().getCharacter())), + true); + } + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/FileNode.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/FileNode.java new file mode 100644 index 00000000000..5b68864b8c8 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/FileNode.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename.node; + +import static java.util.stream.Collectors.toList; + +import com.google.inject.assistedinject.Assisted; +import java.util.List; +import javax.inject.Inject; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.api.promises.client.PromiseProvider; +import org.eclipse.che.ide.api.data.tree.Node; +import org.eclipse.che.ide.project.shared.NodesResources; +import org.eclipse.che.ide.ui.smartTree.presentation.AbstractPresentationNode; +import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameFile; + +/** Tree node, represent {@link RenameFile} */ +public class FileNode extends AbstractPresentationNode { + + private final NodesResources resources; + private final PromiseProvider promiseProvider; + private final RenameNodeFactory factory; + private final RenameFile renameFile; + + private final List child; + + @Inject + public FileNode( + NodesResources resources, + PromiseProvider promiseProvider, + RenameNodeFactory factory, + @Assisted RenameFile renameFile) { + this.resources = resources; + this.promiseProvider = promiseProvider; + this.factory = factory; + this.renameFile = renameFile; + child = renameFile.getChanges().stream().map(factory::create).collect(toList()); + } + + @Override + protected Promise> getChildrenImpl() { + return promiseProvider.resolve(child); + } + + @Override + public String getName() { + return renameFile.getFileName(); + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public void updatePresentation(NodePresentation presentation) { + presentation.setPresentableText(renameFile.getFileName()); + presentation.setPresentableIcon(resources.file()); + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/FolderNode.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/FolderNode.java new file mode 100644 index 00000000000..361644817e2 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/FolderNode.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename.node; + +import static java.util.stream.Collectors.toList; + +import com.google.inject.assistedinject.Assisted; +import java.util.List; +import javax.inject.Inject; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.api.promises.client.PromiseProvider; +import org.eclipse.che.ide.api.data.tree.Node; +import org.eclipse.che.ide.project.shared.NodesResources; +import org.eclipse.che.ide.ui.smartTree.presentation.AbstractPresentationNode; +import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameFolder; + +/** Tree node, represent {@link RenameFolder} */ +public class FolderNode extends AbstractPresentationNode { + + private final NodesResources resources; + private final PromiseProvider promiseProvider; + private final RenameFolder renameFolder; + private final List child; + + @Inject + public FolderNode( + NodesResources resources, + PromiseProvider promiseProvider, + RenameNodeFactory factory, + @Assisted RenameFolder renameFolder) { + this.resources = resources; + this.promiseProvider = promiseProvider; + this.renameFolder = renameFolder; + child = renameFolder.getFiles().stream().map(factory::create).collect(toList()); + } + + @Override + protected Promise> getChildrenImpl() { + return promiseProvider.resolve(child); + } + + @Override + public String getName() { + return renameFolder.getName(); + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public void updatePresentation(NodePresentation presentation) { + presentation.setPresentableText(renameFolder.getName()); + presentation.setPresentableIcon(resources.simpleFolder()); + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/ProjectNode.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/ProjectNode.java new file mode 100644 index 00000000000..370d81528e5 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/ProjectNode.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename.node; + +import static java.util.stream.Collectors.toList; + +import com.google.inject.assistedinject.Assisted; +import java.util.List; +import javax.inject.Inject; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.api.promises.client.PromiseProvider; +import org.eclipse.che.ide.api.data.tree.Node; +import org.eclipse.che.ide.project.shared.NodesResources; +import org.eclipse.che.ide.ui.smartTree.presentation.AbstractPresentationNode; +import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameProject; + +/** Tree node, represent {@link RenameProject} */ +public class ProjectNode extends AbstractPresentationNode { + + private final NodesResources resources; + private final PromiseProvider promiseProvider; + private final RenameProject project; + + private final List children; + + @Inject + public ProjectNode( + NodesResources resources, + PromiseProvider promiseProvider, + RenameNodeFactory factory, + @Assisted RenameProject project) { + this.resources = resources; + this.promiseProvider = promiseProvider; + this.project = project; + children = project.getFolders().stream().map(factory::create).collect(toList()); + } + + @Override + protected Promise> getChildrenImpl() { + return promiseProvider.resolve(children); + } + + @Override + public String getName() { + return project.getName(); + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public void updatePresentation(NodePresentation presentation) { + presentation.setPresentableText(project.getName()); + presentation.setPresentableIcon(resources.projectFolder()); + } + + public RenameProject getProject() { + return project; + } +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/RenameNodeFactory.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/RenameNodeFactory.java new file mode 100644 index 00000000000..16db88d511b --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/rename/node/RenameNodeFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.plugin.languageserver.ide.rename.node; + +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameChange; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameFile; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameFolder; +import org.eclipse.che.plugin.languageserver.ide.rename.model.RenameProject; + +/** Factory for all rename nodes */ +public interface RenameNodeFactory { + + ProjectNode create(RenameProject project); + + FolderNode create(RenameFolder folder); + + FileNode create(RenameFile file); + + ChangeNode create(RenameChange change); +} diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/TextDocumentServiceClient.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/TextDocumentServiceClient.java index 2e34a6a23d5..6607739f5a7 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/TextDocumentServiceClient.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/TextDocumentServiceClient.java @@ -18,6 +18,7 @@ import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; import org.eclipse.che.api.languageserver.shared.model.ExtendedCompletionItem; import org.eclipse.che.api.languageserver.shared.model.ExtendedCompletionList; +import org.eclipse.che.api.languageserver.shared.model.RenameResult; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.api.promises.client.js.Promises; @@ -36,6 +37,7 @@ import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.ReferenceParams; +import org.eclipse.lsp4j.RenameParams; import org.eclipse.lsp4j.SignatureHelp; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.TextDocumentPositionParams; @@ -209,6 +211,15 @@ public Promise> documentHighlight(TextDocumentPositionPa return transmitDtoAndReceiveDtoList( params, "textDocument/documentHighlight", DocumentHighlight.class); } + /** + * GWT client implementation of {@link TextDocumentService#rename(RenameParams)} + * + * @param params + * @return a {@link Promise} of a rename result object which contains all workspace edits. + */ + public Promise rename(RenameParams params) { + return transmitDtoAndReceiveDto(params, "textDocument/rename", RenameResult.class); + } public Promise> codeAction(CodeActionParams params) { return transmitDtoAndReceiveDtoList(params, "textDocument/codeAction", Command.class); diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/resources/org/eclipse/che/plugin/languageserver/ide/LanguageServerLocalization.properties b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/resources/org/eclipse/che/plugin/languageserver/ide/LanguageServerLocalization.properties index f3ad32da64e..73ecb001af3 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/resources/org/eclipse/che/plugin/languageserver/ide/LanguageServerLocalization.properties +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/resources/org/eclipse/che/plugin/languageserver/ide/LanguageServerLocalization.properties @@ -19,6 +19,7 @@ apply.workspace.edit.action.notification.undoing=Undoing changes apply.workspace.edit.action.notification.undone=Changes undone apply.workspace.edit.action.notification.undo.failed=Undo failed apply.workspace.edit.action.notification.modifying=Modifying file: {0} +rename.action.title = Rename # LIST go.to.symbol.symbols = symbols ({0}) @@ -32,3 +33,11 @@ variable.type = variables ({0}) constructor.type = constructors ({0}) #NOTIFICATIONS + +### RENAME VIEW +rename.view.title= Rename +rename.view.tooltip = Rename change preview +rename.dialog.label = New name: +rename.dialog.preview.label = Preview +rename.view.cancel.label = Cancel +rename.view.do.rename.label = Do Rename diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/test/java/org/eclipse/che/ide/editor/orion/client/WordDetectionUtilTest.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/test/java/org/eclipse/che/ide/editor/orion/client/WordDetectionUtilTest.java new file mode 100644 index 00000000000..e7e3e2c7c38 --- /dev/null +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/test/java/org/eclipse/che/ide/editor/orion/client/WordDetectionUtilTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.ide.editor.orion.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + +import com.google.gwtmockito.GwtMockitoTestRunner; +import org.eclipse.che.ide.api.editor.document.Document; +import org.eclipse.che.ide.api.editor.text.Position; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +/** */ +@RunWith(GwtMockitoTestRunner.class) +public class WordDetectionUtilTest { + + @Mock private Document document; + private WordDetectionUtil detectionUtil; + + @Before + public void setUp() throws Exception { + detectionUtil = new WordDetectionUtil(); + } + + @Test + public void testGetWordAtOffset() throws Exception { + when(document.getLineAtOffset(5)).thenReturn(1); + when(document.getLineContent(1)).thenReturn("foo bar foo "); + when(document.getLineStart(1)).thenReturn(0); + + Position wordAtOffset = detectionUtil.getWordAtOffset(document, 5); + + assertNotNull(wordAtOffset); + assertEquals(4, wordAtOffset.offset); + assertEquals(3, wordAtOffset.length); + } + + @Test + public void testGetWordAtOffsetMultiLine() throws Exception { + when(document.getLineAtOffset(16)).thenReturn(2); + when(document.getLineContent(2)).thenReturn("foo bar !fooBar' "); + when(document.getLineStart(2)).thenReturn(5); + + Position wordAtOffset = detectionUtil.getWordAtOffset(document, 16); + + assertNotNull(wordAtOffset); + assertEquals(14, wordAtOffset.offset); + assertEquals(6, wordAtOffset.length); + } +} diff --git a/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedTextDocumentEdit.java b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedTextDocumentEdit.java new file mode 100644 index 00000000000..3ff84549a2c --- /dev/null +++ b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedTextDocumentEdit.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.api.languageserver.shared.model; + +import java.util.List; +import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; + +/** */ +public class ExtendedTextDocumentEdit { + private VersionedTextDocumentIdentifier textDocument; + private List edits; + + public ExtendedTextDocumentEdit() {} + + public ExtendedTextDocumentEdit( + VersionedTextDocumentIdentifier textDocument, List edits) { + this.textDocument = textDocument; + this.edits = edits; + } + + public VersionedTextDocumentIdentifier getTextDocument() { + return textDocument; + } + + public void setTextDocument(VersionedTextDocumentIdentifier textDocument) { + this.textDocument = textDocument; + } + + public List getEdits() { + return edits; + } + + public void setEdits(List edits) { + this.edits = edits; + } +} diff --git a/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedTextEdit.java b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedTextEdit.java new file mode 100644 index 00000000000..ac1c81efd69 --- /dev/null +++ b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedTextEdit.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.api.languageserver.shared.model; + +import org.eclipse.lsp4j.Range; + +/** */ +public class ExtendedTextEdit { + private Range range; + private String newText; + private String lineText; + private int inLineStart; + private int inLineEnd; + + public ExtendedTextEdit() {} + + public ExtendedTextEdit(Range range, String newText) { + this.range = range; + this.newText = newText; + } + + public ExtendedTextEdit( + Range range, String newText, String lineText, int inLineStart, int inLineEnd) { + this.range = range; + this.newText = newText; + this.lineText = lineText; + this.inLineStart = inLineStart; + this.inLineEnd = inLineEnd; + } + + public Range getRange() { + return range; + } + + public void setRange(Range range) { + this.range = range; + } + + public String getNewText() { + return newText; + } + + public void setNewText(String newText) { + this.newText = newText; + } + + public String getLineText() { + return lineText; + } + + public void setLineText(String lineText) { + this.lineText = lineText; + } + + public int getInLineStart() { + return inLineStart; + } + + public void setInLineStart(int inLineStart) { + this.inLineStart = inLineStart; + } + + public int getInLineEnd() { + return inLineEnd; + } + + public void setInLineEnd(int inLineEnd) { + this.inLineEnd = inLineEnd; + } +} diff --git a/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedWorkspaceEdit.java b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedWorkspaceEdit.java new file mode 100644 index 00000000000..9690652eef8 --- /dev/null +++ b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedWorkspaceEdit.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.api.languageserver.shared.model; + +import java.util.List; + +/** */ +public class ExtendedWorkspaceEdit { + private List documentChanges; + + public ExtendedWorkspaceEdit() {} + + public ExtendedWorkspaceEdit(List documentChanges) { + this.documentChanges = documentChanges; + } + + public List getDocumentChanges() { + return documentChanges; + } + + public void setDocumentChanges(List documentChanges) { + this.documentChanges = documentChanges; + } +} diff --git a/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/RenameResult.java b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/RenameResult.java new file mode 100644 index 00000000000..376e5910392 --- /dev/null +++ b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/RenameResult.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012-2017 Red Hat, 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: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.api.languageserver.shared.model; + +import java.util.Map; + +/** + * Result of the LS 'rename' call. Contains results from all LS bind to the file type, client must + * chose only one. + */ +public class RenameResult { + + private Map renameResults; + + public RenameResult() {} + + public RenameResult(Map renameResults) { + this.renameResults = renameResults; + } + + public Map getRenameResults() { + return renameResults; + } + + public void setRenameResults(Map renameResults) { + this.renameResults = renameResults; + } +} diff --git a/wsagent/che-core-api-languageserver/pom.xml b/wsagent/che-core-api-languageserver/pom.xml index f047b89bf48..eb1eb67c701 100644 --- a/wsagent/che-core-api-languageserver/pom.xml +++ b/wsagent/che-core-api-languageserver/pom.xml @@ -85,6 +85,10 @@ org.eclipse.lsp4j org.eclipse.lsp4j.jsonrpc + + org.eclipse.text + org.eclipse.text + org.slf4j slf4j-api diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageServiceUtils.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageServiceUtils.java index f4714d25fa2..69181d4cd06 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageServiceUtils.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageServiceUtils.java @@ -12,6 +12,8 @@ /** Language service service utilities */ public class LanguageServiceUtils { + + private static final String PROJECTS = "/projects"; private static final String FILE_PROJECTS = "file:///projects"; public static String prefixURI(String relativePath) { @@ -33,4 +35,12 @@ public static boolean truish(Boolean b) { public static boolean isProjectUri(String path) { return path.startsWith(FILE_PROJECTS); } + + public static boolean isStartWithProject(String path) { + return path.startsWith(PROJECTS); + } + + public static String prefixProject(String path) { + return path.startsWith(PROJECTS) ? path : PROJECTS + path; + } } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentService.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentService.java index 66bdf0d9900..909bc5c6de9 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentService.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentService.java @@ -10,15 +10,24 @@ */ package org.eclipse.che.api.languageserver.service; +import static org.eclipse.che.api.languageserver.service.LanguageServiceUtils.isStartWithProject; +import static org.eclipse.che.api.languageserver.service.LanguageServiceUtils.prefixProject; import static org.eclipse.che.api.languageserver.service.LanguageServiceUtils.prefixURI; import static org.eclipse.che.api.languageserver.service.LanguageServiceUtils.removePrefixUri; +import static org.eclipse.che.api.languageserver.service.LanguageServiceUtils.removeUriScheme; import com.google.inject.Singleton; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -40,12 +49,20 @@ import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.ExtendedCompletionListDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.HoverDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.LocationDto; +import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.RenameResultDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.SignatureHelpDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.SymbolInformationDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.TextEditDto; import org.eclipse.che.api.languageserver.shared.model.ExtendedCompletionItem; +import org.eclipse.che.api.languageserver.shared.model.ExtendedTextDocumentEdit; +import org.eclipse.che.api.languageserver.shared.model.ExtendedTextEdit; +import org.eclipse.che.api.languageserver.shared.model.ExtendedWorkspaceEdit; +import org.eclipse.che.api.languageserver.shared.model.RenameResult; import org.eclipse.che.api.languageserver.util.LSOperation; import org.eclipse.che.api.languageserver.util.OperationUtil; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IRegion; import org.eclipse.lsp4j.CodeActionParams; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.CompletionItem; @@ -62,10 +79,15 @@ import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.ReferenceParams; +import org.eclipse.lsp4j.RenameParams; import org.eclipse.lsp4j.SignatureHelp; import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.TextDocumentEdit; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextDocumentPositionParams; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; +import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -134,6 +156,8 @@ public void configureMethods() { SignatureHelpDto.class, this::signatureHelp); + dtoToDto("rename", RenameParams.class, RenameResultDto.class, this::rename); + dtoToNothing("didChange", DidChangeTextDocumentParams.class, this::didChange); dtoToNothing("didClose", DidCloseTextDocumentParams.class, this::didClose); dtoToNothing("didOpen", DidOpenTextDocumentParams.class, this::didOpen); @@ -776,6 +800,115 @@ public boolean handleResult( } } + private RenameResultDto rename(RenameParams renameParams) { + String uri = prefixURI(renameParams.getTextDocument().getUri()); + renameParams.getTextDocument().setUri(uri); + Map edits = new ConcurrentHashMap<>(); + try { + List servers = + languageServerRegistry + .getApplicableLanguageServers(uri) + .stream() + .flatMap(Collection::stream) + .collect(Collectors.toList()); + LSOperation op = + new LSOperation() { + @Override + public boolean canDo(InitializedLanguageServer server) { + Boolean renameProvider = + server.getInitializeResult().getCapabilities().getRenameProvider(); + return renameProvider != null && renameProvider; + } + + @Override + public CompletableFuture start(InitializedLanguageServer element) { + return element.getServer().getTextDocumentService().rename(renameParams); + } + + @Override + public boolean handleResult(InitializedLanguageServer element, WorkspaceEdit result) { + + addRenameResult(edits, element.getLauncher().getDescription().getId(), result); + return true; + } + }; + OperationUtil.doInParallel(servers, op, TimeUnit.SECONDS.toMillis(30)); + } catch (LanguageServerException e) { + throw new JsonRpcException(-27000, e.getMessage()); + } + return new RenameResultDto(new RenameResult(edits)); + } + + private void addRenameResult( + Map map, String id, WorkspaceEdit workspaceEdit) { + + ExtendedWorkspaceEdit result = new ExtendedWorkspaceEdit(); + List edits = new ArrayList<>(); + if (workspaceEdit.getDocumentChanges() != null) { + for (TextDocumentEdit documentEdit : workspaceEdit.getDocumentChanges()) { + ExtendedTextDocumentEdit edit = new ExtendedTextDocumentEdit(); + edit.setTextDocument(documentEdit.getTextDocument()); + edit.getTextDocument().setUri(removePrefixUri(edit.getTextDocument().getUri())); + edit.setEdits( + convertToExtendedEdit( + documentEdit.getEdits(), removeUriScheme(documentEdit.getTextDocument().getUri()))); + edits.add(edit); + } + } else if (workspaceEdit.getChanges() != null) { + for (Entry> entry : workspaceEdit.getChanges().entrySet()) { + ExtendedTextDocumentEdit edit = new ExtendedTextDocumentEdit(); + VersionedTextDocumentIdentifier documentIdentifier = new VersionedTextDocumentIdentifier(); + documentIdentifier.setVersion(-1); + documentIdentifier.setUri(removePrefixUri(entry.getKey())); + edit.setTextDocument(documentIdentifier); + edit.setEdits(convertToExtendedEdit(entry.getValue(), removeUriScheme(entry.getKey()))); + edits.add(edit); + } + } + + if (!edits.isEmpty()) { + result.setDocumentChanges(edits); + map.put(id, result); + } + } + + private List convertToExtendedEdit(List edits, String filePath) { + try { + // for some reason C# LS sends ws related path, + if (!isStartWithProject(filePath)) { + filePath = prefixProject(filePath); + } + String fileContent = + com.google.common.io.Files.toString(new File(filePath), Charset.defaultCharset()); + Document document = new Document(fileContent); + return edits + .stream() + .map( + textEdit -> { + ExtendedTextEdit result = new ExtendedTextEdit(); + result.setRange(textEdit.getRange()); + result.setNewText(textEdit.getNewText()); + try { + IRegion lineInformation = + document.getLineInformation(textEdit.getRange().getStart().getLine()); + String lineText = + document.get(lineInformation.getOffset(), lineInformation.getLength()); + result.setLineText(lineText); + result.setInLineStart(textEdit.getRange().getStart().getCharacter()); + result.setInLineEnd(textEdit.getRange().getEnd().getCharacter()); + } catch (BadLocationException e) { + LOG.error("Can't read file line", e); + } + + return result; + }) + .collect(Collectors.toList()); + } catch (IOException e) { + LOG.error("Can't read file", e); + } + return Collections.emptyList(); + } + private

void dtoToNothing(String name, Class

pClass, Consumer

consumer) { requestHandler .newConfiguration() diff --git a/wsagent/che-core-git-impl-jgit/pom.xml b/wsagent/che-core-git-impl-jgit/pom.xml index 2eaa523c031..b8aa4d4ae0c 100644 --- a/wsagent/che-core-git-impl-jgit/pom.xml +++ b/wsagent/che-core-git-impl-jgit/pom.xml @@ -60,8 +60,8 @@ javax.inject - javax.validation - validation-api + javax.validation + validation-api javax.ws.rs