Skip to content

Commit 9ee333e

Browse files
authored
Merge pull request #3707 from 1c-syntax/copilot/sub-pr-3706
Fix inline text insertion handling in semantic token delta calculation
2 parents 65674c2 + a967801 commit 9ee333e

File tree

2 files changed

+100
-5
lines changed

2 files changed

+100
-5
lines changed

src/main/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProvider.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,13 @@ private static List<SemanticTokensEdit> computeEdits(int[] prev, int[] curr) {
294294
* <p>
295295
* При дельта-кодировании токены после точки вставки идентичны,
296296
* кроме первого токена, у которого deltaLine смещён на lineOffset.
297+
* При вставке текста без перевода строки (lineOffset == 0), первый токен
298+
* может иметь смещённый deltaStart.
297299
*/
298300
private static int findSuffixMatchWithOffset(int[] prev, int[] curr, int firstDiffToken, int lineOffset, int tokenSize) {
301+
final int DELTA_LINE_INDEX = 0;
302+
final int DELTA_START_INDEX = 1;
303+
299304
int prevTokenCount = prev.length / tokenSize;
300305
int currTokenCount = curr.length / tokenSize;
301306

@@ -310,9 +315,13 @@ private static int findSuffixMatchWithOffset(int[] prev, int[] curr, int firstDi
310315
int prevIdx = (prevTokenCount - 1 - i) * tokenSize;
311316
int currIdx = (currTokenCount - 1 - i) * tokenSize;
312317

313-
// Сначала проверяем все поля кроме deltaLine
318+
// Для граничного токена при inline-редактировании (lineOffset == 0)
319+
// разрешаем различие в deltaStart
320+
int firstFieldToCheck = (!foundBoundary && lineOffset == 0) ? DELTA_START_INDEX + 1 : DELTA_START_INDEX;
321+
322+
// Проверяем поля кроме deltaLine (и возможно deltaStart для граничного токена)
314323
boolean otherFieldsMatch = true;
315-
for (int j = 1; j < tokenSize; j++) {
324+
for (int j = firstFieldToCheck; j < tokenSize; j++) {
316325
if (prev[prevIdx + j] != curr[currIdx + j]) {
317326
otherFieldsMatch = false;
318327
break;
@@ -324,12 +333,20 @@ private static int findSuffixMatchWithOffset(int[] prev, int[] curr, int firstDi
324333
}
325334

326335
// Теперь проверяем deltaLine
327-
int prevDeltaLine = prev[prevIdx];
328-
int currDeltaLine = curr[currIdx];
336+
int prevDeltaLine = prev[prevIdx + DELTA_LINE_INDEX];
337+
int currDeltaLine = curr[currIdx + DELTA_LINE_INDEX];
329338

330339
if (prevDeltaLine == currDeltaLine) {
331-
// Полное совпадение
340+
// Полное совпадение (или совпадение с учётом deltaStart при inline-редактировании)
332341
suffixMatch++;
342+
// Если это был граничный токен при inline-редактировании, отмечаем его найденным
343+
if (!foundBoundary && lineOffset == 0) {
344+
int prevDeltaStart = prev[prevIdx + DELTA_START_INDEX];
345+
int currDeltaStart = curr[currIdx + DELTA_START_INDEX];
346+
if (prevDeltaStart != currDeltaStart) {
347+
foundBoundary = true;
348+
}
349+
}
333350
} else if (!foundBoundary && currDeltaLine - prevDeltaLine == lineOffset) {
334351
// Граничный токен — deltaLine отличается ровно на lineOffset
335352
suffixMatch++;

src/test/java/com/github/_1c_syntax/bsl/languageserver/providers/SemanticTokensProviderTest.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,84 @@ void deltaWithLineInsertedInMiddle_shouldReturnOptimalDelta() {
13411341
assertThat(editSize).isLessThan(originalDataSize);
13421342
}
13431343

1344+
@Test
1345+
void deltaWithTextInsertedOnSameLine_shouldReturnOptimalDelta() {
1346+
// given - simulate inserting text on the same line without line breaks
1347+
// This tests the case raised by @nixel2007: text insertion without newline
1348+
String bsl1 = """
1349+
Перем А;
1350+
""";
1351+
1352+
String bsl2 = """
1353+
Перем Новая, А;
1354+
""";
1355+
1356+
DocumentContext context1 = TestUtils.getDocumentContext(bsl1);
1357+
referenceIndexFiller.fill(context1);
1358+
TextDocumentIdentifier textDocId1 = TestUtils.getTextDocumentIdentifier(context1.getUri());
1359+
SemanticTokens tokens1 = provider.getSemanticTokensFull(context1, new SemanticTokensParams(textDocId1));
1360+
1361+
// Verify original tokens structure
1362+
var decoded1 = decode(tokens1.getData());
1363+
var expected1 = List.of(
1364+
new ExpectedToken(0, 0, 5, SemanticTokenTypes.Keyword, "Перем"),
1365+
new ExpectedToken(0, 6, 1, SemanticTokenTypes.Variable, SemanticTokenModifiers.Definition, "А"),
1366+
new ExpectedToken(0, 7, 1, SemanticTokenTypes.Operator, ";")
1367+
);
1368+
assertTokensMatch(decoded1, expected1);
1369+
1370+
DocumentContext context2 = TestUtils.getDocumentContext(context1.getUri(), bsl2);
1371+
referenceIndexFiller.fill(context2);
1372+
SemanticTokens tokens2 = provider.getSemanticTokensFull(context2, new SemanticTokensParams(textDocId1));
1373+
1374+
// Verify modified tokens structure
1375+
var decoded2 = decode(tokens2.getData());
1376+
var expected2 = List.of(
1377+
new ExpectedToken(0, 0, 5, SemanticTokenTypes.Keyword, "Перем"),
1378+
new ExpectedToken(0, 6, 5, SemanticTokenTypes.Variable, SemanticTokenModifiers.Definition, "Новая"),
1379+
new ExpectedToken(0, 11, 1, SemanticTokenTypes.Operator, ","),
1380+
new ExpectedToken(0, 13, 1, SemanticTokenTypes.Variable, SemanticTokenModifiers.Definition, "А"),
1381+
new ExpectedToken(0, 14, 1, SemanticTokenTypes.Operator, ";")
1382+
);
1383+
assertTokensMatch(decoded2, expected2);
1384+
1385+
// when
1386+
var deltaParams = new SemanticTokensDeltaParams(textDocId1, tokens1.getResultId());
1387+
var result = provider.getSemanticTokensFullDelta(context2, deltaParams);
1388+
1389+
// then - should return delta, not full tokens
1390+
assertThat(result.isRight()).isTrue();
1391+
var delta = result.getRight();
1392+
assertThat(delta.getEdits()).isNotEmpty();
1393+
assertThat(delta.getEdits()).hasSize(1);
1394+
1395+
// Verify the delta edit details
1396+
// Original: [Перем, А, ;] - 3 tokens = 15 integers
1397+
// Modified: [Перем, Новая, ,, А, ;] - 5 tokens = 25 integers
1398+
//
1399+
// With lineOffset=0 inline edit handling:
1400+
// - Prefix match: "Перем" (1 token = 5 integers)
1401+
// - Suffix match: "А" and ";" (2 tokens = 10 integers)
1402+
// Note: "А" matches because the algorithm allows deltaStart to differ when lineOffset=0
1403+
// - Edit deletes: nothing (0 integers)
1404+
// - Edit inserts: "Новая" and "," (2 tokens = 10 integers)
1405+
var edit = delta.getEdits().get(0);
1406+
assertThat(edit.getStart())
1407+
.as("Edit should start after the prefix match (Перем = 5 integers)")
1408+
.isEqualTo(5);
1409+
assertThat(edit.getDeleteCount())
1410+
.as("Edit should delete nothing (suffix match includes А and ;)")
1411+
.isEqualTo(0);
1412+
assertThat(edit.getData())
1413+
.as("Edit should insert Новая and , tokens (2 tokens = 10 integers)")
1414+
.isNotNull()
1415+
.hasSize(10);
1416+
1417+
// Verify the edit is optimal (smaller than sending all new tokens)
1418+
int editSize = edit.getDeleteCount() + edit.getData().size();
1419+
assertThat(editSize).isLessThan(tokens2.getData().size());
1420+
}
1421+
13441422
// endregion
13451423
}
13461424

0 commit comments

Comments
 (0)