blob: 0cc291d501328e3b226bc6824ef0d4b3895f0c37 [file] [edit]
import Lean.Server.FileWorker.SemanticHighlighting
open Lean Server.FileWorker
deriving instance Repr for Lsp.SemanticTokenType
deriving instance Repr for AbsoluteLspSemanticToken
/-!
# Non-Overlapping Cases
When no tokens overlap, and they are all sorted, then `handleOverlappingSemanticTokens` is the
identity.
-/
def noOverlaps : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 7⟩
type := .keyword },
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .function },
{ pos := ⟨1, 10⟩
tailPos := ⟨1, 14⟩
type := .keyword }
]
#guard handleOverlappingSemanticTokens noOverlaps == noOverlaps
/-!
When no tokens overlap, then `handleOverlappingSemanticTokens` is equivalent to sorting the tokens
by their start position.
-/
partial def permutations (xs : Array α) :=
if h : xs.size > 0 then
go h ⟨xs.size, by grind⟩ xs.toVector |>.run #[] |>.2
else #[]
where
go (h : xs.size > 0) (k : Fin (xs.size + 1)) (ys : Vector α xs.size) : StateM (Array (Array α)) Unit := do
if h0 : k = 0 then pure ()
else if h1 : k = 1 then
modify (·.push ys.toArray)
else
go h (k - 1) ys
for h' : i in [0 : k - 1] do
have : i < xs.size := by
cases h'
grind
go h (k - 1) <|
if k % 2 == 0 then
ys.swapIfInBounds i (k - 1)
else
ys.swapIfInBounds 0 (k - 1)
#guard (permutations noOverlaps).map handleOverlappingSemanticTokens |>.all (· == noOverlaps)
/-!
# Duplicate and Co-Extensive Tokens
Duplicate tokens are removed
-/
def duplicate : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 7⟩
type := .keyword },
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .function },
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .function },
{ pos := ⟨1, 10⟩
tailPos := ⟨1, 14⟩
type := .keyword }
]
#guard handleOverlappingSemanticTokens duplicate == noOverlaps
/-!
When two tokens are co-extensive, the highest priority is retained.
If the priorities are the same, then the later token is retained.
-/
def sameRange1 : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 7⟩
type := .keyword },
-- Here, we have two tokens in the same range with the same priority:
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .function },
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .variable },
{ pos := ⟨1, 10⟩
tailPos := ⟨1, 14⟩
type := .keyword }
]
def sameRange1expected : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 7⟩
type := .keyword },
-- The later of the two coextensive tokens (WRT the input array) is retained.
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .variable },
{ pos := ⟨1, 10⟩
tailPos := ⟨1, 14⟩
type := .keyword }
]
#guard handleOverlappingSemanticTokens sameRange1 == sameRange1expected
def sameRange1' : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 7⟩
type := .keyword },
-- Here, we have two tokens in the same range with the same priority:
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .variable },
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .function },
{ pos := ⟨1, 10⟩
tailPos := ⟨1, 14⟩
type := .keyword }
]
def sameRange1'expected : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 7⟩
type := .keyword },
-- The later of the two coextensive tokens (WRT the input array) is retained.
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .function },
{ pos := ⟨1, 10⟩
tailPos := ⟨1, 14⟩
type := .keyword }
]
#guard handleOverlappingSemanticTokens sameRange1' == sameRange1'expected
def sameRange2 : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 7⟩
type := .keyword },
-- Here, we have two tokens in the same range with different priorities.
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .function },
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .variable
priority := 6 },
{ pos := ⟨1, 10⟩
tailPos := ⟨1, 14⟩
type := .keyword }
]
def sameRange2expected : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 7⟩
type := .keyword },
-- The higher-priority token is retained.
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .variable
priority := 6 },
{ pos := ⟨1, 10⟩
tailPos := ⟨1, 14⟩
type := .keyword }
]
#guard handleOverlappingSemanticTokens sameRange2 == sameRange2expected
def sameRange3 : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 7⟩
type := .keyword },
-- Here, we have two tokens in the same range with different priorities.
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .function
priority := 6 },
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .variable },
{ pos := ⟨1, 10⟩
tailPos := ⟨1, 14⟩
type := .keyword }
]
def sameRange3expected : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 7⟩
type := .keyword },
-- The higher-priority token is retained.
{ pos := ⟨1, 8⟩
tailPos := ⟨1, 10⟩
type := .function
priority := 6 },
{ pos := ⟨1, 10⟩
tailPos := ⟨1, 14⟩
type := .keyword }
]
#guard handleOverlappingSemanticTokens sameRange3 == sameRange3expected
/-!
# General Overlaps
This is the following situation:
Given tokens A, B, C, D as in:
```
|-----A------| |----D----|
|------B----------|
|----C----|
```
with priorities C > B, B > A, B > D, we want to emit the tokens:
```
|-A-|-B-|----C----|-B-|-D--|
```
In other words, `B` is split into two regions: before and after `C`.
-/
def multiOverlap : Array AbsoluteLspSemanticToken := #[
-- A
{ pos := ⟨1, 1⟩
tailPos := ⟨1, 14⟩
type := .function
priority := 3 },
-- B
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 23⟩
type := .string
priority := 4 },
-- C
{ pos := ⟨1, 9⟩
tailPos := ⟨1, 19⟩
type := .property
priority := 5 },
-- D
{ pos := ⟨1, 17⟩
tailPos := ⟨1, 27⟩
type := .keyword
priority := 3 },
]
def multiExpected : Array AbsoluteLspSemanticToken := #[
-- A
{ pos := { line := 1, character := 1 },
tailPos := { line := 1, character := 5 },
type := .function
priority := 3 },
-- B
{ pos := { line := 1, character := 5 },
tailPos := { line := 1, character := 9 },
type := .string
priority := 4 },
-- C
{ pos := { line := 1, character := 9 },
tailPos := { line := 1, character := 19 },
type := .property
priority := 5 },
-- B
{ pos := { line := 1, character := 19 },
tailPos := { line := 1, character := 23 },
type := .string
priority := 4 },
-- D
{ pos := { line := 1, character := 23 },
tailPos := { line := 1, character := 27 },
type := .keyword
priority := 3 }]
#guard handleOverlappingSemanticTokens multiOverlap == multiExpected
def overlappingLeftRight : Array AbsoluteLspSemanticToken := #[
-- An overlap of left over right
{ pos := ⟨2, 5⟩
tailPos := ⟨2, 10⟩
type := .function
priority := 6 },
{ pos := ⟨2, 7⟩
tailPos := ⟨2, 13⟩
type := .function
priority := 5 },
-- An overlap of right over left
{ pos := ⟨3, 5⟩
tailPos := ⟨3, 10⟩
type := .function
priority := 4 },
{ pos := ⟨3, 7⟩
tailPos := ⟨3, 13⟩
type := .function
priority := 5 },
]
def overlappingLeftRightExpected : Array AbsoluteLspSemanticToken := #[
-- Left over right:
{ pos := ⟨2, 5⟩
tailPos := ⟨2, 10⟩
type := .function
priority := 6 },
{ pos := ⟨2, 10⟩
tailPos := ⟨2, 13⟩
type := .function
priority := 5 },
-- Right over left:
{ pos := ⟨3, 5⟩
tailPos := ⟨3, 7⟩
type := .function
priority := 4 },
{ pos := ⟨3, 7⟩
tailPos := ⟨3, 13⟩
type := .function
priority := 5 },
]
#guard handleOverlappingSemanticTokens overlappingLeftRight == overlappingLeftRightExpected
/-!
If two tokens start at the same position and have the same priority, the shorter one is used first.
-/
def sameStart : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 3⟩
tailPos := ⟨1, 5⟩
type := .function },
{ pos := ⟨1, 3⟩
tailPos := ⟨1, 8⟩
type := .string },
]
def sameStartExpected : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 3⟩
tailPos := ⟨1, 5⟩
type := .function },
{ pos := ⟨1, 5⟩
tailPos := ⟨1, 8⟩
type := .string },
]
#guard handleOverlappingSemanticTokens sameStart == sameStartExpected
def sameStart' : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 3⟩
tailPos := ⟨1, 8⟩
type := .string },
{ pos := ⟨1, 3⟩
tailPos := ⟨1, 5⟩
type := .function },
]
#guard handleOverlappingSemanticTokens sameStart' == sameStartExpected
/-!
If two tokens end at the same position, then the shorter one takes precedence over the longer one in
its range.
-/
def sameEnd : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 1⟩
tailPos := ⟨1, 5⟩
type := .function },
{ pos := ⟨1, 3⟩
tailPos := ⟨1, 5⟩
type := .string },
]
def sameEndExpected : Array AbsoluteLspSemanticToken := #[
{ pos := { line := 1, character := 1 },
tailPos := { line := 1, character := 3 },
type := Lean.Lsp.SemanticTokenType.function,
priority := 5 },
{ pos := { line := 1, character := 3 },
tailPos := { line := 1, character := 5 },
type := Lean.Lsp.SemanticTokenType.string,
priority := 5 }]
#guard handleOverlappingSemanticTokens sameEnd == sameEndExpected
#guard handleOverlappingSemanticTokens sameEnd.reverse == sameEndExpected
def overlapMiddle : Array AbsoluteLspSemanticToken := #[
{ pos := ⟨1, 1⟩
tailPos := ⟨1, 5⟩
type := .function },
{ pos := ⟨1, 3⟩
tailPos := ⟨1, 8⟩
type := .string },
]
def overlapMiddleExpected : Array AbsoluteLspSemanticToken := #[
{ pos := { line := 1, character := 1 },
tailPos := { line := 1, character := 3 },
type := Lean.Lsp.SemanticTokenType.function,
priority := 5 },
{ pos := { line := 1, character := 3 },
tailPos := { line := 1, character := 8 },
type := Lean.Lsp.SemanticTokenType.string,
priority := 5 }
]
#guard handleOverlappingSemanticTokens overlapMiddle == overlapMiddleExpected
#guard handleOverlappingSemanticTokens overlapMiddle.reverse == overlapMiddleExpected