|
15 | 15 | Location, |
16 | 16 | Position, |
17 | 17 | Range, |
| 18 | + TextEdit, |
18 | 19 | ) |
19 | 20 | from pygls.workspace import TextDocument |
20 | 21 |
|
@@ -330,7 +331,8 @@ def completions(self, line, character): |
330 | 331 | ), |
331 | 332 | ) |
332 | 333 | ) |
333 | | - return [] |
| 334 | + # Check CSS |
| 335 | + return self.get_css_class_name_completions(line, character) |
334 | 336 |
|
335 | 337 | def get_load_completions(self, match: Match, **kwargs): |
336 | 338 | prefix = match.group(1).split(" ")[-1] |
@@ -558,6 +560,58 @@ def resolve_completion(item: CompletionItem): |
558 | 560 |
|
559 | 561 | return item |
560 | 562 |
|
| 563 | + def _absolute_index_to_position(self, index: int) -> Position: |
| 564 | + """Translate an absolute character index into an LSP `Position`.""" |
| 565 | + running_total = 0 |
| 566 | + for line_no, text in enumerate(self.document.lines): |
| 567 | + line_length = len(text) |
| 568 | + if index <= running_total + line_length: |
| 569 | + return Position(line=line_no, character=index - running_total) |
| 570 | + running_total += line_length |
| 571 | + # Fallback to end of document if index is out of bounds |
| 572 | + last_line = len(self.document.lines) - 1 |
| 573 | + last_char = len(self.document.lines[last_line]) if last_line >= 0 else 0 |
| 574 | + return Position(line=last_line, character=last_char) |
| 575 | + |
| 576 | + def get_css_class_name_completions(self, line, character): |
| 577 | + # Flatten text to one line and remove Django template |
| 578 | + one_line_html = "".join(self.document.lines) |
| 579 | + one_line_html = re.sub( |
| 580 | + r"\{\%.*?\%\}", lambda m: " " * len(m.group(0)), one_line_html |
| 581 | + ) |
| 582 | + one_line_html = re.sub( |
| 583 | + r"\{\{.*?\}\}", lambda m: " " * len(m.group(0)), one_line_html |
| 584 | + ) |
| 585 | + |
| 586 | + abs_position = sum(len(self.document.lines[i]) for i in range(line)) + character |
| 587 | + class_attr_pattern = re.compile(r'class=["\']([^"\']*)["\']', re.DOTALL) |
| 588 | + |
| 589 | + for match in class_attr_pattern.finditer(one_line_html): |
| 590 | + start_idx, end_idx = match.span(1) |
| 591 | + |
| 592 | + if start_idx <= abs_position <= end_idx: |
| 593 | + class_value = match.group(1) |
| 594 | + relative_pos = abs_position - start_idx |
| 595 | + |
| 596 | + prefix_match = re.search(r"\b[\w-]*$", class_value[:relative_pos]) |
| 597 | + prefix = prefix_match.group(0) if prefix_match else "" |
| 598 | + |
| 599 | + start_index = abs_position - len(prefix) |
| 600 | + start_position = self._absolute_index_to_position(start_index) |
| 601 | + end_position = self._absolute_index_to_position(abs_position) |
| 602 | + replace_range = Range(start=start_position, end=end_position) |
| 603 | + |
| 604 | + return [ |
| 605 | + CompletionItem( |
| 606 | + label=class_name, |
| 607 | + text_edit=TextEdit(range=replace_range, new_text=class_name), |
| 608 | + ) |
| 609 | + for class_name in self.workspace_index.css_class_names |
| 610 | + if class_name.startswith(prefix) |
| 611 | + ] |
| 612 | + |
| 613 | + return [] |
| 614 | + |
561 | 615 | ################################################################################### |
562 | 616 | # Hover |
563 | 617 | ################################################################################### |
|
0 commit comments