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