Spaces:
Runtime error
Runtime error
Rob McKnight commited on
Commit ·
85b08fb
1
Parent(s): 4545835
boom
Browse files- Dockerfile +35 -7
- README.md +0 -13
- app.py +0 -470
- development.md +0 -8
- requirements.txt +4 -5
- supervisord.conf +22 -0
- tutorial.py +970 -0
Dockerfile
CHANGED
|
@@ -1,16 +1,44 @@
|
|
| 1 |
-
FROM
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
|
|
|
|
|
|
| 4 |
RUN useradd -m -u 1000 user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
ENV PATH="/home/user/.local/bin:$PATH"
|
| 6 |
ENV UV_SYSTEM_PYTHON=1
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
| 9 |
|
|
|
|
|
|
|
| 10 |
COPY --chown=user ./requirements.txt requirements.txt
|
| 11 |
-
RUN
|
| 12 |
-
|
| 13 |
COPY --chown=user . /app
|
| 14 |
-
USER user
|
| 15 |
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM agenticlabs/lsproxy:0.1.1
|
| 2 |
+
|
| 3 |
+
# Install Python, pip, and git
|
| 4 |
+
RUN apt-get update && apt-get install -y \
|
| 5 |
+
python3 \
|
| 6 |
+
python3-pip \
|
| 7 |
+
supervisor \
|
| 8 |
+
git \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
|
| 11 |
+
# Set up uv
|
| 12 |
+
COPY --from=ghcr.io/astral-sh/uv:0.4.20 /uv /bin/uv
|
| 13 |
RUN useradd -m -u 1000 user
|
| 14 |
+
|
| 15 |
+
# Set up marimo config directory with proper permissions
|
| 16 |
+
RUN mkdir -p /home/user/.marimo && \
|
| 17 |
+
chown -R user:user /home/user/.marimo
|
| 18 |
+
|
| 19 |
ENV PATH="/home/user/.local/bin:$PATH"
|
| 20 |
ENV UV_SYSTEM_PYTHON=1
|
| 21 |
+
ENV HOME=/home/user
|
| 22 |
+
ENV XDG_CONFIG_HOME=/home/user/.config
|
| 23 |
+
ENV MARIMO_CONFIG_DIR=/home/user/.marimo
|
| 24 |
|
| 25 |
+
# Clone specific commit of trieve
|
| 26 |
+
RUN git clone https://github.com/devflowinc/trieve /mnt/workspace && \
|
| 27 |
+
cd /mnt/workspace && \
|
| 28 |
+
git checkout e65889a13715e8833e7cccfe0168b57c1fc966cc
|
| 29 |
|
| 30 |
+
# Set up marimo
|
| 31 |
+
WORKDIR /app
|
| 32 |
COPY --chown=user ./requirements.txt requirements.txt
|
| 33 |
+
RUN pip install --break-system-packages -r requirements.txt
|
|
|
|
| 34 |
COPY --chown=user . /app
|
|
|
|
| 35 |
|
| 36 |
+
# Set up supervisor
|
| 37 |
+
RUN mkdir -p /var/log/supervisor
|
| 38 |
+
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
| 39 |
+
|
| 40 |
+
# Expose both ports
|
| 41 |
+
EXPOSE 4444 7860
|
| 42 |
+
|
| 43 |
+
# Start supervisor to manage both processes
|
| 44 |
+
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
README.md
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: marimo app template
|
| 3 |
-
emoji: 🍃
|
| 4 |
-
colorFrom: indigo
|
| 5 |
-
colorTo: purple
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned: false
|
| 8 |
-
license: mit
|
| 9 |
-
short_description: Template for deploying a marimo application to HF
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
Check out marimo at <https://github.com/marimo-team/marimo>
|
| 13 |
-
Check out the configuration reference at <https://huggingface.co/docs/hub/spaces-config-reference>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
DELETED
|
@@ -1,470 +0,0 @@
|
|
| 1 |
-
import marimo
|
| 2 |
-
|
| 3 |
-
__generated_with = "0.9.2"
|
| 4 |
-
app = marimo.App()
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
@app.cell
|
| 8 |
-
def __():
|
| 9 |
-
import marimo as mo
|
| 10 |
-
|
| 11 |
-
mo.md("# Welcome to marimo! 🌊🍃")
|
| 12 |
-
return (mo,)
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
@app.cell
|
| 16 |
-
def __(mo):
|
| 17 |
-
slider = mo.ui.slider(1, 22)
|
| 18 |
-
return (slider,)
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
@app.cell
|
| 22 |
-
def __(mo, slider):
|
| 23 |
-
mo.md(
|
| 24 |
-
f"""
|
| 25 |
-
marimo is a **reactive** Python notebook.
|
| 26 |
-
|
| 27 |
-
This means that unlike traditional notebooks, marimo notebooks **run
|
| 28 |
-
automatically** when you modify them or
|
| 29 |
-
interact with UI elements, like this slider: {slider}.
|
| 30 |
-
|
| 31 |
-
{"##" + "🍃" * slider.value}
|
| 32 |
-
"""
|
| 33 |
-
)
|
| 34 |
-
return
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
@app.cell(hide_code=True)
|
| 38 |
-
def __(mo):
|
| 39 |
-
mo.accordion(
|
| 40 |
-
{
|
| 41 |
-
"Tip: disabling automatic execution": mo.md(
|
| 42 |
-
rf"""
|
| 43 |
-
marimo lets you disable automatic execution: just go into the
|
| 44 |
-
notebook settings and set
|
| 45 |
-
|
| 46 |
-
"Runtime > On Cell Change" to "lazy".
|
| 47 |
-
|
| 48 |
-
When the runtime is lazy, after running a cell, marimo marks its
|
| 49 |
-
descendants as stale instead of automatically running them. The
|
| 50 |
-
lazy runtime puts you in control over when cells are run, while
|
| 51 |
-
still giving guarantees about the notebook state.
|
| 52 |
-
"""
|
| 53 |
-
)
|
| 54 |
-
}
|
| 55 |
-
)
|
| 56 |
-
return
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
@app.cell(hide_code=True)
|
| 60 |
-
def __(mo):
|
| 61 |
-
mo.md(
|
| 62 |
-
"""
|
| 63 |
-
Tip: This is a tutorial notebook. You can create your own notebooks
|
| 64 |
-
by entering `marimo edit` at the command line.
|
| 65 |
-
"""
|
| 66 |
-
).callout()
|
| 67 |
-
return
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
@app.cell(hide_code=True)
|
| 71 |
-
def __(mo):
|
| 72 |
-
mo.md(
|
| 73 |
-
"""
|
| 74 |
-
## 1. Reactive execution
|
| 75 |
-
|
| 76 |
-
A marimo notebook is made up of small blocks of Python code called
|
| 77 |
-
cells.
|
| 78 |
-
|
| 79 |
-
marimo reads your cells and models the dependencies among them: whenever
|
| 80 |
-
a cell that defines a global variable is run, marimo
|
| 81 |
-
**automatically runs** all cells that reference that variable.
|
| 82 |
-
|
| 83 |
-
Reactivity keeps your program state and outputs in sync with your code,
|
| 84 |
-
making for a dynamic programming environment that prevents bugs before they
|
| 85 |
-
happen.
|
| 86 |
-
"""
|
| 87 |
-
)
|
| 88 |
-
return
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
@app.cell(hide_code=True)
|
| 92 |
-
def __(changed, mo):
|
| 93 |
-
(
|
| 94 |
-
mo.md(
|
| 95 |
-
f"""
|
| 96 |
-
**✨ Nice!** The value of `changed` is now {changed}.
|
| 97 |
-
|
| 98 |
-
When you updated the value of the variable `changed`, marimo
|
| 99 |
-
**reacted** by running this cell automatically, because this cell
|
| 100 |
-
references the global variable `changed`.
|
| 101 |
-
|
| 102 |
-
Reactivity ensures that your notebook state is always
|
| 103 |
-
consistent, which is crucial for doing good science; it's also what
|
| 104 |
-
enables marimo notebooks to double as tools and apps.
|
| 105 |
-
"""
|
| 106 |
-
)
|
| 107 |
-
if changed
|
| 108 |
-
else mo.md(
|
| 109 |
-
"""
|
| 110 |
-
**🌊 See it in action.** In the next cell, change the value of the
|
| 111 |
-
variable `changed` to `True`, then click the run button.
|
| 112 |
-
"""
|
| 113 |
-
)
|
| 114 |
-
)
|
| 115 |
-
return
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
@app.cell
|
| 119 |
-
def __():
|
| 120 |
-
changed = False
|
| 121 |
-
return (changed,)
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
@app.cell(hide_code=True)
|
| 125 |
-
def __(mo):
|
| 126 |
-
mo.accordion(
|
| 127 |
-
{
|
| 128 |
-
"Tip: execution order": (
|
| 129 |
-
"""
|
| 130 |
-
The order of cells on the page has no bearing on
|
| 131 |
-
the order in which cells are executed: marimo knows that a cell
|
| 132 |
-
reading a variable must run after the cell that defines it. This
|
| 133 |
-
frees you to organize your code in the way that makes the most
|
| 134 |
-
sense for you.
|
| 135 |
-
"""
|
| 136 |
-
)
|
| 137 |
-
}
|
| 138 |
-
)
|
| 139 |
-
return
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
@app.cell(hide_code=True)
|
| 143 |
-
def __(mo):
|
| 144 |
-
mo.md(
|
| 145 |
-
"""
|
| 146 |
-
**Global names must be unique.** To enable reactivity, marimo imposes a
|
| 147 |
-
constraint on how names appear in cells: no two cells may define the same
|
| 148 |
-
variable.
|
| 149 |
-
"""
|
| 150 |
-
)
|
| 151 |
-
return
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
@app.cell(hide_code=True)
|
| 155 |
-
def __(mo):
|
| 156 |
-
mo.accordion(
|
| 157 |
-
{
|
| 158 |
-
"Tip: encapsulation": (
|
| 159 |
-
"""
|
| 160 |
-
By encapsulating logic in functions, classes, or Python modules,
|
| 161 |
-
you can minimize the number of global variables in your notebook.
|
| 162 |
-
"""
|
| 163 |
-
)
|
| 164 |
-
}
|
| 165 |
-
)
|
| 166 |
-
return
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
@app.cell(hide_code=True)
|
| 170 |
-
def __(mo):
|
| 171 |
-
mo.accordion(
|
| 172 |
-
{
|
| 173 |
-
"Tip: private variables": (
|
| 174 |
-
"""
|
| 175 |
-
Variables prefixed with an underscore are "private" to a cell, so
|
| 176 |
-
they can be defined by multiple cells.
|
| 177 |
-
"""
|
| 178 |
-
)
|
| 179 |
-
}
|
| 180 |
-
)
|
| 181 |
-
return
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
@app.cell(hide_code=True)
|
| 185 |
-
def __(mo):
|
| 186 |
-
mo.md(
|
| 187 |
-
"""
|
| 188 |
-
## 2. UI elements
|
| 189 |
-
|
| 190 |
-
Cells can output interactive UI elements. Interacting with a UI
|
| 191 |
-
element **automatically triggers notebook execution**: when
|
| 192 |
-
you interact with a UI element, its value is sent back to Python, and
|
| 193 |
-
every cell that references that element is re-run.
|
| 194 |
-
|
| 195 |
-
marimo provides a library of UI elements to choose from under
|
| 196 |
-
`marimo.ui`.
|
| 197 |
-
"""
|
| 198 |
-
)
|
| 199 |
-
return
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
@app.cell
|
| 203 |
-
def __(mo):
|
| 204 |
-
mo.md("""**🌊 Some UI elements.** Try interacting with the below elements.""")
|
| 205 |
-
return
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
@app.cell
|
| 209 |
-
def __(mo):
|
| 210 |
-
icon = mo.ui.dropdown(["🍃", "🌊", "✨"], value="🍃")
|
| 211 |
-
return (icon,)
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
@app.cell
|
| 215 |
-
def __(icon, mo):
|
| 216 |
-
repetitions = mo.ui.slider(1, 16, label=f"number of {icon.value}: ")
|
| 217 |
-
return (repetitions,)
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
@app.cell
|
| 221 |
-
def __(icon, repetitions):
|
| 222 |
-
icon, repetitions
|
| 223 |
-
return
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
@app.cell
|
| 227 |
-
def __(icon, mo, repetitions):
|
| 228 |
-
mo.md("# " + icon.value * repetitions.value)
|
| 229 |
-
return
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
@app.cell(hide_code=True)
|
| 233 |
-
def __(mo):
|
| 234 |
-
mo.md(
|
| 235 |
-
"""
|
| 236 |
-
## 3. marimo is just Python
|
| 237 |
-
|
| 238 |
-
marimo cells parse Python (and only Python), and marimo notebooks are
|
| 239 |
-
stored as pure Python files — outputs are _not_ included. There's no
|
| 240 |
-
magical syntax.
|
| 241 |
-
|
| 242 |
-
The Python files generated by marimo are:
|
| 243 |
-
|
| 244 |
-
- easily versioned with git, yielding minimal diffs
|
| 245 |
-
- legible for both humans and machines
|
| 246 |
-
- formattable using your tool of choice,
|
| 247 |
-
- usable as Python scripts, with UI elements taking their default
|
| 248 |
-
values, and
|
| 249 |
-
- importable by other modules (more on that in the future).
|
| 250 |
-
"""
|
| 251 |
-
)
|
| 252 |
-
return
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
@app.cell(hide_code=True)
|
| 256 |
-
def __(mo):
|
| 257 |
-
mo.md(
|
| 258 |
-
"""
|
| 259 |
-
## 4. Running notebooks as apps
|
| 260 |
-
|
| 261 |
-
marimo notebooks can double as apps. Click the app window icon in the
|
| 262 |
-
bottom-right to see this notebook in "app view."
|
| 263 |
-
|
| 264 |
-
Serve a notebook as an app with `marimo run` at the command-line.
|
| 265 |
-
Of course, you can use marimo just to level-up your
|
| 266 |
-
notebooking, without ever making apps.
|
| 267 |
-
"""
|
| 268 |
-
)
|
| 269 |
-
return
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
@app.cell(hide_code=True)
|
| 273 |
-
def __(mo):
|
| 274 |
-
mo.md(
|
| 275 |
-
"""
|
| 276 |
-
## 5. The `marimo` command-line tool
|
| 277 |
-
|
| 278 |
-
**Creating and editing notebooks.** Use
|
| 279 |
-
|
| 280 |
-
```
|
| 281 |
-
marimo edit
|
| 282 |
-
```
|
| 283 |
-
|
| 284 |
-
in a terminal to start the marimo notebook server. From here
|
| 285 |
-
you can create a new notebook or edit existing ones.
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
**Running as apps.** Use
|
| 289 |
-
|
| 290 |
-
```
|
| 291 |
-
marimo run notebook.py
|
| 292 |
-
```
|
| 293 |
-
|
| 294 |
-
to start a webserver that serves your notebook as an app in read-only mode,
|
| 295 |
-
with code cells hidden.
|
| 296 |
-
|
| 297 |
-
**Convert a Jupyter notebook.** Convert a Jupyter notebook to a marimo
|
| 298 |
-
notebook using `marimo convert`:
|
| 299 |
-
|
| 300 |
-
```
|
| 301 |
-
marimo convert your_notebook.ipynb > your_app.py
|
| 302 |
-
```
|
| 303 |
-
|
| 304 |
-
**Tutorials.** marimo comes packaged with tutorials:
|
| 305 |
-
|
| 306 |
-
- `dataflow`: more on marimo's automatic execution
|
| 307 |
-
- `ui`: how to use UI elements
|
| 308 |
-
- `markdown`: how to write markdown, with interpolated values and
|
| 309 |
-
LaTeX
|
| 310 |
-
- `plots`: how plotting works in marimo
|
| 311 |
-
- `sql`: how to use SQL
|
| 312 |
-
- `layout`: layout elements in marimo
|
| 313 |
-
- `fileformat`: how marimo's file format works
|
| 314 |
-
- `markdown-format`: for using `.md` files in marimo
|
| 315 |
-
- `for-jupyter-users`: if you are coming from Jupyter
|
| 316 |
-
|
| 317 |
-
Start a tutorial with `marimo tutorial`; for example,
|
| 318 |
-
|
| 319 |
-
```
|
| 320 |
-
marimo tutorial dataflow
|
| 321 |
-
```
|
| 322 |
-
|
| 323 |
-
In addition to tutorials, we have examples in our
|
| 324 |
-
[our GitHub repo](https://www.github.com/marimo-team/marimo/tree/main/examples).
|
| 325 |
-
"""
|
| 326 |
-
)
|
| 327 |
-
return
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
@app.cell(hide_code=True)
|
| 331 |
-
def __(mo):
|
| 332 |
-
mo.md(
|
| 333 |
-
"""
|
| 334 |
-
## 6. The marimo editor
|
| 335 |
-
|
| 336 |
-
Here are some tips to help you get started with the marimo editor.
|
| 337 |
-
"""
|
| 338 |
-
)
|
| 339 |
-
return
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
@app.cell
|
| 343 |
-
def __(mo, tips):
|
| 344 |
-
mo.accordion(tips)
|
| 345 |
-
return
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
@app.cell(hide_code=True)
|
| 349 |
-
def __(mo):
|
| 350 |
-
mo.md("""## Finally, a fun fact""")
|
| 351 |
-
return
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
@app.cell(hide_code=True)
|
| 355 |
-
def __(mo):
|
| 356 |
-
mo.md(
|
| 357 |
-
"""
|
| 358 |
-
The name "marimo" is a reference to a type of algae that, under
|
| 359 |
-
the right conditions, clumps together to form a small sphere
|
| 360 |
-
called a "marimo moss ball". Made of just strands of algae, these
|
| 361 |
-
beloved assemblages are greater than the sum of their parts.
|
| 362 |
-
"""
|
| 363 |
-
)
|
| 364 |
-
return
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
@app.cell(hide_code=True)
|
| 368 |
-
def __():
|
| 369 |
-
tips = {
|
| 370 |
-
"Saving": (
|
| 371 |
-
"""
|
| 372 |
-
**Saving**
|
| 373 |
-
|
| 374 |
-
- _Name_ your app using the box at the top of the screen, or
|
| 375 |
-
with `Ctrl/Cmd+s`. You can also create a named app at the
|
| 376 |
-
command line, e.g., `marimo edit app_name.py`.
|
| 377 |
-
|
| 378 |
-
- _Save_ by clicking the save icon on the bottom right, or by
|
| 379 |
-
inputting `Ctrl/Cmd+s`. By default marimo is configured
|
| 380 |
-
to autosave.
|
| 381 |
-
"""
|
| 382 |
-
),
|
| 383 |
-
"Running": (
|
| 384 |
-
"""
|
| 385 |
-
1. _Run a cell_ by clicking the play ( ▷ ) button on the top
|
| 386 |
-
right of a cell, or by inputting `Ctrl/Cmd+Enter`.
|
| 387 |
-
|
| 388 |
-
2. _Run a stale cell_ by clicking the yellow run button on the
|
| 389 |
-
right of the cell, or by inputting `Ctrl/Cmd+Enter`. A cell is
|
| 390 |
-
stale when its code has been modified but not run.
|
| 391 |
-
|
| 392 |
-
3. _Run all stale cells_ by clicking the play ( ▷ ) button on
|
| 393 |
-
the bottom right of the screen, or input `Ctrl/Cmd+Shift+r`.
|
| 394 |
-
"""
|
| 395 |
-
),
|
| 396 |
-
"Console Output": (
|
| 397 |
-
"""
|
| 398 |
-
Console output (e.g., `print()` statements) is shown below a
|
| 399 |
-
cell.
|
| 400 |
-
"""
|
| 401 |
-
),
|
| 402 |
-
"Creating, Moving, and Deleting Cells": (
|
| 403 |
-
"""
|
| 404 |
-
1. _Create_ a new cell above or below a given one by clicking
|
| 405 |
-
the plus button to the left of the cell, which appears on
|
| 406 |
-
mouse hover.
|
| 407 |
-
|
| 408 |
-
2. _Move_ a cell up or down by dragging on the handle to the
|
| 409 |
-
right of the cell, which appears on mouse hover.
|
| 410 |
-
|
| 411 |
-
3. _Delete_ a cell by clicking the trash bin icon. Bring it
|
| 412 |
-
back by clicking the undo button on the bottom right of the
|
| 413 |
-
screen, or with `Ctrl/Cmd+Shift+z`.
|
| 414 |
-
"""
|
| 415 |
-
),
|
| 416 |
-
"Disabling Automatic Execution": (
|
| 417 |
-
"""
|
| 418 |
-
Via the notebook settings (gear icon) or footer panel, you
|
| 419 |
-
can disable automatic execution. This is helpful when
|
| 420 |
-
working with expensive notebooks or notebooks that have
|
| 421 |
-
side-effects like database transactions.
|
| 422 |
-
"""
|
| 423 |
-
),
|
| 424 |
-
"Disabling Cells": (
|
| 425 |
-
"""
|
| 426 |
-
You can disable a cell via the cell context menu.
|
| 427 |
-
marimo will never run a disabled cell or any cells that depend on it.
|
| 428 |
-
This can help prevent accidental execution of expensive computations
|
| 429 |
-
when editing a notebook.
|
| 430 |
-
"""
|
| 431 |
-
),
|
| 432 |
-
"Code Folding": (
|
| 433 |
-
"""
|
| 434 |
-
You can collapse or fold the code in a cell by clicking the arrow
|
| 435 |
-
icons in the line number column to the left, or by using keyboard
|
| 436 |
-
shortcuts.
|
| 437 |
-
|
| 438 |
-
Use the command palette (`Ctrl/Cmd+k`) or a keyboard shortcut to
|
| 439 |
-
quickly fold or unfold all cells.
|
| 440 |
-
"""
|
| 441 |
-
),
|
| 442 |
-
"Code Formatting": (
|
| 443 |
-
"""
|
| 444 |
-
If you have [ruff](https://github.com/astral-sh/ruff) installed,
|
| 445 |
-
you can format a cell with the keyboard shortcut `Ctrl/Cmd+b`.
|
| 446 |
-
"""
|
| 447 |
-
),
|
| 448 |
-
"Command Palette": (
|
| 449 |
-
"""
|
| 450 |
-
Use `Ctrl/Cmd+k` to open the command palette.
|
| 451 |
-
"""
|
| 452 |
-
),
|
| 453 |
-
"Keyboard Shortcuts": (
|
| 454 |
-
"""
|
| 455 |
-
Open the notebook menu (top-right) or input `Ctrl/Cmd+Shift+h` to
|
| 456 |
-
view a list of all keyboard shortcuts.
|
| 457 |
-
"""
|
| 458 |
-
),
|
| 459 |
-
"Configuration": (
|
| 460 |
-
"""
|
| 461 |
-
Configure the editor by clicking the gears icon near the top-right
|
| 462 |
-
of the screen.
|
| 463 |
-
"""
|
| 464 |
-
),
|
| 465 |
-
}
|
| 466 |
-
return (tips,)
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
if __name__ == "__main__":
|
| 470 |
-
app.run()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
development.md
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
# Development
|
| 2 |
-
|
| 3 |
-
## Testing your Dockerfile locally
|
| 4 |
-
|
| 5 |
-
```bash
|
| 6 |
-
docker build -t marimo-app .
|
| 7 |
-
docker run -it --rm -p 7860:7860 marimo-app
|
| 8 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
-
marimo
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
# Add other dependencies as needed
|
|
|
|
| 1 |
+
marimo==0.9.14
|
| 2 |
+
lsproxy-sdk==0.1.1
|
| 3 |
+
requests
|
| 4 |
+
unidiff
|
|
|
supervisord.conf
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[supervisord]
|
| 2 |
+
nodaemon=true
|
| 3 |
+
logfile=/var/log/supervisor/supervisord.log
|
| 4 |
+
loglevel=debug
|
| 5 |
+
|
| 6 |
+
[program:lsproxy]
|
| 7 |
+
command=/lsproxy
|
| 8 |
+
environment=RUST_LOG=info,RA_LOG=/tmp/rust-analyzer.log
|
| 9 |
+
stdout_logfile=/var/log/supervisor/lsproxy.log
|
| 10 |
+
stderr_logfile=/var/log/supervisor/lsproxy.err
|
| 11 |
+
autorestart=true
|
| 12 |
+
startretries=5
|
| 13 |
+
|
| 14 |
+
[program:marimo]
|
| 15 |
+
command=marimo run tutorial.py --host 0.0.0.0 --port 7860
|
| 16 |
+
user=user
|
| 17 |
+
directory=/app
|
| 18 |
+
stdout_logfile=/var/log/supervisor/marimo.log
|
| 19 |
+
stderr_logfile=/var/log/supervisor/marimo.err
|
| 20 |
+
autorestart=true
|
| 21 |
+
startretries=5
|
| 22 |
+
environment=HOME="/home/user",XDG_CONFIG_HOME="/home/user/.config",MARIMO_CONFIG_DIR="/home/user/.marimo",PYTHONUNBUFFERED=1
|
tutorial.py
ADDED
|
@@ -0,0 +1,970 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import marimo
|
| 2 |
+
|
| 3 |
+
__generated_with = "0.9.14"
|
| 4 |
+
app = marimo.App(width="medium")
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@app.cell
|
| 8 |
+
def __():
|
| 9 |
+
import requests
|
| 10 |
+
import json
|
| 11 |
+
import sys
|
| 12 |
+
from typing import Dict, Any, Optional, List
|
| 13 |
+
|
| 14 |
+
from lsproxy import GetReferencesRequest, FileRange, Position
|
| 15 |
+
|
| 16 |
+
import marimo as mo
|
| 17 |
+
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.cell
|
| 33 |
+
def __(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 |
+
return
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@app.cell
|
| 39 |
+
def __(mo):
|
| 40 |
+
mo.md("""<div style="height: 50px;"></div>""")
|
| 41 |
+
return
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@app.cell
|
| 45 |
+
def __(mo):
|
| 46 |
+
# The first step is to create our API client
|
| 47 |
+
from lsproxy import Lsproxy
|
| 48 |
+
|
| 49 |
+
api_client = Lsproxy()
|
| 50 |
+
mo.show_code()
|
| 51 |
+
return Lsproxy, api_client
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@app.cell
|
| 55 |
+
def __(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 |
+
return
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
@app.cell
|
| 61 |
+
def __(mo):
|
| 62 |
+
start_button = mo.ui.run_button(label="Click to initialize")
|
| 63 |
+
start_button
|
| 64 |
+
return (start_button,)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@app.cell
|
| 68 |
+
def __(get_files, mo, start_button):
|
| 69 |
+
# Reads all the files in the repo on initialization
|
| 70 |
+
mo.stop(not start_button.value)
|
| 71 |
+
file_symbol_dict = get_files()
|
| 72 |
+
return (file_symbol_dict,)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
@app.cell
|
| 76 |
+
def __(mo):
|
| 77 |
+
mo.md("""<div style="height: 100px;"></div>""")
|
| 78 |
+
return
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
@app.cell
|
| 82 |
+
def __(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 |
+
return
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
@app.cell
|
| 92 |
+
def __(selections_ex1):
|
| 93 |
+
selections_ex1
|
| 94 |
+
return
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
@app.cell
|
| 98 |
+
def __(code_language_select_ex1, file_dropdown_ex1, mo):
|
| 99 |
+
# This is just for controlling the flow of this tutorial
|
| 100 |
+
mo.stop(not file_dropdown_ex1.value)
|
| 101 |
+
selected_file_first_time = True
|
| 102 |
+
code_language_ex1 = code_language_select_ex1.value
|
| 103 |
+
selected_file_ex1 = file_dropdown_ex1.value
|
| 104 |
+
return code_language_ex1, selected_file_ex1, selected_file_first_time
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
@app.cell
|
| 108 |
+
def __(code_language_ex1, mo, selected_file_first_time):
|
| 109 |
+
# This is just for controlling the flow of this tutorial
|
| 110 |
+
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 |
+
return
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
@app.cell
|
| 119 |
+
def __(api_client, mo, selected_file_ex1):
|
| 120 |
+
# Retrieving the symbols defined in a file is just a single call
|
| 121 |
+
symbols_ex1 = api_client.definitions_in_file(selected_file_ex1)
|
| 122 |
+
|
| 123 |
+
mo.show_code()
|
| 124 |
+
return (symbols_ex1,)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
@app.cell
|
| 128 |
+
def __(mo, symbols_ex1):
|
| 129 |
+
# Pack the data from the symbols into a tabular format
|
| 130 |
+
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 display
|
| 143 |
+
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 table
|
| 150 |
+
symbol_table_ex1
|
| 151 |
+
return symbol_table_ex1, table_data_ex1
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@app.cell
|
| 155 |
+
def __(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.cell
|
| 162 |
+
def __(
|
| 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_code
|
| 177 |
+
|
| 178 |
+
# Get references to the symbol and optionally include context lines surrounding the usage
|
| 179 |
+
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 = True
|
| 185 |
+
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.cell
|
| 196 |
+
def __(
|
| 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 display
|
| 205 |
+
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_ex1
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
# Display the code and reference text
|
| 211 |
+
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_ex1
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
@app.cell
|
| 223 |
+
def __(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_2
|
| 227 |
+
return (example_2,)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
@app.cell
|
| 231 |
+
def __(mo):
|
| 232 |
+
mo.md("""<div style="height: 100px;"></div>""")
|
| 233 |
+
return
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
@app.cell
|
| 237 |
+
def __(example_2, mo):
|
| 238 |
+
mo.stop(not example_2.value)
|
| 239 |
+
ex2_unlocked = True
|
| 240 |
+
return (ex2_unlocked,)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
@app.cell
|
| 244 |
+
def __(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 |
+
return
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
@app.cell
|
| 254 |
+
def __(ex2_unlocked, file_dropdown_2, mo):
|
| 255 |
+
mo.stop(not ex2_unlocked)
|
| 256 |
+
# Pull the symbols inside a file
|
| 257 |
+
selected_file_ex2 = file_dropdown_2.value
|
| 258 |
+
return (selected_file_ex2,)
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
@app.cell
|
| 262 |
+
def __(api_client, mo, selected_file_ex2):
|
| 263 |
+
# As before we can get all of the symbols from a file
|
| 264 |
+
symbols_ex2 = api_client.definitions_in_file(selected_file_ex2)
|
| 265 |
+
mo.show_code()
|
| 266 |
+
return (symbols_ex2,)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
@app.cell
|
| 270 |
+
def __(
|
| 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 references
|
| 278 |
+
referenced_symbols_in_file_dict = {}
|
| 279 |
+
for symbol in mo.status.progress_bar(
|
| 280 |
+
symbols_ex2, title="Symbols processed", remove_on_exit=True
|
| 281 |
+
):
|
| 282 |
+
reference_request_ex2 = GetReferencesRequest(
|
| 283 |
+
identifier_position=symbol.identifier_position,
|
| 284 |
+
)
|
| 285 |
+
references_ex2 = api_client.find_references(reference_request_ex2).references
|
| 286 |
+
|
| 287 |
+
# Save which symbols were referenced by which file
|
| 288 |
+
for ref in references_ex2:
|
| 289 |
+
referencing_file = ref.path
|
| 290 |
+
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.cell
|
| 306 |
+
def __(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 |
+
return
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
@app.cell
|
| 316 |
+
def __(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 = True
|
| 322 |
+
mo.mermaid(mermaid_diagram)
|
| 323 |
+
return diagram_shown, mermaid_diagram
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
@app.cell
|
| 327 |
+
def __(mo):
|
| 328 |
+
mo.md("""<div style="height: 100px;"></div>""")
|
| 329 |
+
return
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
@app.cell
|
| 333 |
+
def __(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_3
|
| 337 |
+
return (example_3,)
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
@app.cell
|
| 341 |
+
def __(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 |
+
return
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
@app.cell
|
| 350 |
+
def __(example_3, mo):
|
| 351 |
+
mo.stop(not example_3.value)
|
| 352 |
+
import subprocess
|
| 353 |
+
import io
|
| 354 |
+
from pydantic import BaseModel
|
| 355 |
+
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, subprocess
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
@app.cell
|
| 360 |
+
def __(mo, subprocess):
|
| 361 |
+
parent_commit = "1910d6867877bfdd64ca822e266372335392a8be"
|
| 362 |
+
|
| 363 |
+
# Load in the diff
|
| 364 |
+
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_commit
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
@app.cell
|
| 373 |
+
def __(example_3, mo):
|
| 374 |
+
mo.stop(not example_3.value)
|
| 375 |
+
mo.md("""First, we extract affected lines from the diff text.""")
|
| 376 |
+
return
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
@app.cell
|
| 380 |
+
def __(Dict, List, Tuple, diff_text, io, mo):
|
| 381 |
+
from unidiff import PatchSet
|
| 382 |
+
|
| 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_no
|
| 392 |
+
)
|
| 393 |
+
elif line.is_removed:
|
| 394 |
+
affected_lines.setdefault(patched_file.path, set()).add(
|
| 395 |
+
line.source_line_no
|
| 396 |
+
)
|
| 397 |
+
return affected_lines
|
| 398 |
+
|
| 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_diff
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
@app.cell
|
| 409 |
+
def __(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 |
+
return
|
| 413 |
+
|
| 414 |
+
|
| 415 |
+
@app.cell
|
| 416 |
+
def __(BaseModel, GetReferencesRequest, List, Set, Tuple, api_client, mo):
|
| 417 |
+
from lsproxy import FilePosition
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
class HierarchyItem(BaseModel):
|
| 421 |
+
name: str
|
| 422 |
+
kind: str
|
| 423 |
+
defined_at: FilePosition
|
| 424 |
+
source_code: str
|
| 425 |
+
|
| 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].path
|
| 440 |
+
|
| 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 symbols
|
| 453 |
+
for target_position in target_positions
|
| 454 |
+
if symbol.range.contains(target_position)
|
| 455 |
+
}
|
| 456 |
+
return symbols_containing_position
|
| 457 |
+
|
| 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 positions
|
| 467 |
+
stack = list(symbols_changed_directly)
|
| 468 |
+
|
| 469 |
+
while stack:
|
| 470 |
+
symbol = stack.pop()
|
| 471 |
+
|
| 472 |
+
# If we've already processesed this symbol skip it
|
| 473 |
+
if symbol in nodes:
|
| 474 |
+
continue
|
| 475 |
+
nodes.add(symbol)
|
| 476 |
+
|
| 477 |
+
# For each symbol we find all its references
|
| 478 |
+
references = api_client.find_references(
|
| 479 |
+
GetReferencesRequest(
|
| 480 |
+
identifier_position=symbol.defined_at,
|
| 481 |
+
include_declaration=False,
|
| 482 |
+
)
|
| 483 |
+
).references
|
| 484 |
+
|
| 485 |
+
# Group them by file
|
| 486 |
+
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 processing
|
| 491 |
+
related_symbols = [
|
| 492 |
+
sym
|
| 493 |
+
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, edges
|
| 503 |
+
|
| 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.cell
|
| 515 |
+
def __(
|
| 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 to
|
| 531 |
+
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 references
|
| 538 |
+
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.cell
|
| 554 |
+
def __(
|
| 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.cell
|
| 570 |
+
def __(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 symbols
|
| 582 |
+
edges: Set of tuples containing (from_symbol, to_symbol) relationships
|
| 583 |
+
symbols_changed_directly: Set of HierarchyItem objects that were changed directly
|
| 584 |
+
|
| 585 |
+
Returns:
|
| 586 |
+
str: Mermaid diagram representation of the hierarchy with file-based subgraphs
|
| 587 |
+
"""
|
| 588 |
+
mermaid_lines = [
|
| 589 |
+
"%%{",
|
| 590 |
+
" init: {",
|
| 591 |
+
" 'flowchart': {",
|
| 592 |
+
" 'rankSpacing': 100,", # Increase vertical space between ranks
|
| 593 |
+
" 'nodeSpacing': 50,", # Increase horizontal space between nodes
|
| 594 |
+
" 'padding': 20", # Add padding around the entire diagram
|
| 595 |
+
" }",
|
| 596 |
+
" }",
|
| 597 |
+
"}%%",
|
| 598 |
+
"graph TD",
|
| 599 |
+
]
|
| 600 |
+
|
| 601 |
+
# Track nodes that need red styling
|
| 602 |
+
direct_node_ids = set()
|
| 603 |
+
indirect_node_ids = set()
|
| 604 |
+
|
| 605 |
+
# Group nodes by file
|
| 606 |
+
nodes_by_file = {}
|
| 607 |
+
for node in nodes:
|
| 608 |
+
file_path = node.defined_at.path
|
| 609 |
+
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 red
|
| 614 |
+
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 file
|
| 620 |
+
for file_idx, (file_path, file_nodes) in enumerate(nodes_by_file.items()):
|
| 621 |
+
# Create subgraph with unique ID
|
| 622 |
+
subgraph_id = f"subgraph_{file_idx}"
|
| 623 |
+
mermaid_lines.append(f" subgraph {subgraph_id}[{file_path}]")
|
| 624 |
+
|
| 625 |
+
# Add nodes for this file
|
| 626 |
+
for node in file_nodes:
|
| 627 |
+
# Escape quotes and special characters in names
|
| 628 |
+
escaped_name = node.name.replace('"', '\\"')
|
| 629 |
+
# Add kind as a suffix in italics
|
| 630 |
+
label = f'"{escaped_name}<br><i>{node.kind}</i>"'
|
| 631 |
+
# Use absolute value of hash to ensure positive ID
|
| 632 |
+
node_id = f"node{abs(hash(node))}"
|
| 633 |
+
mermaid_lines.append(f" {node_id}[{label}]")
|
| 634 |
+
|
| 635 |
+
# Close subgraph
|
| 636 |
+
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 nodes
|
| 645 |
+
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.cell
|
| 655 |
+
def __(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_files
|
| 659 |
+
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.cell
|
| 670 |
+
def __(mo):
|
| 671 |
+
mo.md("""<div style="height: 400px;"></div>""")
|
| 672 |
+
return
|
| 673 |
+
|
| 674 |
+
|
| 675 |
+
@app.cell
|
| 676 |
+
def __(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 |
+
return
|
| 679 |
+
|
| 680 |
+
|
| 681 |
+
@app.cell
|
| 682 |
+
def __():
|
| 683 |
+
# Appendix A: UI code to run the example
|
| 684 |
+
return
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
@app.cell
|
| 688 |
+
def __(create_dropdowns, create_selector_dict, file_symbol_dict, mo):
|
| 689 |
+
# UI Elements for the first example
|
| 690 |
+
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.cell
|
| 702 |
+
def __(code_language_select_ex1, mo, selector_dict_1):
|
| 703 |
+
# Combining UI selections for the first example
|
| 704 |
+
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_ex1
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
@app.cell
|
| 714 |
+
def __(create_dropdowns, create_selector_dict, file_symbol_dict, mo):
|
| 715 |
+
# UI Elements for the second example
|
| 716 |
+
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.cell
|
| 730 |
+
def __(code_language_select_ex2, mo, selector_dict_2):
|
| 731 |
+
# Combining UI selections for the second example
|
| 732 |
+
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_2
|
| 742 |
+
|
| 743 |
+
|
| 744 |
+
@app.cell
|
| 745 |
+
def __():
|
| 746 |
+
# Appendix B: Helper functions to create the UI code
|
| 747 |
+
return
|
| 748 |
+
|
| 749 |
+
|
| 750 |
+
@app.cell
|
| 751 |
+
def __(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=True
|
| 758 |
+
):
|
| 759 |
+
symbols = api_client.definitions_in_file(file)
|
| 760 |
+
file_dict[file] = symbols
|
| 761 |
+
return file_dict
|
| 762 |
+
return (get_files,)
|
| 763 |
+
|
| 764 |
+
|
| 765 |
+
@app.cell
|
| 766 |
+
def __(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) > 0
|
| 772 |
+
]
|
| 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 ->", None
|
| 780 |
+
)
|
| 781 |
+
rs_dropdown = create_lang_dropdown(
|
| 782 |
+
file_with_symbol_count, ["rs"], "Select a rust file ->", value
|
| 783 |
+
)
|
| 784 |
+
return js_dropdown, rs_dropdown
|
| 785 |
+
return (create_dropdowns,)
|
| 786 |
+
|
| 787 |
+
|
| 788 |
+
@app.cell
|
| 789 |
+
def __(mo):
|
| 790 |
+
def create_lang_dropdown(file_symbol_dict, endings, label, value):
|
| 791 |
+
file_options = {
|
| 792 |
+
f"{file}: ({symbols} symbols)": file
|
| 793 |
+
for file, symbols in file_symbol_dict
|
| 794 |
+
if file.split(".")[-1] in endings
|
| 795 |
+
}
|
| 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.cell
|
| 804 |
+
def __():
|
| 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.cell
|
| 814 |
+
def __():
|
| 815 |
+
# Appendix C: Formatting functions for the text and mermaid charts
|
| 816 |
+
return
|
| 817 |
+
|
| 818 |
+
|
| 819 |
+
@app.cell
|
| 820 |
+
def __():
|
| 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.cell
|
| 827 |
+
def __():
|
| 828 |
+
def pretty_format_reference_results(reference_results, code_language):
|
| 829 |
+
# Header or no references
|
| 830 |
+
ref_text = (
|
| 831 |
+
["\n### `References`\n---\n"]
|
| 832 |
+
if reference_results.references
|
| 833 |
+
else ["\n---\n### `No references found`"]
|
| 834 |
+
)
|
| 835 |
+
refs = {}
|
| 836 |
+
for ref, context in zip(
|
| 837 |
+
reference_results.references, reference_results.context
|
| 838 |
+
):
|
| 839 |
+
# Split the code into it's lines and add an indicator for where the reference is
|
| 840 |
+
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 separator
|
| 857 |
+
file = ref.path
|
| 858 |
+
refs.setdefault(file, []).extend(code_with_line_nums)
|
| 859 |
+
refs[file].append(("@@@@@", "-----"))
|
| 860 |
+
|
| 861 |
+
# For each file
|
| 862 |
+
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.cell
|
| 871 |
+
def __():
|
| 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 symbols
|
| 878 |
+
OR a string representing the root file path when there are no dependencies
|
| 879 |
+
max_chars: Maximum length for displayed file paths, truncating from left if needed
|
| 880 |
+
Returns:
|
| 881 |
+
String containing the Mermaid diagram definition
|
| 882 |
+
"""
|
| 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_path
|
| 888 |
+
return "..." + file_path[-(max_chars - 3) :]
|
| 889 |
+
|
| 890 |
+
# Handle case where dependencies is just a root file string
|
| 891 |
+
if isinstance(dependencies, str):
|
| 892 |
+
return f"""graph LR
|
| 893 |
+
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 padding
|
| 903 |
+
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 nodes
|
| 913 |
+
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 file
|
| 919 |
+
node_names = {}
|
| 920 |
+
for idx, file in enumerate(unique_files):
|
| 921 |
+
node_name = f"n{idx}"
|
| 922 |
+
node_names[file] = node_name
|
| 923 |
+
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 connections
|
| 928 |
+
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 symbols
|
| 936 |
+
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 examples
|
| 947 |
+
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 connections
|
| 954 |
+
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 |
+
|
| 969 |
+
if __name__ == "__main__":
|
| 970 |
+
app.run()
|