Spaces:
Runtime error
Runtime error
File size: 32,106 Bytes
85b08fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 | import marimo
__generated_with = "0.9.14"
app = marimo.App(width="medium")
@app.cell
def __():
import requests
import json
import sys
from typing import Dict, Any, Optional, List
from lsproxy import GetReferencesRequest, FileRange, Position
import marimo as mo
return (
Any,
Dict,
FileRange,
GetReferencesRequest,
List,
Optional,
Position,
json,
mo,
requests,
sys,
)
@app.cell
def __(mo):
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!""")
return
@app.cell
def __(mo):
mo.md("""<div style="height: 50px;"></div>""")
return
@app.cell
def __(mo):
# The first step is to create our API client
from lsproxy import Lsproxy
api_client = Lsproxy()
mo.show_code()
return Lsproxy, api_client
@app.cell
def __(mo):
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.""")
return
@app.cell
def __(mo):
start_button = mo.ui.run_button(label="Click to initialize")
start_button
return (start_button,)
@app.cell
def __(get_files, mo, start_button):
# Reads all the files in the repo on initialization
mo.stop(not start_button.value)
file_symbol_dict = get_files()
return (file_symbol_dict,)
@app.cell
def __(mo):
mo.md("""<div style="height: 100px;"></div>""")
return
@app.cell
def __(file_symbol_dict, mo):
mo.stop(not file_symbol_dict)
mo.md(
"""### `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"""
)
return
@app.cell
def __(selections_ex1):
selections_ex1
return
@app.cell
def __(code_language_select_ex1, file_dropdown_ex1, mo):
# This is just for controlling the flow of this tutorial
mo.stop(not file_dropdown_ex1.value)
selected_file_first_time = True
code_language_ex1 = code_language_select_ex1.value
selected_file_ex1 = file_dropdown_ex1.value
return code_language_ex1, selected_file_ex1, selected_file_first_time
@app.cell
def __(code_language_ex1, mo, selected_file_first_time):
# This is just for controlling the flow of this tutorial
mo.stop(not selected_file_first_time)
mo.md(
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!"
)
return
@app.cell
def __(api_client, mo, selected_file_ex1):
# Retrieving the symbols defined in a file is just a single call
symbols_ex1 = api_client.definitions_in_file(selected_file_ex1)
mo.show_code()
return (symbols_ex1,)
@app.cell
def __(mo, symbols_ex1):
# Pack the data from the symbols into a tabular format
table_data_ex1 = [
{
"name": symbol.name,
"kind": symbol.kind,
"start_line": symbol.identifier_position.position.line,
"start_character": symbol.identifier_position.position.character,
"num_lines": symbol.range.end.line - symbol.range.start.line + 1,
"index": i,
}
for i, symbol in enumerate(symbols_ex1)
]
# Create the table element to display
symbol_table_ex1 = mo.ui.table(
data=table_data_ex1,
page_size=10,
selection="single",
label="Now, select a symbol to view code and references",
)
# Display the table
symbol_table_ex1
return symbol_table_ex1, table_data_ex1
@app.cell
def __(mo, symbol_table_ex1, symbols_ex1):
mo.stop(not symbol_table_ex1.value)
selected_symbol_ex1 = symbols_ex1[symbol_table_ex1.value[0].get("index")]
return (selected_symbol_ex1,)
@app.cell
def __(
FileRange,
GetReferencesRequest,
api_client,
mo,
selected_file_ex1,
selected_symbol_ex1,
):
# Read the source code for a particular range in a file by just asking for it!
file_range_ex1 = FileRange(
path=selected_file_ex1,
start=selected_symbol_ex1.range.start,
end=selected_symbol_ex1.range.end,
)
source_code_ex1 = api_client.read_source_code(file_range_ex1).source_code
# Get references to the symbol and optionally include context lines surrounding the usage
reference_request_ex1 = GetReferencesRequest(
identifier_position=selected_symbol_ex1.identifier_position,
include_code_context_lines=2,
)
reference_results_ex1 = api_client.find_references(reference_request_ex1)
viewed_symbol = True
mo.show_code()
return (
file_range_ex1,
reference_request_ex1,
reference_results_ex1,
source_code_ex1,
viewed_symbol,
)
@app.cell
def __(
code_language_ex1,
mo,
pretty_format_code_result,
pretty_format_reference_results,
reference_results_ex1,
source_code_ex1,
):
# Format the code and reference results for display
code_text_ex1 = pretty_format_code_result(source_code_ex1, code_language_ex1)
reference_text_ex1 = pretty_format_reference_results(
reference_results_ex1, code_language_ex1
)
# Display the code and reference text
mo.callout(
mo.vstack(
[
mo.md(code_text_ex1),
mo.md(reference_text_ex1),
]
)
)
return code_text_ex1, reference_text_ex1
@app.cell
def __(mo, viewed_symbol):
mo.stop(not viewed_symbol)
example_2 = mo.ui.run_button(label="Click to move on to example 2: Exploring connections between files", full_width=True)
example_2
return (example_2,)
@app.cell
def __(mo):
mo.md("""<div style="height: 100px;"></div>""")
return
@app.cell
def __(example_2, mo):
mo.stop(not example_2.value)
ex2_unlocked = True
return (ex2_unlocked,)
@app.cell
def __(ex2_unlocked, mo, selections_2):
mo.stop(not ex2_unlocked)
mo.vstack([
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---"""),
selections_2,
])
return
@app.cell
def __(ex2_unlocked, file_dropdown_2, mo):
mo.stop(not ex2_unlocked)
# Pull the symbols inside a file
selected_file_ex2 = file_dropdown_2.value
return (selected_file_ex2,)
@app.cell
def __(api_client, mo, selected_file_ex2):
# As before we can get all of the symbols from a file
symbols_ex2 = api_client.definitions_in_file(selected_file_ex2)
mo.show_code()
return (symbols_ex2,)
@app.cell
def __(
GetReferencesRequest,
api_client,
mo,
selected_file_ex2,
symbols_ex2,
):
# But now we can repeatedly look for references on EVERY symbol in the file and build up a graph of the references
referenced_symbols_in_file_dict = {}
for symbol in mo.status.progress_bar(
symbols_ex2, title="Symbols processed", remove_on_exit=True
):
reference_request_ex2 = GetReferencesRequest(
identifier_position=symbol.identifier_position,
)
references_ex2 = api_client.find_references(reference_request_ex2).references
# Save which symbols were referenced by which file
for ref in references_ex2:
referencing_file = ref.path
if referencing_file != selected_file_ex2:
referenced_symbols_in_file_dict.setdefault(
(selected_file_ex2, referencing_file), set()
).add(symbol.name)
mo.show_code()
return (
ref,
reference_request_ex2,
referenced_symbols_in_file_dict,
references_ex2,
referencing_file,
symbol,
)
@app.cell
def __(ex2_unlocked, mo):
mo.stop(not ex2_unlocked)
mo.md(
"From this information we can build a simple graph showing how a file's symbols are referenced by other files in the codebase."
)
return
@app.cell
def __(generate_reference_diagram, mo, referenced_symbols_in_file_dict):
if not referenced_symbols_in_file_dict:
mermaid_diagram = generate_reference_diagram("No external references found")
else:
mermaid_diagram = generate_reference_diagram(referenced_symbols_in_file_dict)
diagram_shown = True
mo.mermaid(mermaid_diagram)
return diagram_shown, mermaid_diagram
@app.cell
def __(mo):
mo.md("""<div style="height: 100px;"></div>""")
return
@app.cell
def __(diagram_shown, mo):
mo.stop(not diagram_shown)
example_3 = mo.ui.run_button(label="Click to move on to example 3: Analyzing a change diff with call hierarchy.", full_width=True)
example_3
return (example_3,)
@app.cell
def __(example_3, mo):
mo.stop(not example_3.value)
mo.md(
"""### 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---"""
)
return
@app.cell
def __(example_3, mo):
mo.stop(not example_3.value)
import subprocess
import io
from pydantic import BaseModel
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)""")
return BaseModel, io, subprocess
@app.cell
def __(mo, subprocess):
parent_commit = "1910d6867877bfdd64ca822e266372335392a8be"
# Load in the diff
diff_text = subprocess.check_output(
["git", "diff", parent_commit], cwd="./trieve"
).decode("utf-8")
mo.show_code(f"Output: Diff has {len(diff_text.splitlines())} lines")
return diff_text, parent_commit
@app.cell
def __(example_3, mo):
mo.stop(not example_3.value)
mo.md("""First, we extract affected lines from the diff text.""")
return
@app.cell
def __(Dict, List, Tuple, diff_text, io, mo):
from unidiff import PatchSet
def parse_diff(diff_text) -> Tuple[Dict[str, List[int]], str]:
patch = PatchSet(io.StringIO(diff_text))
affected_lines = {}
for patched_file in patch:
for hunk in patched_file:
for line in hunk:
if line.is_added:
affected_lines.setdefault(patched_file.path, set()).add(
line.target_line_no
)
elif line.is_removed:
affected_lines.setdefault(patched_file.path, set()).add(
line.source_line_no
)
return affected_lines
affected_lines = parse_diff(diff_text)
mo.show_code(
f"Output: Diff contains {sum([len(lines) for lines in affected_lines.values()])} changed lines in {len(affected_lines)} files."
)
return PatchSet, affected_lines, parse_diff
@app.cell
def __(example_3, mo):
mo.stop(not example_3.value)
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.""")
return
@app.cell
def __(BaseModel, GetReferencesRequest, List, Set, Tuple, api_client, mo):
from lsproxy import FilePosition
class HierarchyItem(BaseModel):
name: str
kind: str
defined_at: FilePosition
source_code: str
def __hash__(self) -> int:
return hash(
(
self.defined_at.path,
self.defined_at.position.line,
self.defined_at.position.character,
)
)
def get_symbols_containing_positions(
target_positions: List[FilePosition],
) -> List[HierarchyItem]:
file_path = target_positions[0].path
#######################
### Get definitions ###
#######################
symbols = api_client.definitions_in_file(file_path)
symbols_containing_position = {
HierarchyItem(
name=symbol.name,
kind=symbol.kind,
defined_at=symbol.identifier_position,
source_code=api_client.read_source_code(symbol.range).source_code,
)
for symbol in symbols
for target_position in target_positions
if symbol.range.contains(target_position)
}
return symbols_containing_position
def propagate_changes_through_codebase(symbols_changed_directly: List[FilePosition]):
"""
Compute the chain of code symbols that touch the code at the starting positions.
"""
nodes: Set[HierarchyItem] = set()
edges: Set[Tuple[HierarchyItem, HierarchyItem]] = set()
# Initialize with symbols that contain the starting positions
stack = list(symbols_changed_directly)
while stack:
symbol = stack.pop()
# If we've already processesed this symbol skip it
if symbol in nodes:
continue
nodes.add(symbol)
# For each symbol we find all its references
references = api_client.find_references(
GetReferencesRequest(
identifier_position=symbol.defined_at,
include_declaration=False,
)
).references
# Group them by file
references_by_file = {}
for ref in references:
references_by_file.setdefault(ref.path, []).append(ref)
# And then find symbols that contain the references so we can keep processing
related_symbols = [
sym
for refs in references_by_file.values()
for sym in get_symbols_containing_positions(refs)
]
for related_symbol in related_symbols:
if related_symbol != symbol:
edges.add((symbol, related_symbol))
stack.append(related_symbol)
return nodes, edges
mo.show_code()
return (
FilePosition,
HierarchyItem,
get_symbols_containing_positions,
propagate_changes_through_codebase,
)
@app.cell
def __(
FilePosition,
Position,
affected_lines,
api_client,
get_symbols_containing_positions,
mo,
propagate_changes_through_codebase,
):
affected_files = list(affected_lines.keys())
lsp_files = api_client.list_files()
affected_code_files = filter(lambda file: file in lsp_files, affected_files)
symbols_changed_directly = set()
for file in affected_code_files:
# For all the affected lines, we figure out what symbol they belong to
affected_positions = [
FilePosition(path=file, position=Position(line=line, character=0))
for line in affected_lines[file]
]
symbols_changed_directly.update(get_symbols_containing_positions(affected_positions))
# And then recursively follow the affected symbol through the codebase by following references
all_nodes, all_edges = propagate_changes_through_codebase(symbols_changed_directly)
mo.show_code()
return (
affected_code_files,
affected_files,
affected_positions,
all_edges,
all_nodes,
file,
lsp_files,
symbols_changed_directly,
)
@app.cell
def __(
all_edges,
all_nodes,
hierarchy_to_mermaid,
mo,
symbols_changed_directly,
):
mm = hierarchy_to_mermaid(all_nodes, all_edges, symbols_changed_directly)
mo.vstack([
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."),
mo.mermaid(mm)
])
return (mm,)
@app.cell
def __(HierarchyItem, Set, Tuple):
def hierarchy_to_mermaid(
nodes: Set[HierarchyItem],
edges: Set[Tuple[HierarchyItem, HierarchyItem]],
symbols_changed_directly: Set[HierarchyItem],
) -> str:
"""
Convert hierarchy nodes and edges to a Mermaid diagram string with subgraphs by file.
Uses hash codes as node identifiers. Nodes that were changed directly are colored red.
Args:
nodes: Set of HierarchyItem objects representing code symbols
edges: Set of tuples containing (from_symbol, to_symbol) relationships
symbols_changed_directly: Set of HierarchyItem objects that were changed directly
Returns:
str: Mermaid diagram representation of the hierarchy with file-based subgraphs
"""
mermaid_lines = [
"%%{",
" init: {",
" 'flowchart': {",
" 'rankSpacing': 100,", # Increase vertical space between ranks
" 'nodeSpacing': 50,", # Increase horizontal space between nodes
" 'padding': 20", # Add padding around the entire diagram
" }",
" }",
"}%%",
"graph TD",
]
# Track nodes that need red styling
direct_node_ids = set()
indirect_node_ids = set()
# Group nodes by file
nodes_by_file = {}
for node in nodes:
file_path = node.defined_at.path
if file_path not in nodes_by_file:
nodes_by_file[file_path] = []
nodes_by_file[file_path].append(node)
# Track node IDs that need to be colored red
if node in symbols_changed_directly:
direct_node_ids.add(f"node{abs(hash(node))}")
else:
indirect_node_ids.add(f"node{abs(hash(node))}")
# Create subgraphs for each file
for file_idx, (file_path, file_nodes) in enumerate(nodes_by_file.items()):
# Create subgraph with unique ID
subgraph_id = f"subgraph_{file_idx}"
mermaid_lines.append(f" subgraph {subgraph_id}[{file_path}]")
# Add nodes for this file
for node in file_nodes:
# Escape quotes and special characters in names
escaped_name = node.name.replace('"', '\\"')
# Add kind as a suffix in italics
label = f'"{escaped_name}<br><i>{node.kind}</i>"'
# Use absolute value of hash to ensure positive ID
node_id = f"node{abs(hash(node))}"
mermaid_lines.append(f" {node_id}[{label}]")
# Close subgraph
mermaid_lines.append(" end")
# Add edges using hash IDs (outside subgraphs)
for from_node, to_node in edges:
from_id = f"node{abs(hash(from_node))}"
to_id = f"node{abs(hash(to_node))}"
mermaid_lines.append(f" {from_id} --> {to_id}")
# Add styling for red nodes
for node_id in indirect_node_ids:
mermaid_lines.append(f" style {node_id} fill:#ffcccc,color:#000")
for node_id in direct_node_ids:
mermaid_lines.append(f" style {node_id} fill:#ffffff,color:#000")
return "\n".join(mermaid_lines)
return (hierarchy_to_mermaid,)
@app.cell
def __(affected_lines, all_nodes, mo):
diff_files = set(affected_lines.keys())
call_hierarchy_files = set([n.defined_at.path for n in all_nodes])
affected_files_not_in_diff = call_hierarchy_files - diff_files
affected_files_not_in_diff_str = '\n'.join([f'{i+1}. {f}' for i, f in enumerate(affected_files_not_in_diff)])
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}")
return (
affected_files_not_in_diff,
affected_files_not_in_diff_str,
call_hierarchy_files,
diff_files,
)
@app.cell
def __(mo):
mo.md("""<div style="height: 400px;"></div>""")
return
@app.cell
def __(mo):
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```""")
return
@app.cell
def __():
# Appendix A: UI code to run the example
return
@app.cell
def __(create_dropdowns, create_selector_dict, file_symbol_dict, mo):
# UI Elements for the first example
js_dropdown_1, rs_dropdown_1 = create_dropdowns(file_symbol_dict, "server/src/handlers/chunk_handler.rs: (65 symbols)")
selector_dict_1 = create_selector_dict(js_dropdown_1, rs_dropdown_1)
code_language_select_ex1 = mo.ui.radio(options=["typescript", "rust"], value="rust")
return (
code_language_select_ex1,
js_dropdown_1,
rs_dropdown_1,
selector_dict_1,
)
@app.cell
def __(code_language_select_ex1, mo, selector_dict_1):
# Combining UI selections for the first example
file_dropdown_ex1 = selector_dict_1[code_language_select_ex1.value]
selections_ex1 = mo.hstack(
[file_dropdown_ex1, code_language_select_ex1],
gap=2,
justify="end",
)
return file_dropdown_ex1, selections_ex1
@app.cell
def __(create_dropdowns, create_selector_dict, file_symbol_dict, mo):
# UI Elements for the second example
js_dropdown_2, rs_dropdown_2 = create_dropdowns(file_symbol_dict, "server/src/handlers/analytics_handler.rs: (15 symbols)")
selector_dict_2 = create_selector_dict(js_dropdown_2, rs_dropdown_2)
submit_button_2 = mo.ui.run_button(label="Find referenced files")
code_language_select_ex2 = mo.ui.radio(options=["typescript", "rust"], value="rust")
return (
code_language_select_ex2,
js_dropdown_2,
rs_dropdown_2,
selector_dict_2,
submit_button_2,
)
@app.cell
def __(code_language_select_ex2, mo, selector_dict_2):
# Combining UI selections for the second example
file_dropdown_2 = selector_dict_2[code_language_select_ex2.value]
selections_2 = mo.hstack(
[
file_dropdown_2,
code_language_select_ex2,
],
gap=2,
justify="end",
)
return file_dropdown_2, selections_2
@app.cell
def __():
# Appendix B: Helper functions to create the UI code
return
@app.cell
def __(api_client, mo):
def get_files():
file_dict = {}
with mo.status.spinner():
files = api_client.list_files()
for file in mo.status.progress_bar(
files, title="Files processed", remove_on_exit=True
):
symbols = api_client.definitions_in_file(file)
file_dict[file] = symbols
return file_dict
return (get_files,)
@app.cell
def __(create_lang_dropdown):
def create_dropdowns(file_dict, value = None):
file_with_symbol_count = [
(file, len(symbols))
for file, symbols in file_dict.items()
if len(symbols) > 0
]
file_with_symbol_count = sorted(
file_with_symbol_count, key=lambda item: -item[1]
)
js_dropdown = create_lang_dropdown(
file_with_symbol_count,
["ts", "tsx", "js", "jsx"],
"Select a typescript/javascript file ->", None
)
rs_dropdown = create_lang_dropdown(
file_with_symbol_count, ["rs"], "Select a rust file ->", value
)
return js_dropdown, rs_dropdown
return (create_dropdowns,)
@app.cell
def __(mo):
def create_lang_dropdown(file_symbol_dict, endings, label, value):
file_options = {
f"{file}: ({symbols} symbols)": file
for file, symbols in file_symbol_dict
if file.split(".")[-1] in endings
}
if value:
return mo.ui.dropdown(options=file_options, label=label, value=value)
else:
return mo.ui.dropdown(options=file_options, label=label)
return (create_lang_dropdown,)
@app.cell
def __():
def create_selector_dict(js_dropdown, rs_dropdown):
return {
"typescript": js_dropdown,
"rust": rs_dropdown,
}
return (create_selector_dict,)
@app.cell
def __():
# Appendix C: Formatting functions for the text and mermaid charts
return
@app.cell
def __():
def pretty_format_code_result(code_result, code_language):
return f"""### `Code`\n---\n```{code_language}\n\n{code_result}\n```\n"""
return (pretty_format_code_result,)
@app.cell
def __():
def pretty_format_reference_results(reference_results, code_language):
# Header or no references
ref_text = (
["\n### `References`\n---\n"]
if reference_results.references
else ["\n---\n### `No references found`"]
)
refs = {}
for ref, context in zip(
reference_results.references, reference_results.context
):
# Split the code into it's lines and add an indicator for where the reference is
code = context.source_code.split("\n")
line_nums = range(context.range.start.line, context.range.end.line + 1)
before_reference = filter(
lambda num_code: num_code[0] < ref.position.line + 1,
zip(line_nums, code),
)
after_reference = filter(
lambda num_code: num_code[0] > ref.position.line,
zip(line_nums, code),
)
code_with_line_nums = (
list(before_reference)
+ [("", "_" * ref.position.character + "^")]
+ list(after_reference)
)
# Extend the list for the file with the new the (line_num, code) plus a separator
file = ref.path
refs.setdefault(file, []).extend(code_with_line_nums)
refs[file].append(("@@@@@", "-----"))
# For each file
for ref_file, ref_lines in refs.items():
ref_text.append(f"**{ref_file}**\n\n```{code_language}")
ref_text.extend([f"{num:5}: {line}" for num, line in ref_lines])
ref_text.append(f"```\n\n")
return "\n".join(ref_text)
return (pretty_format_reference_results,)
@app.cell
def __():
def generate_reference_diagram(dependencies: dict, max_chars: int = 28) -> str:
"""
Convert a dictionary of file dependencies and their referenced symbols into a Mermaid diagram string.
Arrows point from referenced file back to source file through reference nodes.
Args:
dependencies: Dict where keys are tuples of (defined_file, referenced_file) and values are sets of referenced symbols
OR a string representing the root file path when there are no dependencies
max_chars: Maximum length for displayed file paths, truncating from left if needed
Returns:
String containing the Mermaid diagram definition
"""
def get_display_name(file_path: str) -> str:
"""Get display name for a file, truncating from left if needed."""
if len(file_path) <= max_chars:
return file_path
return "..." + file_path[-(max_chars - 3) :]
# Handle case where dependencies is just a root file string
if isinstance(dependencies, str):
return f"""graph LR
root["{dependencies}"]
classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px,color:#000;
classDef source fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:#000;
class root source;"""
if not dependencies:
return "graph LR\n %% No dependencies to display"
mermaid_lines = ["graph LR"]
# Add styling with reduced padding
mermaid_lines.extend(
[
" %% Styling",
" classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px,color:#000,max-width:none,text-overflow:clip,padding:0px;",
" classDef source fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:#000,max-width:none,text-overflow:clip,padding:0px;",
" classDef reference fill:#e8e7ff,stroke:#6b69d6,stroke-width:2px,color:#000,max-width:none,text-overflow:clip,padding:0px;",
]
)
# Collect all unique files and create nodes
unique_files = set()
for defined_file, referenced_file in dependencies.keys():
unique_files.add(defined_file)
unique_files.add(referenced_file)
# Create nodes for each unique file
node_names = {}
for idx, file in enumerate(unique_files):
node_name = f"n{idx}"
node_names[file] = node_name
display_name = get_display_name(file)
clean_name = display_name.replace('"', """)
mermaid_lines.append(f' {node_name}["{clean_name}"]')
# Create reference nodes and connections
for idx, ((defined_file, referenced_file), symbols) in enumerate(
dependencies.items()
):
from_node = node_names[defined_file]
to_node = node_names[referenced_file]
ref_node = f"ref{idx}"
# Clean and truncate symbols
cleaned_symbols = []
for symbol in sorted(symbols):
clean_symbol = str(symbol)
clean_symbol = clean_symbol.replace('"', """)
clean_symbol = clean_symbol.replace("<", "<")
clean_symbol = clean_symbol.replace(">", ">")
if len(clean_symbol) > 20:
clean_symbol = clean_symbol[:17] + "..."
cleaned_symbols.append(clean_symbol)
# Create symbol display with limited number of examples
symbols_display = "<br/>" + "<br/>".join(cleaned_symbols)
if len(cleaned_symbols) > 5:
symbols_display = (
"<br/>" + "<br/>".join(cleaned_symbols[:5]) + "<br/>..."
)
# Add reference node and connections
ref_node_def = f' {ref_node}["{len(symbols)} refs{symbols_display}"]'
mermaid_lines.append(ref_node_def)
mermaid_lines.append(f" {to_node} --> {ref_node} --> {from_node}")
mermaid_lines.append(f" class {ref_node} reference")
mermaid_lines.extend(
[
f" class {node_names[next(iter(dependencies))[0]]} source;",
]
)
return "\n".join(mermaid_lines)
return (generate_reference_diagram,)
if __name__ == "__main__":
app.run()
|