Skip to content

Commit 18c093f

Browse files
authored
Merge pull request #3705 from 1c-syntax/copilot/add-semantic-tokens-support-again
Add support for textDocument/semanticTokens/range
2 parents 116269d + bc85d81 commit 18c093f

File tree

4 files changed

+352
-2
lines changed

4 files changed

+352
-2
lines changed

src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLLanguageServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ private SemanticTokensWithRegistrationOptions getSemanticTokensProvider() {
385385
fullOptions.setDelta(Boolean.TRUE);
386386
semanticTokensProvider.setFull(fullOptions);
387387

388-
semanticTokensProvider.setRange(Boolean.FALSE);
388+
semanticTokensProvider.setRange(Boolean.TRUE);
389389
return semanticTokensProvider;
390390
}
391391

src/main/java/com/github/_1c_syntax/bsl/languageserver/BSLTextDocumentService.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
import org.eclipse.lsp4j.SemanticTokensDelta;
102102
import org.eclipse.lsp4j.SemanticTokensDeltaParams;
103103
import org.eclipse.lsp4j.SemanticTokensParams;
104+
import org.eclipse.lsp4j.SemanticTokensRangeParams;
104105
import org.eclipse.lsp4j.SymbolInformation;
105106
import org.eclipse.lsp4j.TextDocumentClientCapabilities;
106107
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
@@ -367,6 +368,19 @@ public CompletableFuture<Either<SemanticTokens, SemanticTokensDelta>> semanticTo
367368
);
368369
}
369370

371+
@Override
372+
public CompletableFuture<SemanticTokens> semanticTokensRange(SemanticTokensRangeParams params) {
373+
var documentContext = context.getDocumentUnsafe(params.getTextDocument().getUri());
374+
if (documentContext == null) {
375+
return CompletableFuture.completedFuture(null);
376+
}
377+
378+
return withFreshDocumentContext(
379+
documentContext,
380+
() -> semanticTokensProvider.getSemanticTokensRange(documentContext, params)
381+
);
382+
}
383+
370384
@Override
371385
public CompletableFuture<List<CallHierarchyIncomingCall>> callHierarchyIncomingCalls(
372386
CallHierarchyIncomingCallsParams params

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

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@
3030
import jakarta.annotation.PostConstruct;
3131
import jakarta.annotation.PreDestroy;
3232
import lombok.RequiredArgsConstructor;
33+
import org.eclipse.lsp4j.Range;
3334
import org.eclipse.lsp4j.SemanticTokens;
3435
import org.eclipse.lsp4j.SemanticTokensDelta;
3536
import org.eclipse.lsp4j.SemanticTokensDeltaParams;
3637
import org.eclipse.lsp4j.SemanticTokensEdit;
3738
import org.eclipse.lsp4j.SemanticTokensParams;
39+
import org.eclipse.lsp4j.SemanticTokensRangeParams;
3840
import org.eclipse.lsp4j.jsonrpc.messages.Either;
3941
import org.springframework.context.event.EventListener;
4042
import org.springframework.stereotype.Component;
@@ -57,14 +59,20 @@
5759
/**
5860
* Провайдер для предоставления семантических токенов.
5961
* <p>
60-
* Обрабатывает запросы {@code textDocument/semanticTokens/full} и {@code textDocument/semanticTokens/full/delta}.
62+
* Обрабатывает запросы {@code textDocument/semanticTokens/full}, {@code textDocument/semanticTokens/full/delta}
63+
* и {@code textDocument/semanticTokens/range}.
6164
*
6265
* @see <a href="https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens">Semantic Tokens specification</a>
6366
*/
6467
@Component
6568
@RequiredArgsConstructor
6669
public class SemanticTokensProvider {
6770

71+
/**
72+
* Порог количества токенов, при превышении которого используется параллельная обработка.
73+
*/
74+
private static final int PARALLEL_PROCESSING_THRESHOLD = 1000;
75+
6876
@SuppressWarnings("NullAway.Init")
6977
private ExecutorService executorService;
7078

@@ -164,6 +172,93 @@ public Either<SemanticTokens, SemanticTokensDelta> getSemanticTokensFullDelta(
164172
return Either.forRight(delta);
165173
}
166174

175+
/**
176+
* Получить семантические токены для указанного диапазона документа.
177+
*
178+
* @param documentContext Контекст документа
179+
* @param params Параметры запроса с диапазоном
180+
* @return Семантические токены для указанного диапазона в дельта-кодированном формате
181+
*/
182+
public SemanticTokens getSemanticTokensRange(
183+
DocumentContext documentContext,
184+
SemanticTokensRangeParams params
185+
) {
186+
Range range = params.getRange();
187+
188+
// Collect tokens from all suppliers in parallel
189+
var entries = collectTokens(documentContext);
190+
191+
// Filter tokens that fall within the specified range
192+
var filteredEntries = filterTokensByRange(entries, range);
193+
194+
// Build delta-encoded data as int array
195+
int[] data = toDeltaEncodedArray(filteredEntries);
196+
197+
// Range requests do not use resultId caching as per LSP specification
198+
return new SemanticTokens(toList(data));
199+
}
200+
201+
/**
202+
* Фильтрует токены, оставляя только те, которые попадают в указанный диапазон.
203+
* <p>
204+
* Токен считается попадающим в диапазон, если он хотя бы частично пересекается с ним.
205+
* <p>
206+
* Оптимизация: сначала выполняется быстрая фильтрация по строкам (простое сравнение целых чисел),
207+
* затем для граничных строк проверяются позиции символов. Использует параллельную обработку
208+
* для больших объемов данных.
209+
*
210+
* @param entries Список токенов
211+
* @param range Диапазон для фильтрации
212+
* @return Отфильтрованный список токенов
213+
*/
214+
private static List<SemanticTokenEntry> filterTokensByRange(List<SemanticTokenEntry> entries, Range range) {
215+
int startLine = range.getStart().getLine();
216+
int startChar = range.getStart().getCharacter();
217+
int endLine = range.getEnd().getLine();
218+
int endChar = range.getEnd().getCharacter();
219+
220+
// Use parallel stream for large collections to leverage multiple cores
221+
var stream = entries.size() > PARALLEL_PROCESSING_THRESHOLD
222+
? entries.parallelStream()
223+
: entries.stream();
224+
225+
return stream
226+
// Quick line-based pre-filter (simple integer comparison)
227+
.filter(token -> token.line() >= startLine && token.line() <= endLine)
228+
// Detailed check for boundary lines only
229+
.filter(token -> isTokenInRangeDetailed(token, startLine, startChar, endLine, endChar))
230+
.toList();
231+
}
232+
233+
/**
234+
* Проверяет позицию символов для токенов на граничных строках.
235+
* <p>
236+
* Предполагается, что токен уже прошел проверку по строкам (находится между startLine и endLine).
237+
*/
238+
private static boolean isTokenInRangeDetailed(
239+
SemanticTokenEntry token,
240+
int startLine,
241+
int startChar,
242+
int endLine,
243+
int endChar
244+
) {
245+
int tokenLine = token.line();
246+
int tokenStart = token.start();
247+
int tokenEnd = tokenStart + token.length();
248+
249+
// Token is on the start line - check if it ends after range start
250+
if (tokenLine == startLine && tokenEnd <= startChar) {
251+
return false;
252+
}
253+
254+
// Token is on the end line - check if it starts before range end
255+
if (tokenLine == endLine && tokenStart >= endChar) {
256+
return false;
257+
}
258+
259+
return true;
260+
}
261+
167262
/**
168263
* Обрабатывает событие закрытия документа в контексте сервера.
169264
* <p>

0 commit comments

Comments
 (0)