Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds editing of start/end tag simultaneously #597

Merged
merged 1 commit into from
Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InitializedParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.eclipse.lsp4j.services.LanguageClient;
Expand Down Expand Up @@ -232,6 +233,13 @@ public CompletableFuture<AutoCloseTagResponse> closeTag(TextDocumentPositionPara
});
}

@Override
public CompletableFuture<Position> matchingTagPosition(TextDocumentPositionParams params) {
return xmlTextDocumentService.computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().getMatchingTagPosition(xmlDocument, params.getPosition(), cancelChecker);
});
}

@Override
public DOMDocument getDocument(String uri) {
ModelTextDocument<DOMDocument> document = xmlTextDocumentService.getDocument(uri);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import java.util.concurrent.CompletableFuture;

import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.jsonrpc.services.JsonSegment;
Expand All @@ -25,6 +26,9 @@ public interface XMLCustomService {

@JsonRequest
CompletableFuture<AutoCloseTagResponse> closeTag(TextDocumentPositionParams params);

@JsonRequest
CompletableFuture<Position> matchingTagPosition(TextDocumentPositionParams params);
}


Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public static void createEndTagInsertCodeAction(Diagnostic diagnostic, Range ran

DOMElement element = (DOMElement) node;
int startOffset = element.getStartTagOpenOffset();
if(startOffset == -1) {
return;
}
Position startPosition = document.positionAt(startOffset);
Position endPosition;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.eclipse.lsp4xml.customservice.AutoCloseTagResponse;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.extensions.contentmodel.settings.XMLValidationSettings;
import org.eclipse.lsp4xml.services.extensions.XMLExtensionsRegistry;
import org.eclipse.lsp4xml.settings.SharedSettings;
Expand Down Expand Up @@ -270,4 +271,8 @@ public AutoCloseTagResponse doAutoClose(DOMDocument xmlDocument, Position positi
}
}

public Position getMatchingTagPosition(DOMDocument xmlDocument, Position position, CancelChecker cancelChecker) {
return XMLPositionUtility.getMatchingTagPosition(xmlDocument, position);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -839,4 +839,38 @@ public static Location toLocation(LocationLink locationLink) {
return new Location(locationLink.getTargetUri(), locationLink.getTargetRange());
}

public static Position getMatchingTagPosition(DOMDocument xmlDocument, Position position) {
try {
int offset = xmlDocument.offsetAt(position);
DOMNode node = xmlDocument.findNodeAt(offset);

if(node.isElement()) {
DOMElement element = (DOMElement) node;

if(!element.hasEndTag() || element.isSelfClosed() || !element.hasStartTag()){
return null;
}
int tagNameLength = element.getTagName().length();
int startTagNameStart = element.getStartTagOpenOffset() + 1;
int startTagNameEnd = startTagNameStart + tagNameLength;
int endTagNameStart = element.getEndTagOpenOffset() + 2;
int endTagNameEnd = endTagNameStart + tagNameLength;

if(offset <= startTagNameEnd && offset >= startTagNameStart) {
int mirroredCursorOffset = endTagNameStart + (offset - startTagNameStart);
return xmlDocument.positionAt(mirroredCursorOffset);
}
else {
if(offset >= endTagNameStart && offset <= endTagNameEnd) {
int mirroredCursorOffset = startTagNameStart + (offset - endTagNameStart);
return xmlDocument.positionAt(mirroredCursorOffset);
}
}
}
} catch (BadLocationException e) {
return null;
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/*******************************************************************************
* Copyright (c) 2019 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/

package org.eclipse.lsp4xml.utils;

import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4xml.commons.BadLocationException;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMParser;
import org.junit.Assert;
import org.junit.Test;

/**
* XMLPositionUtilityTest
*/
public class XMLPositionUtilityTest {

@Test
public void testGetMatchingEndTagPositionMiddle() {
String initialText=
"<Apple>\n" +
" <Or|ange></Orange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <Orange></Or|ange>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingEndTagPositionBeginning() {
String initialText=
"<Apple>\n" +
" <|Orange></Orange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <Orange></|Orange>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingEndTagPositionEnd() {
String initialText=
"<Apple>\n" +
" <Orange|></Orange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <Orange></Orange|>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingEndTagPositionAttributes() {
String initialText=
"<Apple>\n" +
" <Orange| amount=\"1\"></Orange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <Orange amount=\"1\"></Orange|>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingEndTagNoResult() {
String initialText=
"<Apple>\n" +
" |<Orange></Orange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <Orange></Orange>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingEndTagNoResult2() {
String initialText=
"<Apple>\n" +
" <Orange |></Orange>\n" + // Because there is a space
"</Apple>";

String expectedText=
"<Apple>\n" +
" <Orange ></Orange>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingEndTagTextBetween() {
String initialText=
"<Apple>\n" +
" <Orange|>Text Between</Orange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <Orange>Text Between</Orange|>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingEndTagElementBetween() {
String initialText=
"<Apple>\n" +
" <Orange|><Lemon></Lemon></Orange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <Orange><Lemon></Lemon></Orange|>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingStartTagPositionMiddle() {
String initialText=
"<Apple>\n" +
" <Orange></Or|ange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <Or|ange></Orange>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingStartTagPositionBeginning() {
String initialText=
"<Apple>\n" +
" <Orange></|Orange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <|Orange></Orange>\n" +
"</Apple>";


testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingStartTagPositionEnd() {
String initialText=
"<Apple>\n" +
" <Orange></Orange|>\n" +
"</Apple>";


String expectedText=
"<Apple>\n" +
" <Orange|></Orange>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingStartTagPositionAttributes() {
String initialText=
"<Apple>\n" +
" <Orange amount=\"1\"></Orange|>\n" +
"</Apple>";


String expectedText=
"<Apple>\n" +
" <Orange| amount=\"1\"></Orange>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingEndTagPositionAttributesPrefixed() {
String initialText=
"<Apple>\n" +
" <prefix:Orange| amount=\"1\"></prefix:Orange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <prefix:Orange amount=\"1\"></prefix:Orange|>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

@Test
public void testGetMatchingEndTagPositionPrefixed() {
String initialText=
"<Apple>\n" +
" <pref|ix:Orange></prefix:Orange>\n" +
"</Apple>";

String expectedText=
"<Apple>\n" +
" <prefix:Orange></pref|ix:Orange>\n" +
"</Apple>";

testMatchingTagPosition(initialText, expectedText);
}

private static void testMatchingTagPosition(String initialCursorText, String expectedCursorText) {

int offset = initialCursorText.indexOf('|');
initialCursorText = initialCursorText.substring(0, offset) + initialCursorText.substring(offset + 1);
DOMDocument xmlDocument = DOMParser.getInstance().parse(initialCursorText, "testURI", null);
Position initialCursorPosition;
Position newCursorPosition;
int newCursorOffset = -1;
try {
initialCursorPosition = xmlDocument.positionAt(offset);
newCursorPosition = XMLPositionUtility.getMatchingTagPosition(xmlDocument, initialCursorPosition);
if(newCursorPosition != null) { // a result for a matching position was found
newCursorOffset = xmlDocument.offsetAt(newCursorPosition);
}
} catch (BadLocationException e) {
Assert.fail();
return;
}

StringBuffer sBuffer = new StringBuffer(initialCursorText);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use StringBuilder

String actualOutputString;
if(newCursorOffset > -1) {
actualOutputString = sBuffer.insert(newCursorOffset, "|").toString();
}
else { // no matching position was found
actualOutputString = initialCursorText;
}

Assert.assertEquals(expectedCursorText, actualOutputString);
}
}