agentic-labs/lsproxy-tutorial
0
1import marimo2 3__generated_with = "0.9.14"4app = marimo.App(width="medium")5 6 7@app.cell8def __():9 import requests10 import json11 import sys12 from typing import Dict, Any, Optional, List13 14 from lsproxy import GetReferencesRequest, FileRange, Position15 16 import marimo as mo17 return (18 Any,19 Dict,20 FileRange,21 GetReferencesRequest,22 List,23 Optional,24 Position,25 json,26 mo,27 requests,28 sys,29 )30 31 32@app.cell33def __(mo):34 mo.md("""### Welcome to the `lsproxy` tutorial! We'll be showing you how you can use `lsproxy` to easily navigate and search another codebase using python. Let's get started!\n> We will be using an open-source repo to demonstrate `lsproxy`. We chose [Trieve](https://github.com/devflowinc/trieve), a rust-based infrastructure solution for search, recommendations and RAG. They have rust for their backend, and typescript to run multiple frontend interfaces. We love their product and their team, check them out!""")35 return36 37 38@app.cell39def __(mo):40 mo.md("""<div style="height: 50px;"></div>""")41 return42 43 44@app.cell45def __(mo):46 # The first step is to create our API client47 from lsproxy import Lsproxy48 49 api_client = Lsproxy()50 mo.show_code()51 return Lsproxy, api_client52 53 54@app.cell55def __(mo):56 mo.md("""Other than starting the `lsproxy` docker container, no initialization is required to use `lsproxy`. Here, our "initialization" is just reading in the files to make the tutorial easier to navigate! Please click the button below to get started.""")57 return58 59 60@app.cell61def __(mo):62 start_button = mo.ui.run_button(label="Click to initialize")63 start_button64 return (start_button,)65 66 67@app.cell68def __(get_files, mo, start_button):69 # Reads all the files in the repo on initialization70 mo.stop(not start_button.value)71 file_symbol_dict = get_files()72 return (file_symbol_dict,)73 74 75@app.cell76def __(mo):77 mo.md("""<div style="height: 100px;"></div>""")78 return79 80 81@app.cell82def __(file_symbol_dict, mo):83 mo.stop(not file_symbol_dict)84 85 mo.md(86 """### `Example 1: Exploring symbols and their references in a file`\nYou'll see how easy it is to:\n\n- Get symbol definitions from a file.\n- Read the source code for any symbol.\n- Find references to the symbol across the codebase\n\n<p>Also note that we are only showing typescript and rust in this example, but we also support python!</p>\n---\n"""87 )88 return89 90 91@app.cell92def __(selections_ex1):93 selections_ex194 return95 96 97@app.cell98def __(code_language_select_ex1, file_dropdown_ex1, mo):99 # This is just for controlling the flow of this tutorial100 mo.stop(not file_dropdown_ex1.value)101 selected_file_first_time = True102 code_language_ex1 = code_language_select_ex1.value103 selected_file_ex1 = file_dropdown_ex1.value104 return code_language_ex1, selected_file_ex1, selected_file_first_time105 106 107@app.cell108def __(code_language_ex1, mo, selected_file_first_time):109 # This is just for controlling the flow of this tutorial110 mo.stop(not selected_file_first_time)111 112 mo.md(113 f"Note that you selected a file in {code_language_ex1}, but `lsproxy` wraps language servers for all the supported languages, and routes your request to the right one, so you don't have to worry about configuring servers for each language. Go ahead and try a different language!"114 )115 return116 117 118@app.cell119def __(api_client, mo, selected_file_ex1):120 # Retrieving the symbols defined in a file is just a single call121 symbols_ex1 = api_client.definitions_in_file(selected_file_ex1)122 123 mo.show_code()124 return (symbols_ex1,)125 126 127@app.cell128def __(mo, symbols_ex1):129 # Pack the data from the symbols into a tabular format130 table_data_ex1 = [131 {132 "name": symbol.name,133 "kind": symbol.kind,134 "start_line": symbol.identifier_position.position.line,135 "start_character": symbol.identifier_position.position.character,136 "num_lines": symbol.range.end.line - symbol.range.start.line + 1,137 "index": i,138 }139 for i, symbol in enumerate(symbols_ex1)140 ]141 142 # Create the table element to display143 symbol_table_ex1 = mo.ui.table(144 data=table_data_ex1,145 page_size=10,146 selection="single",147 label="Now, select a symbol to view code and references",148 )149 # Display the table150 symbol_table_ex1151 return symbol_table_ex1, table_data_ex1152 153 154@app.cell155def __(mo, symbol_table_ex1, symbols_ex1):156 mo.stop(not symbol_table_ex1.value)157 selected_symbol_ex1 = symbols_ex1[symbol_table_ex1.value[0].get("index")]158 return (selected_symbol_ex1,)159 160 161@app.cell162def __(163 FileRange,164 GetReferencesRequest,165 api_client,166 mo,167 selected_file_ex1,168 selected_symbol_ex1,169):170 # Read the source code for a particular range in a file by just asking for it!171 file_range_ex1 = FileRange(172 path=selected_file_ex1,173 start=selected_symbol_ex1.range.start,174 end=selected_symbol_ex1.range.end,175 )176 source_code_ex1 = api_client.read_source_code(file_range_ex1).source_code177 178 # Get references to the symbol and optionally include context lines surrounding the usage179 reference_request_ex1 = GetReferencesRequest(180 identifier_position=selected_symbol_ex1.identifier_position,181 include_code_context_lines=2,182 )183 reference_results_ex1 = api_client.find_references(reference_request_ex1)184 viewed_symbol = True185 mo.show_code()186 return (187 file_range_ex1,188 reference_request_ex1,189 reference_results_ex1,190 source_code_ex1,191 viewed_symbol,192 )193 194 195@app.cell196def __(197 code_language_ex1,198 mo,199 pretty_format_code_result,200 pretty_format_reference_results,201 reference_results_ex1,202 source_code_ex1,203):204 # Format the code and reference results for display205 code_text_ex1 = pretty_format_code_result(source_code_ex1, code_language_ex1)206 reference_text_ex1 = pretty_format_reference_results(207 reference_results_ex1, code_language_ex1208 )209 210 # Display the code and reference text211 mo.callout(212 mo.vstack(213 [214 mo.md(code_text_ex1),215 mo.md(reference_text_ex1),216 ]217 )218 )219 return code_text_ex1, reference_text_ex1220 221 222@app.cell223def __(mo, viewed_symbol):224 mo.stop(not viewed_symbol)225 example_2 = mo.ui.run_button(label="Click to move on to example 2: Exploring connections between files", full_width=True)226 example_2227 return (example_2,)228 229 230@app.cell231def __(mo):232 mo.md("""<div style="height: 100px;"></div>""")233 return234 235 236@app.cell237def __(example_2, mo):238 mo.stop(not example_2.value)239 ex2_unlocked = True240 return (ex2_unlocked,)241 242 243@app.cell244def __(ex2_unlocked, mo, selections_2):245 mo.stop(not ex2_unlocked)246 mo.vstack([247 mo.md("""### Example 2: Exploring connections between files\nThe examples above are similar to the kind of functionality you can find in your IDE, but having everything accessible with easy python functions means that you can compose these operations to be much more powerful.\n\nIn this example, we show:\n\n- Finding all the files that reference a given file\n- Tagging each file with the symbols it references\n\n---"""),248 selections_2,249 ])250 return251 252 253@app.cell254def __(ex2_unlocked, file_dropdown_2, mo):255 mo.stop(not ex2_unlocked)256 # Pull the symbols inside a file257 selected_file_ex2 = file_dropdown_2.value258 return (selected_file_ex2,)259 260 261@app.cell262def __(api_client, mo, selected_file_ex2):263 # As before we can get all of the symbols from a file264 symbols_ex2 = api_client.definitions_in_file(selected_file_ex2)265 mo.show_code()266 return (symbols_ex2,)267 268 269@app.cell270def __(271 GetReferencesRequest,272 api_client,273 mo,274 selected_file_ex2,275 symbols_ex2,276):277 # But now we can repeatedly look for references on EVERY symbol in the file and build up a graph of the references278 referenced_symbols_in_file_dict = {}279 for symbol in mo.status.progress_bar(280 symbols_ex2, title="Symbols processed", remove_on_exit=True281 ):282 reference_request_ex2 = GetReferencesRequest(283 identifier_position=symbol.identifier_position,284 )285 references_ex2 = api_client.find_references(reference_request_ex2).references286 287 # Save which symbols were referenced by which file288 for ref in references_ex2:289 referencing_file = ref.path290 if referencing_file != selected_file_ex2:291 referenced_symbols_in_file_dict.setdefault(292 (selected_file_ex2, referencing_file), set()293 ).add(symbol.name)294 mo.show_code()295 return (296 ref,297 reference_request_ex2,298 referenced_symbols_in_file_dict,299 references_ex2,300 referencing_file,301 symbol,302 )303 304 305@app.cell306def __(ex2_unlocked, mo):307 mo.stop(not ex2_unlocked)308 309 mo.md(310 "From this information we can build a simple graph showing how a file's symbols are referenced by other files in the codebase."311 )312 return313 314 315@app.cell316def __(generate_reference_diagram, mo, referenced_symbols_in_file_dict):317 if not referenced_symbols_in_file_dict:318 mermaid_diagram = generate_reference_diagram("No external references found")319 else:320 mermaid_diagram = generate_reference_diagram(referenced_symbols_in_file_dict)321 diagram_shown = True322 mo.mermaid(mermaid_diagram)323 return diagram_shown, mermaid_diagram324 325 326@app.cell327def __(mo):328 mo.md("""<div style="height: 100px;"></div>""")329 return330 331 332@app.cell333def __(diagram_shown, mo):334 mo.stop(not diagram_shown)335 example_3 = mo.ui.run_button(label="Click to move on to example 3: Analyzing a change diff with call hierarchy.", full_width=True)336 example_3337 return (example_3,)338 339 340@app.cell341def __(example_3, mo):342 mo.stop(not example_3.value)343 mo.md(344 """### Example 3 (Advanced): Analyzing a change diff with call hierarchy.\n We can compose definitions and references to identify the full code paths that are affected by a particular change, and uncover ripple effects through the codebase.\n\n---"""345 )346 return347 348 349@app.cell350def __(example_3, mo):351 mo.stop(not example_3.value)352 import subprocess353 import io354 from pydantic import BaseModel355 mo.md("""Let's start with a diff of a change to the deletion logic in Trieve in this [PR](https://github.com/devflowinc/trieve/pull/2649)""")356 return BaseModel, io, subprocess357 358 359@app.cell360def __(mo, subprocess):361 parent_commit = "1910d6867877bfdd64ca822e266372335392a8be"362 363 # Load in the diff364 diff_text = subprocess.check_output(365 ["git", "diff", parent_commit], cwd="./trieve"366 ).decode("utf-8")367 368 mo.show_code(f"Output: Diff has {len(diff_text.splitlines())} lines")369 return diff_text, parent_commit370 371 372@app.cell373def __(example_3, mo):374 mo.stop(not example_3.value)375 mo.md("""First, we extract affected lines from the diff text.""")376 return377 378 379@app.cell380def __(Dict, List, Tuple, diff_text, io, mo):381 from unidiff import PatchSet382 383 def parse_diff(diff_text) -> Tuple[Dict[str, List[int]], str]:384 patch = PatchSet(io.StringIO(diff_text))385 affected_lines = {}386 for patched_file in patch:387 for hunk in patched_file:388 for line in hunk:389 if line.is_added:390 affected_lines.setdefault(patched_file.path, set()).add(391 line.target_line_no392 )393 elif line.is_removed:394 affected_lines.setdefault(patched_file.path, set()).add(395 line.source_line_no396 )397 return affected_lines398 399 400 affected_lines = parse_diff(diff_text)401 402 mo.show_code(403 f"Output: Diff contains {sum([len(lines) for lines in affected_lines.values()])} changed lines in {len(affected_lines)} files."404 )405 return PatchSet, affected_lines, parse_diff406 407 408@app.cell409def __(example_3, mo):410 mo.stop(not example_3.value)411 mo.md("""Then we define logic to \n\n 1. Find symbol definitions containing affected lines\n\n 2. Find references to these symbols.\n\n 3. Repeat with lines containing references, until we reach the end.\n\nWe also save the symbols that are direct parents of affected lines, so we can distinguish them from the symbols indirectly affected by the change.""")412 return413 414 415@app.cell416def __(BaseModel, GetReferencesRequest, List, Set, Tuple, api_client, mo):417 from lsproxy import FilePosition418 419 420 class HierarchyItem(BaseModel):421 name: str422 kind: str423 defined_at: FilePosition424 source_code: str425 426 def __hash__(self) -> int:427 return hash(428 (429 self.defined_at.path,430 self.defined_at.position.line,431 self.defined_at.position.character,432 )433 )434 435 436 def get_symbols_containing_positions(437 target_positions: List[FilePosition],438 ) -> List[HierarchyItem]:439 file_path = target_positions[0].path440 441 #######################442 ### Get definitions ###443 #######################444 symbols = api_client.definitions_in_file(file_path)445 symbols_containing_position = {446 HierarchyItem(447 name=symbol.name,448 kind=symbol.kind,449 defined_at=symbol.identifier_position,450 source_code=api_client.read_source_code(symbol.range).source_code,451 )452 for symbol in symbols453 for target_position in target_positions454 if symbol.range.contains(target_position)455 }456 return symbols_containing_position457 458 459 def propagate_changes_through_codebase(symbols_changed_directly: List[FilePosition]):460 """461 Compute the chain of code symbols that touch the code at the starting positions.462 """463 nodes: Set[HierarchyItem] = set()464 edges: Set[Tuple[HierarchyItem, HierarchyItem]] = set()465 466 # Initialize with symbols that contain the starting positions467 stack = list(symbols_changed_directly)468 469 while stack:470 symbol = stack.pop()471 472 # If we've already processesed this symbol skip it473 if symbol in nodes:474 continue475 nodes.add(symbol)476 477 # For each symbol we find all its references478 references = api_client.find_references(479 GetReferencesRequest(480 identifier_position=symbol.defined_at,481 include_declaration=False,482 )483 ).references484 485 # Group them by file486 references_by_file = {}487 for ref in references:488 references_by_file.setdefault(ref.path, []).append(ref)489 490 # And then find symbols that contain the references so we can keep processing491 related_symbols = [492 sym493 for refs in references_by_file.values()494 for sym in get_symbols_containing_positions(refs)495 ]496 497 for related_symbol in related_symbols:498 if related_symbol != symbol:499 edges.add((symbol, related_symbol))500 stack.append(related_symbol)501 502 return nodes, edges503 504 505 mo.show_code()506 return (507 FilePosition,508 HierarchyItem,509 get_symbols_containing_positions,510 propagate_changes_through_codebase,511 )512 513 514@app.cell515def __(516 FilePosition,517 Position,518 affected_lines,519 api_client,520 get_symbols_containing_positions,521 mo,522 propagate_changes_through_codebase,523):524 affected_files = list(affected_lines.keys())525 lsp_files = api_client.list_files()526 affected_code_files = filter(lambda file: file in lsp_files, affected_files)527 528 symbols_changed_directly = set()529 for file in affected_code_files:530 # For all the affected lines, we figure out what symbol they belong to531 affected_positions = [532 FilePosition(path=file, position=Position(line=line, character=0))533 for line in affected_lines[file]534 ]535 symbols_changed_directly.update(get_symbols_containing_positions(affected_positions))536 537 # And then recursively follow the affected symbol through the codebase by following references538 all_nodes, all_edges = propagate_changes_through_codebase(symbols_changed_directly)539 540 mo.show_code()541 return (542 affected_code_files,543 affected_files,544 affected_positions,545 all_edges,546 all_nodes,547 file,548 lsp_files,549 symbols_changed_directly,550 )551 552 553@app.cell554def __(555 all_edges,556 all_nodes,557 hierarchy_to_mermaid,558 mo,559 symbols_changed_directly,560):561 mm = hierarchy_to_mermaid(all_nodes, all_edges, symbols_changed_directly)562 mo.vstack([563 mo.md("### Call graph of the code affected by the change.\n #### The white nodes are present in the diff, while the red ones are affected indirectly."),564 mo.mermaid(mm)565 ])566 return (mm,)567 568 569@app.cell570def __(HierarchyItem, Set, Tuple):571 def hierarchy_to_mermaid(572 nodes: Set[HierarchyItem],573 edges: Set[Tuple[HierarchyItem, HierarchyItem]],574 symbols_changed_directly: Set[HierarchyItem],575 ) -> str:576 """577 Convert hierarchy nodes and edges to a Mermaid diagram string with subgraphs by file.578 Uses hash codes as node identifiers. Nodes that were changed directly are colored red.579 580 Args:581 nodes: Set of HierarchyItem objects representing code symbols582 edges: Set of tuples containing (from_symbol, to_symbol) relationships583 symbols_changed_directly: Set of HierarchyItem objects that were changed directly584 585 Returns:586 str: Mermaid diagram representation of the hierarchy with file-based subgraphs587 """588 mermaid_lines = [589 "%%{",590 " init: {",591 " 'flowchart': {",592 " 'rankSpacing': 100,", # Increase vertical space between ranks593 " 'nodeSpacing': 50,", # Increase horizontal space between nodes594 " 'padding': 20", # Add padding around the entire diagram595 " }",596 " }",597 "}%%",598 "graph TD",599 ]600 601 # Track nodes that need red styling602 direct_node_ids = set()603 indirect_node_ids = set()604 605 # Group nodes by file606 nodes_by_file = {}607 for node in nodes:608 file_path = node.defined_at.path609 if file_path not in nodes_by_file:610 nodes_by_file[file_path] = []611 nodes_by_file[file_path].append(node)612 613 # Track node IDs that need to be colored red614 if node in symbols_changed_directly:615 direct_node_ids.add(f"node{abs(hash(node))}")616 else:617 indirect_node_ids.add(f"node{abs(hash(node))}")618 619 # Create subgraphs for each file620 for file_idx, (file_path, file_nodes) in enumerate(nodes_by_file.items()):621 # Create subgraph with unique ID622 subgraph_id = f"subgraph_{file_idx}"623 mermaid_lines.append(f" subgraph {subgraph_id}[{file_path}]")624 625 # Add nodes for this file626 for node in file_nodes:627 # Escape quotes and special characters in names628 escaped_name = node.name.replace('"', '\\"')629 # Add kind as a suffix in italics630 label = f'"{escaped_name}<br><i>{node.kind}</i>"'631 # Use absolute value of hash to ensure positive ID632 node_id = f"node{abs(hash(node))}"633 mermaid_lines.append(f" {node_id}[{label}]")634 635 # Close subgraph636 mermaid_lines.append(" end")637 638 # Add edges using hash IDs (outside subgraphs)639 for from_node, to_node in edges:640 from_id = f"node{abs(hash(from_node))}"641 to_id = f"node{abs(hash(to_node))}"642 mermaid_lines.append(f" {from_id} --> {to_id}")643 644 # Add styling for red nodes645 for node_id in indirect_node_ids:646 mermaid_lines.append(f" style {node_id} fill:#ffcccc,color:#000")647 for node_id in direct_node_ids:648 mermaid_lines.append(f" style {node_id} fill:#ffffff,color:#000")649 650 return "\n".join(mermaid_lines)651 return (hierarchy_to_mermaid,)652 653 654@app.cell655def __(affected_lines, all_nodes, mo):656 diff_files = set(affected_lines.keys())657 call_hierarchy_files = set([n.defined_at.path for n in all_nodes])658 affected_files_not_in_diff = call_hierarchy_files - diff_files659 affected_files_not_in_diff_str = '\n'.join([f'{i+1}. {f}' for i, f in enumerate(affected_files_not_in_diff)])660 mo.md(f"We now see code paths crossing {len(affected_files_not_in_diff)} files that are not in the diff:\n\n{affected_files_not_in_diff_str}")661 return (662 affected_files_not_in_diff,663 affected_files_not_in_diff_str,664 call_hierarchy_files,665 diff_files,666 )667 668 669@app.cell670def __(mo):671 mo.md("""<div style="height: 400px;"></div>""")672 return673 674 675@app.cell676def __(mo):677 mo.md("""Thanks for trying `lsproxy`! See the README on our [github repo](https://github.com/agentic-labs/lsproxy) to run on your own code. Or if you want to play with the code in this example, you can use:\n\n```./examples/run.sh --edit```""")678 return679 680 681@app.cell682def __():683 # Appendix A: UI code to run the example684 return685 686 687@app.cell688def __(create_dropdowns, create_selector_dict, file_symbol_dict, mo):689 # UI Elements for the first example690 js_dropdown_1, rs_dropdown_1 = create_dropdowns(file_symbol_dict, "server/src/handlers/chunk_handler.rs: (65 symbols)")691 selector_dict_1 = create_selector_dict(js_dropdown_1, rs_dropdown_1)692 code_language_select_ex1 = mo.ui.radio(options=["typescript", "rust"], value="rust")693 return (694 code_language_select_ex1,695 js_dropdown_1,696 rs_dropdown_1,697 selector_dict_1,698 )699 700 701@app.cell702def __(code_language_select_ex1, mo, selector_dict_1):703 # Combining UI selections for the first example704 file_dropdown_ex1 = selector_dict_1[code_language_select_ex1.value]705 selections_ex1 = mo.hstack(706 [file_dropdown_ex1, code_language_select_ex1],707 gap=2,708 justify="end",709 )710 return file_dropdown_ex1, selections_ex1711 712 713@app.cell714def __(create_dropdowns, create_selector_dict, file_symbol_dict, mo):715 # UI Elements for the second example716 js_dropdown_2, rs_dropdown_2 = create_dropdowns(file_symbol_dict, "server/src/handlers/analytics_handler.rs: (15 symbols)")717 selector_dict_2 = create_selector_dict(js_dropdown_2, rs_dropdown_2)718 submit_button_2 = mo.ui.run_button(label="Find referenced files")719 code_language_select_ex2 = mo.ui.radio(options=["typescript", "rust"], value="rust")720 return (721 code_language_select_ex2,722 js_dropdown_2,723 rs_dropdown_2,724 selector_dict_2,725 submit_button_2,726 )727 728 729@app.cell730def __(code_language_select_ex2, mo, selector_dict_2):731 # Combining UI selections for the second example732 file_dropdown_2 = selector_dict_2[code_language_select_ex2.value]733 selections_2 = mo.hstack(734 [735 file_dropdown_2,736 code_language_select_ex2,737 ],738 gap=2,739 justify="end",740 )741 return file_dropdown_2, selections_2742 743 744@app.cell745def __():746 # Appendix B: Helper functions to create the UI code747 return748 749 750@app.cell751def __(api_client, mo):752 def get_files():753 file_dict = {}754 with mo.status.spinner():755 files = api_client.list_files()756 for file in mo.status.progress_bar(757 files, title="Files processed", remove_on_exit=True758 ):759 symbols = api_client.definitions_in_file(file)760 file_dict[file] = symbols761 return file_dict762 return (get_files,)763 764 765@app.cell766def __(create_lang_dropdown):767 def create_dropdowns(file_dict, value = None):768 file_with_symbol_count = [769 (file, len(symbols))770 for file, symbols in file_dict.items()771 if len(symbols) > 0772 ]773 file_with_symbol_count = sorted(774 file_with_symbol_count, key=lambda item: -item[1]775 )776 js_dropdown = create_lang_dropdown(777 file_with_symbol_count,778 ["ts", "tsx", "js", "jsx"],779 "Select a typescript/javascript file ->", None780 )781 rs_dropdown = create_lang_dropdown(782 file_with_symbol_count, ["rs"], "Select a rust file ->", value783 )784 return js_dropdown, rs_dropdown785 return (create_dropdowns,)786 787 788@app.cell789def __(mo):790 def create_lang_dropdown(file_symbol_dict, endings, label, value):791 file_options = {792 f"{file}: ({symbols} symbols)": file793 for file, symbols in file_symbol_dict794 if file.split(".")[-1] in endings795 }796 if value:797 return mo.ui.dropdown(options=file_options, label=label, value=value)798 else:799 return mo.ui.dropdown(options=file_options, label=label)800 return (create_lang_dropdown,)801 802 803@app.cell804def __():805 def create_selector_dict(js_dropdown, rs_dropdown):806 return {807 "typescript": js_dropdown,808 "rust": rs_dropdown,809 }810 return (create_selector_dict,)811 812 813@app.cell814def __():815 # Appendix C: Formatting functions for the text and mermaid charts816 return817 818 819@app.cell820def __():821 def pretty_format_code_result(code_result, code_language):822 return f"""### `Code`\n---\n```{code_language}\n\n{code_result}\n```\n"""823 return (pretty_format_code_result,)824 825 826@app.cell827def __():828 def pretty_format_reference_results(reference_results, code_language):829 # Header or no references830 ref_text = (831 ["\n### `References`\n---\n"]832 if reference_results.references833 else ["\n---\n### `No references found`"]834 )835 refs = {}836 for ref, context in zip(837 reference_results.references, reference_results.context838 ):839 # Split the code into it's lines and add an indicator for where the reference is840 code = context.source_code.split("\n")841 line_nums = range(context.range.start.line, context.range.end.line + 1)842 before_reference = filter(843 lambda num_code: num_code[0] < ref.position.line + 1,844 zip(line_nums, code),845 )846 after_reference = filter(847 lambda num_code: num_code[0] > ref.position.line,848 zip(line_nums, code),849 )850 code_with_line_nums = (851 list(before_reference)852 + [("", "_" * ref.position.character + "^")]853 + list(after_reference)854 )855 856 # Extend the list for the file with the new the (line_num, code) plus a separator857 file = ref.path858 refs.setdefault(file, []).extend(code_with_line_nums)859 refs[file].append(("@@@@@", "-----"))860 861 # For each file862 for ref_file, ref_lines in refs.items():863 ref_text.append(f"**{ref_file}**\n\n```{code_language}")864 ref_text.extend([f"{num:5}: {line}" for num, line in ref_lines])865 ref_text.append(f"```\n\n")866 return "\n".join(ref_text)867 return (pretty_format_reference_results,)868 869 870@app.cell871def __():872 def generate_reference_diagram(dependencies: dict, max_chars: int = 28) -> str:873 """874 Convert a dictionary of file dependencies and their referenced symbols into a Mermaid diagram string.875 Arrows point from referenced file back to source file through reference nodes.876 Args:877 dependencies: Dict where keys are tuples of (defined_file, referenced_file) and values are sets of referenced symbols878 OR a string representing the root file path when there are no dependencies879 max_chars: Maximum length for displayed file paths, truncating from left if needed880 Returns:881 String containing the Mermaid diagram definition882 """883 884 def get_display_name(file_path: str) -> str:885 """Get display name for a file, truncating from left if needed."""886 if len(file_path) <= max_chars:887 return file_path888 return "..." + file_path[-(max_chars - 3) :]889 890 # Handle case where dependencies is just a root file string891 if isinstance(dependencies, str):892 return f"""graph LR893 root["{dependencies}"]894 classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px,color:#000;895 classDef source fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:#000;896 class root source;"""897 898 if not dependencies:899 return "graph LR\n %% No dependencies to display"900 901 mermaid_lines = ["graph LR"]902 # Add styling with reduced padding903 mermaid_lines.extend(904 [905 " %% Styling",906 " classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px,color:#000,max-width:none,text-overflow:clip,padding:0px;",907 " classDef source fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:#000,max-width:none,text-overflow:clip,padding:0px;",908 " classDef reference fill:#e8e7ff,stroke:#6b69d6,stroke-width:2px,color:#000,max-width:none,text-overflow:clip,padding:0px;",909 ]910 )911 912 # Collect all unique files and create nodes913 unique_files = set()914 for defined_file, referenced_file in dependencies.keys():915 unique_files.add(defined_file)916 unique_files.add(referenced_file)917 918 # Create nodes for each unique file919 node_names = {}920 for idx, file in enumerate(unique_files):921 node_name = f"n{idx}"922 node_names[file] = node_name923 display_name = get_display_name(file)924 clean_name = display_name.replace('"', """)925 mermaid_lines.append(f' {node_name}["{clean_name}"]')926 927 # Create reference nodes and connections928 for idx, ((defined_file, referenced_file), symbols) in enumerate(929 dependencies.items()930 ):931 from_node = node_names[defined_file]932 to_node = node_names[referenced_file]933 ref_node = f"ref{idx}"934 935 # Clean and truncate symbols936 cleaned_symbols = []937 for symbol in sorted(symbols):938 clean_symbol = str(symbol)939 clean_symbol = clean_symbol.replace('"', """)940 clean_symbol = clean_symbol.replace("<", "<")941 clean_symbol = clean_symbol.replace(">", ">")942 if len(clean_symbol) > 20:943 clean_symbol = clean_symbol[:17] + "..."944 cleaned_symbols.append(clean_symbol)945 946 # Create symbol display with limited number of examples947 symbols_display = "<br/>" + "<br/>".join(cleaned_symbols)948 if len(cleaned_symbols) > 5:949 symbols_display = (950 "<br/>" + "<br/>".join(cleaned_symbols[:5]) + "<br/>..."951 )952 953 # Add reference node and connections954 ref_node_def = f' {ref_node}["{len(symbols)} refs{symbols_display}"]'955 mermaid_lines.append(ref_node_def)956 mermaid_lines.append(f" {to_node} --> {ref_node} --> {from_node}")957 mermaid_lines.append(f" class {ref_node} reference")958 959 mermaid_lines.extend(960 [961 f" class {node_names[next(iter(dependencies))[0]]} source;",962 ]963 )964 965 return "\n".join(mermaid_lines)966 return (generate_reference_diagram,)967 968 969if __name__ == "__main__":970 app.run()971 