Skip to content

Commit

Permalink
Guess caret position for full document updates
Browse files Browse the repository at this point in the history
  • Loading branch information
valentjn committed Oct 11, 2020
1 parent e07fed2 commit 8c48cea
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,13 @@ public void setLastCaretChangeInstant(Instant lastCaretChangeInstant) {

@Override
public void setText(String text) {
String oldText = getText();
final String oldText = getText();
super.setText(text);
reinitializeLineStartPosList();
this.checkingResult = null;
this.diagnostics = null;
this.caretPosition = null;
reinitializeLineStartPosList();
this.caretPosition = guessCaretPositionInFullUpdate(oldText);
if (this.caretPosition != null) this.lastCaretChangeInstant = Instant.now();
}

/**
Expand Down Expand Up @@ -241,34 +242,77 @@ public void applyTextChangeEvents(List<TextDocumentContentChangeEvent> textChang
public void applyTextChangeEvent(TextDocumentContentChangeEvent textChangeEvent) {
Range changeRange = textChangeEvent.getRange();
String changeText = textChangeEvent.getText();
int fromPos = -1;
int toPos = -1;
String oldText = getText();
String newText;

if (changeRange != null) {
String text = getText();
int fromPos = convertPosition(changeRange.getStart());
int toPos = ((changeRange.getEnd() != changeRange.getStart())
fromPos = convertPosition(changeRange.getStart());
toPos = ((changeRange.getEnd() != changeRange.getStart())
? convertPosition(changeRange.getEnd()) : fromPos);
text = text.substring(0, fromPos) + changeText + text.substring(toPos);
setText(text);

if ((fromPos == toPos) && (changeText.length() == 1)) {
this.caretPosition = convertPosition(toPos + 1);
this.lastCaretChangeInstant = Instant.now();
} else if ((fromPos == toPos - 1) && changeText.isEmpty()) {
if (this.caretPosition == null) this.caretPosition = new Position();
this.caretPosition.setLine(changeRange.getStart().getLine());
this.caretPosition.setCharacter(changeRange.getStart().getCharacter());
this.lastCaretChangeInstant = Instant.now();
} else {
this.caretPosition = null;
}
newText = oldText.substring(0, fromPos) + changeText + oldText.substring(toPos);
} else {
setText(changeText);
this.caretPosition = null;
newText = changeText;
}
}

super.setText(newText);
reinitializeLineStartPosList();
this.checkingResult = null;
this.diagnostics = null;

if (changeRange != null) {
this.caretPosition = guessCaretPositionInIncrementalUpdate(
changeRange, changeText, fromPos, toPos);
} else {
this.caretPosition = guessCaretPositionInFullUpdate(oldText);
}

if (this.caretPosition != null) this.lastCaretChangeInstant = Instant.now();
}

private @Nullable Position guessCaretPositionInIncrementalUpdate(
Range changeRange, String changeText, int fromPos, int toPos) {
@Nullable Position caretPosition = null;

if (fromPos == toPos) {
caretPosition = convertPosition(toPos + changeText.length());
} else if (changeText.isEmpty()) {
caretPosition = new Position(changeRange.getStart().getLine(),
changeRange.getStart().getCharacter());
}

return caretPosition;
}

private @Nullable Position guessCaretPositionInFullUpdate(String oldText) {
String newText = getText();
int numberOfEqualCharsAtStart = 0;

while ((numberOfEqualCharsAtStart < oldText.length())
&& (numberOfEqualCharsAtStart < newText.length())
&& (oldText.charAt(numberOfEqualCharsAtStart)
== newText.charAt(numberOfEqualCharsAtStart))) {
numberOfEqualCharsAtStart++;
}

int numberOfEqualCharsAtEnd = 0;

while ((numberOfEqualCharsAtEnd < oldText.length() - numberOfEqualCharsAtStart)
&& (numberOfEqualCharsAtEnd < newText.length() - numberOfEqualCharsAtStart)
&& (oldText.charAt(oldText.length() - numberOfEqualCharsAtEnd - 1)
== newText.charAt(newText.length() - numberOfEqualCharsAtEnd - 1))) {
numberOfEqualCharsAtEnd++;
}

int numberOfEqualChars = numberOfEqualCharsAtStart + numberOfEqualCharsAtEnd;

if ((numberOfEqualChars < 0.5 * oldText.length())
|| (numberOfEqualChars < 0.5 * newText.length())) {
return null;
}

return convertPosition(newText.length() - numberOfEqualCharsAtEnd);
}

public CompletableFuture<Boolean> checkAndPublishDiagnostics() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,10 @@ public void testConvertPosition() {
}

@Test
public void testApplyTextChangeEvents() {
public void testApplyIncrementalTextChangeEvents() {
LtexLanguageServer languageServer = new LtexLanguageServer();
LtexTextDocumentItem document = new LtexTextDocumentItem(
languageServer,"untitled:text.md", "markdown", 1, "abc");

document.applyTextChangeEvent(new TextDocumentContentChangeEvent("abcdef"));
Assertions.assertEquals("abcdef", document.getText());
assertNull(document.getCaretPosition());

languageServer,"untitled:text.md", "markdown", 1, "abcdef");
Instant pastInstant = Instant.now().minus(Duration.ofSeconds(10));

document.setLastCaretChangeInstant(pastInstant);
Expand All @@ -90,7 +85,8 @@ public void testApplyTextChangeEvents() {
document.applyTextChangeEvent(new TextDocumentContentChangeEvent(
new Range(new Position(0, 3), new Position(0, 3)), 0, "23"));
Assertions.assertEquals("ac123def", document.getText());
assertNull(document.getCaretPosition());
Assertions.assertEquals(new Position(0, 5),
NullnessUtil.castNonNull(document.getCaretPosition()));

document.applyTextChangeEvents(Collections.singletonList(new TextDocumentContentChangeEvent(
new Range(new Position(0, 5), new Position(0, 5)), 0, "4")));
Expand All @@ -107,6 +103,46 @@ public void testApplyTextChangeEvents() {
assertNull(document.getCaretPosition());
}

@Test
public void testApplyFullTextChangeEvents() {
LtexLanguageServer languageServer = new LtexLanguageServer();
LtexTextDocumentItem document = new LtexTextDocumentItem(
languageServer,"untitled:text.md", "markdown", 1, "abcdef");
Instant pastInstant = Instant.now().minus(Duration.ofSeconds(10));

document.setLastCaretChangeInstant(pastInstant);
document.applyTextChangeEvent(new TextDocumentContentChangeEvent("abc1def"));
Assertions.assertEquals("abc1def", document.getText());
Assertions.assertEquals(new Position(0, 4),
NullnessUtil.castNonNull(document.getCaretPosition()));
Assertions.assertTrue(
Duration.between(document.getLastCaretChangeInstant(), Instant.now()).toMillis() < 100);

document.setLastCaretChangeInstant(pastInstant);
document.applyTextChangeEvent(new TextDocumentContentChangeEvent("ac1def"));
Assertions.assertEquals("ac1def", document.getText());
Assertions.assertEquals(new Position(0, 1),
NullnessUtil.castNonNull(document.getCaretPosition()));
Assertions.assertTrue(
Duration.between(document.getLastCaretChangeInstant(), Instant.now()).toMillis() < 100);

document.applyTextChangeEvent(new TextDocumentContentChangeEvent("ac123def"));
Assertions.assertEquals("ac123def", document.getText());
Assertions.assertEquals(new Position(0, 5),
NullnessUtil.castNonNull(document.getCaretPosition()));

document.applyTextChangeEvent(new TextDocumentContentChangeEvent("ac1234def"));
Assertions.assertEquals("ac1234def", document.getText());
Assertions.assertEquals(new Position(0, 6),
NullnessUtil.castNonNull(document.getCaretPosition()));

document.applyTextChangeEvents(Arrays.asList(
new TextDocumentContentChangeEvent("ac12345def"),
new TextDocumentContentChangeEvent("ac123456def")));
Assertions.assertEquals("ac123456def", document.getText());
assertNull(document.getCaretPosition());
}

@Test
public void testProperties() {
LtexLanguageServer languageServer = new LtexLanguageServer();
Expand Down

0 comments on commit 8c48cea

Please sign in to comment.