Skip to content
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5148f71
Add implementation plan for issue #471 range-scoping + CodeLens resolve
clrudolphi Aug 24, 2026
a5dc1d1
Add genuine range-scoped semantic token encoding (issue #471)
clrudolphi Aug 24, 2026
fc6f71b
Wire textDocument/semanticTokens/range to genuine range-scoped encodi…
clrudolphi Aug 24, 2026
e3a5772
Add range-scoped step filtering to GherkinInlayHintService.Build (iss…
clrudolphi Aug 24, 2026
ca0b8b9
Scope InlayHintHandler's Build call to the requested range (issue #471)
clrudolphi Aug 24, 2026
90a9701
Declare codeLensProvider.resolveProvider statically (issue #471)
clrudolphi Aug 24, 2026
4be614c
Add gated deferred-resolve path to StepCodeLensHandler (issue #471)
clrudolphi Aug 24, 2026
a411231
Add gated deferred-resolve path to HookMatchCountCodeLensHandler (iss…
clrudolphi Aug 24, 2026
1c7cf19
Wire codeLens/resolve dispatcher (issue #471)
clrudolphi Aug 24, 2026
2e80d42
Gate codeLens/resolve deferral behind an opt-in allowlist (issue #471)
clrudolphi Aug 24, 2026
53bbf0f
Suppress vscode-languageclient's built-in CodeLens feature (issue #471)
clrudolphi Aug 24, 2026
2fb08dd
Withhold semantic tokens pull support from Visual Studio (issue #471)
clrudolphi Aug 24, 2026
c9f8818
Fix eslint no-unnecessary-type-assertion in codeLensSuppression.test.ts
clrudolphi Aug 24, 2026
172a92f
Index BindingMatchService.FindUsages with a clangd-shaped reverse ind…
clrudolphi Aug 24, 2026
f2bd7c9
Migrate callers holding a binding object to FindUsages(BindingId) (is…
clrudolphi Aug 24, 2026
2087dff
Thread the debouncer's cancellation token through to refresh requests…
clrudolphi Aug 25, 2026
4860b33
Suppress the redundant BindingRegistryChanged fired by startup reconc…
clrudolphi Aug 25, 2026
0c81e60
Skip redundant didOpen reparse once a project's connector has succeed…
clrudolphi Aug 25, 2026
4e8d98c
Replace GherkinRange.ResolveOffset's linear scan with binary search (…
clrudolphi Aug 25, 2026
0a0db19
Get didOpen/didChange's reparse off the shared Serial dispatch lane (…
clrudolphi Aug 25, 2026
82ea1cb
Add a literal prefilter for step-definition matching (issue #471)
clrudolphi Aug 25, 2026
43ad16a
Remove SemanticTokenService's duplicate linear-scan position resolver…
clrudolphi Aug 25, 2026
1d2216e
Merge remote-tracking branch 'origin/master' into issue-471-semantict…
clrudolphi Aug 25, 2026
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 @@ -269,12 +269,12 @@ private static List<int> Encode(
/// A tag that maps to a semantic token type, carrying its already-resolved start/end
/// (line, character) positions and its token type/modifier indices.
/// <para>
/// <see cref="ResolvePosition"/> is an O(document lines) linear scan, so resolving a tag's
/// positions is the dominant per-tag cost here. Carrying the result through the pipeline lets
/// the range filter and <see cref="CollectLeafTokens"/> share ONE resolution per tag instead
/// of each performing its own — without this, adding the range filter (issue #471) actually
/// *increased* the total <see cref="ResolvePosition"/> count for
/// <c>textDocument/semanticTokens/range</c> rather than reducing it.
/// Resolving a tag's positions (<see cref="GherkinRange.StartLinePosition"/>/
/// <see cref="GherkinRange.EndLinePosition"/>, both binary-search-backed — issue #471) is the
/// dominant per-tag cost here. Carrying the result through the pipeline lets the range filter
/// and <see cref="CollectLeafTokens"/> share ONE resolution per tag instead of each performing
/// its own — without this, adding the range filter (issue #471) actually *increased* the
/// total resolution count for <c>textDocument/semanticTokens/range</c> rather than reducing it.
/// </para>
/// </summary>
private readonly record struct PositionedTag(
Expand All @@ -286,8 +286,8 @@ private readonly record struct PositionedTag(
/// <summary>
/// Projects every token-mapped tag to a <see cref="PositionedTag"/> — the whole-document
/// (non-range) path. Tags with no token mapping are dropped before their positions are
/// resolved, so this costs exactly the two <see cref="ResolvePosition"/> calls per *emitted*
/// tag that <see cref="CollectLeafTokens"/> used to make on its own.
/// resolved, so this costs exactly the two position resolutions per *emitted* tag that
/// <see cref="CollectLeafTokens"/> used to make on its own.
/// </summary>
private static IEnumerable<PositionedTag> ResolveTokenTags(IEnumerable<DeveroomTag> tags)
{
Expand All @@ -296,8 +296,8 @@ private static IEnumerable<PositionedTag> ResolveTokenTags(IEnumerable<DeveroomT
if (!ReqnrollSemanticTokens.TryGetToken(tag, out var typeIdx, out var modBits))
continue;

var (startLine, startChar) = ResolvePosition(tag.Range, tag.Range.Start);
var (endLine, endChar) = ResolvePosition(tag.Range, tag.Range.End);
var (startLine, startChar) = tag.Range.StartLinePosition;
var (endLine, endChar) = tag.Range.EndLinePosition;
yield return new PositionedTag(tag, startLine, startChar, endLine, endChar, typeIdx, modBits);
}
}
Expand All @@ -309,11 +309,11 @@ private static IEnumerable<PositionedTag> ResolveTokenTags(IEnumerable<DeveroomT
/// (sorting, <see cref="ResolveOverlaps"/>, delta encoding, and the per-line walk for
/// multi-line tokens) then runs over the range's tag count rather than the document's.
/// <para>
/// Cost: at most two <see cref="ResolvePosition"/> calls per token-mapped tag and no more —
/// the end position is resolved only when the start position did not already rule the tag
/// out (a tag starting after <paramref name="endLine"/> cannot overlap, since its end is
/// never before its start), and <see cref="CollectLeafTokens"/> reuses both rather than
/// resolving the surviving tags a second time.
/// Cost: at most two position resolutions per token-mapped tag and no more — the end
/// position is resolved only when the start position did not already rule the tag out (a tag
/// starting after <paramref name="endLine"/> cannot overlap, since its end is never before its
/// start), and <see cref="CollectLeafTokens"/> reuses both rather than resolving the
/// surviving tags a second time.
/// </para>
/// </summary>
private static IEnumerable<PositionedTag> FilterToLineRange(
Expand All @@ -324,11 +324,11 @@ private static IEnumerable<PositionedTag> FilterToLineRange(
if (!ReqnrollSemanticTokens.TryGetToken(tag, out var typeIdx, out var modBits))
continue;

var (tagStartLine, tagStartChar) = ResolvePosition(tag.Range, tag.Range.Start);
var (tagStartLine, tagStartChar) = tag.Range.StartLinePosition;
if (tagStartLine > endLine)
continue;

var (tagEndLine, tagEndChar) = ResolvePosition(tag.Range, tag.Range.End);
var (tagEndLine, tagEndChar) = tag.Range.EndLinePosition;
if (tagEndLine < startLine)
continue;

Expand Down Expand Up @@ -378,25 +378,6 @@ private static void CollectLeafTokens(
}
}

/// <summary>
/// Resolves an absolute character offset within a snapshot to (line, character).
/// </summary>
private static (int Line, int Character) ResolvePosition(GherkinRange range, int absoluteOffset)
{
var snapshot = range.Snapshot;
// Linear scan — acceptable for typical feature file sizes.
for (int ln = 0; ln < snapshot.LineCount; ln++)
{
var line = snapshot.GetLineFromLineNumber(ln);
if (absoluteOffset <= line.End)
return (ln, absoluteOffset - line.Start);
}
// Clamp to end of last line.
int lastLine = snapshot.LineCount - 1;
var last = snapshot.GetLineFromLineNumber(lastLine);
return (lastLine, last.End - last.Start);
}

// ── Cache housekeeping ────────────────────────────────────────────────────
private void PurgePriorVersions(DocumentUri uri, int currentVersion)
{
Expand Down