@@ -109,7 +109,13 @@ pub fn initialize(
109109
110110 .codeActionProvider = .{ .bool = true },
111111
112- .renameProvider = .{ .bool = true },
112+ .renameProvider = .{
113+ .RenameOptions = .{
114+ .prepareProvider = true ,
115+ },
116+ },
117+
118+ .referencesProvider = .{ .bool = true },
113119
114120 .completionProvider = .{
115121 .triggerCharacters = &.{ "<" , "/" },
@@ -315,6 +321,35 @@ pub fn @"textDocument/codeAction"(
315321 return null ;
316322}
317323
324+ pub fn @"textDocument/prepareRename" (
325+ self : * Handler ,
326+ arena : std.mem.Allocator ,
327+ request : types.PrepareRenameParams ,
328+ ) error {OutOfMemory }! lsp .ResultType ("textDocument/prepareRename" ) {
329+ _ = arena ;
330+
331+ const doc = self .files .getPtr (request .textDocument .uri ) orelse return null ;
332+ const offset = lsp .offsets .positionToIndex (
333+ doc .src ,
334+ request .position ,
335+ self .offset_encoding ,
336+ );
337+
338+ const node_idx = findNode (doc , @intCast (offset ));
339+ if (node_idx == 0 ) return null ;
340+
341+ const node = doc .html .nodes [node_idx ];
342+ const it = node .startTagIterator (doc .src , doc .language );
343+
344+ const range = lsp .offsets .locToRange (doc .src , .{
345+ .start = it .name_span .start ,
346+ .end = it .name_span .end ,
347+ }, self .offset_encoding );
348+
349+ return .{
350+ .Range = range ,
351+ };
352+ }
318353pub fn @"textDocument/rename" (
319354 self : * Handler ,
320355 arena : std.mem.Allocator ,
@@ -350,28 +385,7 @@ pub fn @"textDocument/rename"(
350385 }
351386 if (err .node_idx != 0 ) break err .node_idx ;
352387 }
353- } else blk : {
354- // No match found in the error list, must navigate the AST
355- if (doc .html .nodes .len < 2 ) return null ;
356- var cur_idx : u32 = 1 ;
357- while (cur_idx != 0 ) {
358- const n = doc .html .nodes [cur_idx ];
359- if (n .open .start <= offset and n .open .end > offset ) {
360- break ;
361- }
362- if (n .close .end != 0 and n .close .start <= offset and n .close .end > offset ) {
363- break ;
364- }
365-
366- if (n .open .end <= offset and n .close .start > offset ) {
367- cur_idx = n .first_child_idx ;
368- } else {
369- cur_idx = n .next_idx ;
370- }
371- }
372-
373- break :blk cur_idx ;
374- };
388+ } else findNode (doc , @intCast (offset ));
375389
376390 const node = doc .html .nodes [node_idx ];
377391 var edits : []lsp.types.TextEdit = undefined ;
@@ -422,6 +436,83 @@ pub fn @"textDocument/rename"(
422436 };
423437}
424438
439+ pub fn @"textDocument/references" (
440+ self : * Handler ,
441+ arena : std.mem.Allocator ,
442+ request : types.ReferenceParams ,
443+ ) error {OutOfMemory }! lsp .ResultType ("textDocument/references" ) {
444+ const doc = self .files .getPtr (request .textDocument .uri ) orelse return null ;
445+ const offset = lsp .offsets .positionToIndex (
446+ doc .src ,
447+ request .position ,
448+ self .offset_encoding ,
449+ );
450+
451+ const node_idx = findNode (doc , @intCast (offset ));
452+ log .debug ("------ References request! (node: {}) ------" , .{node_idx });
453+ if (node_idx == 0 ) return null ;
454+
455+ const class = blk : {
456+ const node = doc .html .nodes [node_idx ];
457+ var it = node .startTagIterator (doc .src , doc .language );
458+ while (it .next (doc .src )) | attr | {
459+ if (std .ascii .eqlIgnoreCase (attr .name .slice (doc .src ), "class" )) {
460+ const value = attr .value orelse return null ;
461+ const slice = value .span .slice (doc .src );
462+ if (slice .len == 0 or slice [0 ] == '$' ) return null ;
463+ if (offset < value .span .start or offset >= value .span .end ) return null ;
464+
465+ const rel_offset = offset - value .span .start ;
466+
467+ var vit = std .mem .tokenizeScalar (u8 , slice , ' ' );
468+
469+ while (vit .next ()) | cls | {
470+ if (rel_offset < vit .index - cls .len ) return null ;
471+ if (vit .index > rel_offset ) {
472+ break :blk cls ;
473+ }
474+ } else return null ;
475+ }
476+ } else return null ;
477+ };
478+
479+ log .debug ("------ CLASS: '{s}' ------" , .{class });
480+
481+ var locations : std .ArrayListUnmanaged (lsp .types .Location ) = .empty ;
482+ for (doc .html .nodes ) | n | {
483+ switch (n .kind ) {
484+ .element , .element_void , .element_self_closing = > {},
485+ else = > continue ,
486+ }
487+
488+ var it = n .startTagIterator (doc .src , doc .language );
489+ outer : while (it .next (doc .src )) | attr | {
490+ if (std .ascii .eqlIgnoreCase (attr .name .slice (doc .src ), "class" )) {
491+ const value = attr .value orelse break :outer ;
492+ const slice = value .span .slice (doc .src );
493+ if (slice .len == 0 or slice [0 ] == '$' ) break :outer ;
494+ var vit = std .mem .tokenizeScalar (u8 , slice , ' ' );
495+ while (vit .next ()) | cls | {
496+ if (std .mem .eql (u8 , class , cls )) {
497+ const range = lsp .offsets .locToRange (doc .src , .{
498+ .start = value .span .start + vit .index - cls .len ,
499+ .end = value .span .start + vit .index ,
500+ }, self .offset_encoding );
501+
502+ try locations .append (arena , .{
503+ .uri = request .textDocument .uri ,
504+ .range = range ,
505+ });
506+ break :outer ;
507+ }
508+ }
509+ }
510+ }
511+ }
512+
513+ return locations .items ;
514+ }
515+
425516pub fn @"textDocument/completion" (
426517 self : * Handler ,
427518 arena : std.mem.Allocator ,
@@ -455,3 +546,26 @@ pub fn getRange(span: super.Span, src: []const u8) types.Range {
455546 .end = .{ .line = r .end .row , .character = r .end .col },
456547 };
457548}
549+
550+ // Returns a node index, 0 == not found
551+ pub fn findNode (doc : * const Document , offset : u32 ) u32 {
552+ if (doc .html .nodes .len < 2 ) return 0 ;
553+ var cur_idx : u32 = 1 ;
554+ while (cur_idx != 0 ) {
555+ const n = doc .html .nodes [cur_idx ];
556+ if (n .open .start <= offset and n .open .end > offset ) {
557+ break ;
558+ }
559+ if (n .close .end != 0 and n .close .start <= offset and n .close .end > offset ) {
560+ break ;
561+ }
562+
563+ if (n .open .end <= offset and n .close .start > offset ) {
564+ cur_idx = n .first_child_idx ;
565+ } else {
566+ cur_idx = n .next_idx ;
567+ }
568+ }
569+
570+ return cur_idx ;
571+ }
0 commit comments