From 0aea959c6fea6366117df767a48824c1dadb089a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 11:05:59 +0000 Subject: [PATCH 1/3] Initial plan From cd2d69f1c8dbed43c6ce5f7a8b551c4ad6ea2aa8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 11:14:34 +0000 Subject: [PATCH 2/3] Address review comments: extract TOKEN_SIZE constant, rename label, improve tests - Extract TOKEN_SIZE as class-level constant for consistency - Rename 'outer' label to 'findFirstDifference' for clarity - Remove unnecessary early return check in computeEdits - Add comprehensive delta edit tests that verify correctness by applying edits - Tests confirm that same-line text insertion (without line breaks) is handled correctly Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../providers/SemanticTokensProvider.java | 29 ++- .../providers/SemanticTokensProviderTest.java | 181 ++++++++++++++++++ 2 files changed, 195 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProvider.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProvider.java index 86cec2afb02..fe0bd319116 100644 --- a/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProvider.java +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProvider.java @@ -65,6 +65,11 @@ @RequiredArgsConstructor public class SemanticTokensProvider { + /** + * Number of integers per semantic token (deltaLine, deltaStart, length, type, modifiers). + */ + private static final int TOKEN_SIZE = 5; + @SuppressWarnings("NullAway.Init") private ExecutorService executorService; @@ -218,8 +223,6 @@ private void cacheTokenData(String resultId, URI uri, int[] data) { * и смещение строк при вставке/удалении строк в документе. */ private static List computeEdits(int[] prev, int[] curr) { - final int TOKEN_SIZE = 5; - int prevTokenCount = prev.length / TOKEN_SIZE; int currTokenCount = curr.length / TOKEN_SIZE; @@ -232,13 +235,13 @@ private static List computeEdits(int[] prev, int[] curr) { int prefixAbsLine = 0; int minTokens = Math.min(prevTokenCount, currTokenCount); - outer: + findFirstDifference: for (int i = 0; i < minTokens; i++) { int base = i * TOKEN_SIZE; for (int j = 0; j < TOKEN_SIZE; j++) { if (prev[base + j] != curr[base + j]) { firstDiffToken = i; - break outer; + break findFirstDifference; } } prefixAbsLine += prev[base]; // накапливаем deltaLine @@ -262,7 +265,7 @@ private static List computeEdits(int[] prev, int[] curr) { int lineOffset = currSuffixAbsLine - prevSuffixAbsLine; // Находим последний отличающийся токен с учётом смещения строк - int suffixMatchTokens = findSuffixMatchWithOffset(prev, curr, firstDiffToken, lineOffset, TOKEN_SIZE); + int suffixMatchTokens = findSuffixMatchWithOffset(prev, curr, firstDiffToken, lineOffset); // Вычисляем границы редактирования int deleteEndToken = prevTokenCount - suffixMatchTokens; @@ -272,10 +275,6 @@ private static List computeEdits(int[] prev, int[] curr) { int deleteCount = (deleteEndToken - firstDiffToken) * TOKEN_SIZE; int insertEnd = insertEndToken * TOKEN_SIZE; - if (deleteCount == 0 && deleteStart == insertEnd) { - return List.of(); - } - // Создаём список для вставки из среза массива List insertData = toList(Arrays.copyOfRange(curr, deleteStart, insertEnd)); @@ -295,9 +294,9 @@ private static List computeEdits(int[] prev, int[] curr) { * При дельта-кодировании токены после точки вставки идентичны, * кроме первого токена, у которого deltaLine смещён на lineOffset. */ - private static int findSuffixMatchWithOffset(int[] prev, int[] curr, int firstDiffToken, int lineOffset, int tokenSize) { - int prevTokenCount = prev.length / tokenSize; - int currTokenCount = curr.length / tokenSize; + private static int findSuffixMatchWithOffset(int[] prev, int[] curr, int firstDiffToken, int lineOffset) { + int prevTokenCount = prev.length / TOKEN_SIZE; + int currTokenCount = curr.length / TOKEN_SIZE; int maxPrevSuffix = prevTokenCount - firstDiffToken; int maxCurrSuffix = currTokenCount - firstDiffToken; @@ -307,12 +306,12 @@ private static int findSuffixMatchWithOffset(int[] prev, int[] curr, int firstDi boolean foundBoundary = false; for (int i = 0; i < maxSuffix; i++) { - int prevIdx = (prevTokenCount - 1 - i) * tokenSize; - int currIdx = (currTokenCount - 1 - i) * tokenSize; + int prevIdx = (prevTokenCount - 1 - i) * TOKEN_SIZE; + int currIdx = (currTokenCount - 1 - i) * TOKEN_SIZE; // Сначала проверяем все поля кроме deltaLine boolean otherFieldsMatch = true; - for (int j = 1; j < tokenSize; j++) { + for (int j = 1; j < TOKEN_SIZE; j++) { if (prev[prevIdx + j] != curr[currIdx + j]) { otherFieldsMatch = false; break; diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProviderTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProviderTest.java index 869726045ce..ff523f85c82 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProviderTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProviderTest.java @@ -29,6 +29,7 @@ import org.eclipse.lsp4j.SemanticTokenTypes; import org.eclipse.lsp4j.SemanticTokens; import org.eclipse.lsp4j.SemanticTokensDeltaParams; +import org.eclipse.lsp4j.SemanticTokensEdit; import org.eclipse.lsp4j.SemanticTokensLegend; import org.eclipse.lsp4j.SemanticTokensParams; import org.eclipse.lsp4j.TextDocumentIdentifier; @@ -1341,6 +1342,186 @@ void deltaWithLineInsertedInMiddle_shouldReturnOptimalDelta() { assertThat(editSize).isLessThan(originalDataSize); } + @Test + void deltaEdit_appliedToPreviousData_producesCurrentData() { + // given - simulate modifying document + String bsl1 = """ + Перем А; + Перем Б; + Перем В; + """; + + String bsl2 = """ + Перем А; + Перем Новая; + Перем Б; + Перем В; + """; + + DocumentContext context1 = TestUtils.getDocumentContext(bsl1); + referenceIndexFiller.fill(context1); + TextDocumentIdentifier textDocId1 = TestUtils.getTextDocumentIdentifier(context1.getUri()); + SemanticTokens tokens1 = provider.getSemanticTokensFull(context1, new SemanticTokensParams(textDocId1)); + + DocumentContext context2 = TestUtils.getDocumentContext(context1.getUri(), bsl2); + referenceIndexFiller.fill(context2); + SemanticTokens tokens2 = provider.getSemanticTokensFull(context2, new SemanticTokensParams(textDocId1)); + + // when + var deltaParams = new SemanticTokensDeltaParams(textDocId1, tokens1.getResultId()); + var result = provider.getSemanticTokensFullDelta(context2, deltaParams); + + // then - applying edit to previous data should produce current data + assertThat(result.isRight()).isTrue(); + var delta = result.getRight(); + assertThat(delta.getEdits()).hasSize(1); + + var edit = delta.getEdits().get(0); + List appliedData = applyEdit(tokens1.getData(), edit); + + assertThat(appliedData) + .as("Applying delta edit to previous data should produce current data") + .isEqualTo(tokens2.getData()); + } + + @Test + void deltaEdit_withLineInsertionAtStart_appliedCorrectly() { + // given + String bsl1 = """ + Перем А; + Перем Б; + """; + + String bsl2 = """ + Перем Новая; + Перем А; + Перем Б; + """; + + DocumentContext context1 = TestUtils.getDocumentContext(bsl1); + referenceIndexFiller.fill(context1); + TextDocumentIdentifier textDocId1 = TestUtils.getTextDocumentIdentifier(context1.getUri()); + SemanticTokens tokens1 = provider.getSemanticTokensFull(context1, new SemanticTokensParams(textDocId1)); + + DocumentContext context2 = TestUtils.getDocumentContext(context1.getUri(), bsl2); + referenceIndexFiller.fill(context2); + SemanticTokens tokens2 = provider.getSemanticTokensFull(context2, new SemanticTokensParams(textDocId1)); + + // when + var deltaParams = new SemanticTokensDeltaParams(textDocId1, tokens1.getResultId()); + var result = provider.getSemanticTokensFullDelta(context2, deltaParams); + + // then + assertThat(result.isRight()).isTrue(); + var delta = result.getRight(); + var edit = delta.getEdits().get(0); + List appliedData = applyEdit(tokens1.getData(), edit); + + assertThat(appliedData) + .as("Applying delta edit should produce expected data") + .isEqualTo(tokens2.getData()); + } + + @Test + void deltaEdit_withLineDeletion_appliedCorrectly() { + // given + String bsl1 = """ + Перем А; + Перем Удаляемая; + Перем Б; + Перем В; + """; + + String bsl2 = """ + Перем А; + Перем Б; + Перем В; + """; + + DocumentContext context1 = TestUtils.getDocumentContext(bsl1); + referenceIndexFiller.fill(context1); + TextDocumentIdentifier textDocId1 = TestUtils.getTextDocumentIdentifier(context1.getUri()); + SemanticTokens tokens1 = provider.getSemanticTokensFull(context1, new SemanticTokensParams(textDocId1)); + + DocumentContext context2 = TestUtils.getDocumentContext(context1.getUri(), bsl2); + referenceIndexFiller.fill(context2); + SemanticTokens tokens2 = provider.getSemanticTokensFull(context2, new SemanticTokensParams(textDocId1)); + + // when + var deltaParams = new SemanticTokensDeltaParams(textDocId1, tokens1.getResultId()); + var result = provider.getSemanticTokensFullDelta(context2, deltaParams); + + // then + assertThat(result.isRight()).isTrue(); + var delta = result.getRight(); + var edit = delta.getEdits().get(0); + List appliedData = applyEdit(tokens1.getData(), edit); + + assertThat(appliedData) + .as("Applying delta edit should produce expected data") + .isEqualTo(tokens2.getData()); + } + + @Test + void deltaEdit_withTextInsertionOnSameLine_appliedCorrectly() { + // given - adding text on the same line (no new line) + String bsl1 = """ + Перем А; + """; + + String bsl2 = """ + Перем А, Б; + """; + + DocumentContext context1 = TestUtils.getDocumentContext(bsl1); + referenceIndexFiller.fill(context1); + TextDocumentIdentifier textDocId1 = TestUtils.getTextDocumentIdentifier(context1.getUri()); + SemanticTokens tokens1 = provider.getSemanticTokensFull(context1, new SemanticTokensParams(textDocId1)); + + DocumentContext context2 = TestUtils.getDocumentContext(context1.getUri(), bsl2); + referenceIndexFiller.fill(context2); + SemanticTokens tokens2 = provider.getSemanticTokensFull(context2, new SemanticTokensParams(textDocId1)); + + // when + var deltaParams = new SemanticTokensDeltaParams(textDocId1, tokens1.getResultId()); + var result = provider.getSemanticTokensFullDelta(context2, deltaParams); + + // then + assertThat(result.isRight()).isTrue(); + var delta = result.getRight(); + var edit = delta.getEdits().get(0); + List appliedData = applyEdit(tokens1.getData(), edit); + + assertThat(appliedData) + .as("Applying delta edit for same-line insertion should produce expected data") + .isEqualTo(tokens2.getData()); + } + + /** + * Helper method to apply a semantic tokens edit to previous data. + * Simulates what the LSP client does when receiving a delta. + */ + private List applyEdit(List previousData, SemanticTokensEdit edit) { + List result = new ArrayList<>(previousData); + + int start = edit.getStart(); + int deleteCount = edit.getDeleteCount(); + List insertData = edit.getData(); + + // Remove deleteCount elements starting at start + for (int i = 0; i < deleteCount; i++) { + result.remove(start); + } + + // Insert new data at start position + if (insertData != null && !insertData.isEmpty()) { + result.addAll(start, insertData); + } + + return result; + } + // endregion } + From 243f7d8cb7c97d167fe00db10557a966c8a20820 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 11:18:26 +0000 Subject: [PATCH 3/3] Fix code formatting issues in tests - Remove extra blank lines for consistency - Remove trailing empty line at end of file Co-authored-by: nixel2007 <1132840+nixel2007@users.noreply.github.com> --- .../providers/SemanticTokensProviderTest.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProviderTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProviderTest.java index ff523f85c82..04ed1620799 100644 --- a/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProviderTest.java +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProviderTest.java @@ -1375,10 +1375,10 @@ void deltaEdit_appliedToPreviousData_producesCurrentData() { assertThat(result.isRight()).isTrue(); var delta = result.getRight(); assertThat(delta.getEdits()).hasSize(1); - + var edit = delta.getEdits().get(0); List appliedData = applyEdit(tokens1.getData(), edit); - + assertThat(appliedData) .as("Applying delta edit to previous data should produce current data") .isEqualTo(tokens2.getData()); @@ -1416,7 +1416,6 @@ void deltaEdit_withLineInsertionAtStart_appliedCorrectly() { var delta = result.getRight(); var edit = delta.getEdits().get(0); List appliedData = applyEdit(tokens1.getData(), edit); - assertThat(appliedData) .as("Applying delta edit should produce expected data") .isEqualTo(tokens2.getData()); @@ -1456,7 +1455,6 @@ void deltaEdit_withLineDeletion_appliedCorrectly() { var delta = result.getRight(); var edit = delta.getEdits().get(0); List appliedData = applyEdit(tokens1.getData(), edit); - assertThat(appliedData) .as("Applying delta edit should produce expected data") .isEqualTo(tokens2.getData()); @@ -1491,7 +1489,6 @@ void deltaEdit_withTextInsertionOnSameLine_appliedCorrectly() { var delta = result.getRight(); var edit = delta.getEdits().get(0); List appliedData = applyEdit(tokens1.getData(), edit); - assertThat(appliedData) .as("Applying delta edit for same-line insertion should produce expected data") .isEqualTo(tokens2.getData()); @@ -1517,11 +1514,10 @@ private List applyEdit(List previousData, SemanticTokensEdit e if (insertData != null && !insertData.isEmpty()) { result.addAll(start, insertData); } - + return result; } // endregion } -