2 files changed,
156 insertions(+),
76 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-08-17 14:46:05 +0300
Authored at:
2026-08-16 20:56:00 +0300
Change ID:
mmpnpunwusrzyqykvsysnvmluuoqssyk
Parent:
f68a2c6
A
internal/lsp/testdata/semantic-range.txtar
··· 1 +-- in.journal -- 2 +2024-01-15 test 3 + expenses:food $50 4 + 5 +2024-01-16 other 6 + expenses:drinks $20 7 + 8 +2024-01-17 done 9 + a $1 = $100 10 + b 11 + 12 +-- ranges.txt -- 13 +first-txn 0 1 14 +second-txn 3 4 15 +blank-only 2 2 16 +whole-doc 0 8 17 +inverted 4 2 18 + 19 +-- expect -- 20 +== first-txn == 21 +0:0+10 class 22 +0:11+4 property 23 +1:4+13 namespace 24 +1:19+1 type 25 +1:20+2 number 26 +== second-txn == 27 +3:0+10 class 28 +3:11+5 property 29 +4:2+15 namespace 30 +4:19+1 type 31 +4:20+2 number 32 +== blank-only == 33 +== whole-doc == 34 +0:0+10 class 35 +0:11+4 property 36 +1:4+13 namespace 37 +1:19+1 type 38 +1:20+2 number 39 +3:0+10 class 40 +3:11+5 property 41 +4:2+15 namespace 42 +4:19+1 type 43 +4:20+2 number 44 +6:0+10 class 45 +6:11+4 property 46 +7:2+1 namespace 47 +7:5+1 type 48 +7:6+1 number 49 +7:8+1 operator 50 +7:10+1 type 51 +7:11+3 number 52 +8:2+1 namespace 53 +== inverted ==
M
internal/lsp/textdocument_semantic_tokens_test.go
··· 59 59 } 60 60 } 61 61 62 -func TestServer_Semantic_EmptyDocument(t *testing.T) { 63 - srv := NewServer("test") 64 - srv.server.openDoc(uri.URI("file:///empty.journal"), "", 1, "journal") 65 - 66 - result, err := srv.server.SemanticTokensFull(t.Context(), &protocol.SemanticTokensParams{ 67 - TextDocument: protocol.TextDocumentIdentifier{URI: uri.URI("file:///empty.journal")}, 68 - }) 69 - if err != nil { 70 - t.Fatal(err) 71 - } 72 - if len(result.Data) != 0 { 73 - t.Errorf("expected empty data for empty doc, got %d values", len(result.Data)) 74 - } 75 -} 76 - 77 -func TestServer_Semantic_DocumentNotFound(t *testing.T) { 78 - result, err := NewServer("test").server.SemanticTokensFull(t.Context(), &protocol.SemanticTokensParams{ 79 - TextDocument: protocol.TextDocumentIdentifier{URI: uri.URI("file:///unknown.journal")}, 80 - }) 81 - if err != nil { 82 - t.Fatal(err) 83 - } 84 - if result == nil { 85 - t.Fatal("result is nil") 86 - } 87 - if result.Data != nil { 88 - t.Errorf("expected nil Data for unknown doc, got %v", result.Data) 89 - } 90 -} 91 - 92 -func TestServer_Semantic_Range(t *testing.T) { 93 - content := `2024-01-15 test 94 - expenses:food $50 95 - 96 -2024-01-16 other 97 - expenses:drinks $20 98 -` 99 - 100 - srv := NewServer("test") 101 - srv.server.openDoc(uri.URI("file:///test.journal"), content, 1, "journal") 102 - 103 - result, err := srv.server.SemanticTokensRange(t.Context(), &protocol.SemanticTokensRangeParams{ 104 - TextDocument: protocol.TextDocumentIdentifier{URI: uri.URI("file:///test.journal")}, 105 - Range: protocol.Range{ 106 - Start: protocol.Position{Line: 0, Character: 0}, 107 - End: protocol.Position{Line: 1, Character: 50}, 108 - }, 109 - }) 110 - if err != nil { 111 - t.Fatal(err) 112 - } 113 - if result == nil || len(result.Data) == 0 { 114 - t.Fatal("expected non-empty tokens for range") 115 - } 116 - 117 - // decode and assert every token is inside the requested line range 118 - line, col := 0, 0 119 - for i := 0; i+4 < len(result.Data); i += 5 { 120 - dl, dc := result.Data[i], result.Data[i+1] 121 - if dl > 0 { 122 - col = 0 123 - } 124 - line += int(dl) 125 - col += int(dc) 126 - if line > 1 { 127 - t.Fatalf("token at line %d outside requested range [0,1]", line) 128 - } 129 - } 130 -} 131 - 132 62 func TestSemanticTokensEdits(t *testing.T) { 133 63 tests := map[string]struct{ old, new []uint32 }{ 134 64 "identical": {[]uint32{1, 2, 3}, []uint32{1, 2, 3}}, ··· 156 86 } 157 87 } 158 88 159 -// applySemEdits applies LSP semantic token edits to a client-side copy of data. 160 -func applySemEdits(data []uint32, edits []protocol.SemanticTokensEdit) []uint32 { 161 - out := slices.Clone(data) 162 - for _, e := range edits { 163 - out = append(append(out[:e.Start], e.Data...), out[e.Start+e.DeleteCount:]...) 89 +func TestServer_Semantic_EmptyDocument(t *testing.T) { 90 + srv := NewServer("test") 91 + srv.server.openDoc(uri.URI("file:///empty.journal"), "", 1, "journal") 92 + result, err := srv.server.SemanticTokensFull(t.Context(), &protocol.SemanticTokensParams{ 93 + TextDocument: protocol.TextDocumentIdentifier{URI: uri.URI("file:///empty.journal")}, 94 + }) 95 + if err != nil { 96 + t.Fatal(err) 97 + } 98 + if len(result.Data) != 0 { 99 + t.Errorf("expected empty data for empty doc, got %d values", len(result.Data)) 100 + } 101 +} 102 + 103 +func TestServer_Semantic_DocumentNotFound(t *testing.T) { 104 + result, err := NewServer("test").server.SemanticTokensFull(t.Context(), &protocol.SemanticTokensParams{ 105 + TextDocument: protocol.TextDocumentIdentifier{URI: uri.URI("file:///unknown.journal")}, 106 + }) 107 + if err != nil { 108 + t.Fatal(err) 164 109 } 165 - return out 110 + if result == nil { 111 + t.Fatal("result is nil") 112 + } 113 + if result.Data != nil { 114 + t.Errorf("expected nil Data for unknown doc, got %v", result.Data) 115 + } 166 116 } 167 117 168 118 func TestGolden_SemanticTokens(t *testing.T) { ··· 180 130 } 181 131 } 182 132 133 +func TestGolden_SemanticTokensRange(t *testing.T) { 134 + ar := golden.Read(t, "semantic-range") 135 + in := ar.Get("in.journal") 136 + 137 + t.Run("no-overlap", func(t *testing.T) { 138 + assertGoldenNoOverlap(t, "semantic-range", ar) 139 + }) 140 + 141 + t.Run("golden", func(t *testing.T) { 142 + u := uri.URI("file:///test.journal") 143 + srv := NewServer("test") 144 + srv.server.openDoc(u, string(in), 1, "journal") 145 + 146 + var out strings.Builder 147 + for line := range strings.SplitSeq(string(ar.Get("ranges.txt")), "\n") { 148 + if line == "" { 149 + continue 150 + } 151 + var name string 152 + var start, end uint32 153 + if _, err := fmt.Sscanf(line, "%s %d %d", &name, &start, &end); err != nil { 154 + t.Fatalf("ranges.txt: %q: %v", line, err) 155 + } 156 + 157 + res, err := srv.server.SemanticTokensRange(t.Context(), &protocol.SemanticTokensRangeParams{ 158 + TextDocument: protocol.TextDocumentIdentifier{URI: u}, 159 + Range: protocol.Range{ 160 + Start: protocol.Position{Line: start}, 161 + End: protocol.Position{Line: end}, 162 + }, 163 + }) 164 + if err != nil { 165 + t.Fatal(err) 166 + } 167 + tokens := decodeSemTokens(res.Data) 168 + for _, tok := range tokens { 169 + if tok.line < start || tok.line > end { 170 + t.Fatalf("%s: token %d:%d outside range lines [%d,%d]", name, tok.line, tok.col, start, end) 171 + } 172 + } 173 + fmt.Fprintf(&out, "== %s ==\n", name) 174 + out.WriteString(renderSemanticTokens(tokens)) 175 + } 176 + golden.Assert(t, ar, out.String()) 177 + }) 178 +} 179 + 183 180 func TestGolden_SemanticTokensDelta(t *testing.T) { 184 181 for _, tt := range []string{"semantic-delta-edit", "semantic-delta-nochange", "semantic-delta-stale"} { 185 182 ar := golden.Read(t, tt) ··· 243 240 if want := encodeSemTokens(tokSem(finalText)); !slices.Equal(client, want) { 244 241 t.Errorf("delta flow produced %d elems, want %d", len(client), len(want)) 245 242 } 243 + 246 244 out.WriteString(renderSemanticTokens(tokSem(finalText))) 247 245 golden.Assert(t, ar, out.String()) 248 246 }) ··· 300 298 return b.String() 301 299 } 302 300 301 +func decodeSemTokens(data []uint32) []semanticToken { 302 + var out []semanticToken 303 + line, col := 0, 0 304 + for i := 0; i+4 < len(data); i += 5 { 305 + if data[i] > 0 { 306 + line += int(data[i]) 307 + col = int(data[i+1]) 308 + } else { 309 + col += int(data[i+1]) 310 + } 311 + out = append(out, semanticToken{ 312 + line: uint32(line), 313 + col: uint32(col), 314 + length: data[i+2], 315 + tokenType: data[i+3], 316 + modifiers: data[i+4], 317 + }) 318 + } 319 + return out 320 +} 321 + 303 322 func tokSem(content []byte) []semanticToken { 304 323 c := string(content) 305 324 return tokenizeForSemantics(c, parseJournalStr(c)) ··· 344 363 for b.Loop() { 345 364 _ = semanticTokensEdits(old, new) 346 365 } 366 +} 367 + 368 +func applySemEdits(data []uint32, edits []protocol.SemanticTokensEdit) []uint32 { 369 + out := slices.Clone(data) 370 + for _, e := range edits { 371 + out = append(append(out[:e.Start], e.Data...), out[e.Start+e.DeleteCount:]...) 372 + } 373 + return out 347 374 } 348 375 349 376 func openJournal(t testing.TB, path string) string {