Rob McKnight commited on
Commit
85b08fb
·
1 Parent(s): 4545835
Files changed (7) hide show
  1. Dockerfile +35 -7
  2. README.md +0 -13
  3. app.py +0 -470
  4. development.md +0 -8
  5. requirements.txt +4 -5
  6. supervisord.conf +22 -0
  7. tutorial.py +970 -0
Dockerfile CHANGED
@@ -1,16 +1,44 @@
1
- FROM python:3.12
2
- COPY --from=ghcr.io/astral-sh/uv:0.4.20 /uv /bin/uv
 
 
 
 
 
 
 
3
 
 
 
4
  RUN useradd -m -u 1000 user
 
 
 
 
 
5
  ENV PATH="/home/user/.local/bin:$PATH"
6
  ENV UV_SYSTEM_PYTHON=1
 
 
 
7
 
8
- WORKDIR /app
 
 
 
9
 
 
 
10
  COPY --chown=user ./requirements.txt requirements.txt
11
- RUN uv pip install -r requirements.txt
12
-
13
  COPY --chown=user . /app
14
- USER user
15
 
16
- CMD ["marimo", "run", "app.py", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
 
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
- # Or a specific version
3
- # marimo>=0.9.0
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('"', "&quot;")
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('"', "&quot;")
940
+ clean_symbol = clean_symbol.replace("<", "&lt;")
941
+ clean_symbol = clean_symbol.replace(">", "&gt;")
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()