Skip to content

Commit 94b1818

Browse files
lucharoclaude
andcommitted
feat: add absolute line numbers for improved AI agent communication
- Display line numbers from source Python file instead of relative cell numbers - Add keyboard shortcut (Cmd+Shift+A) to toggle absolute line numbers - Add UI toggle in Settings → Display → "Absolute line numbers" - Add visual indicator ("A" badge) in cell corner when enabled - Persist configuration in user config 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 21d18a1 commit 94b1818

File tree

20 files changed

+238
-10
lines changed

20 files changed

+238
-10
lines changed

frontend/src/components/app-config/user-config-form.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,34 @@ export const UserConfigForm: React.FC = () => {
821821
</div>
822822
)}
823823
/>
824+
<FormField
825+
control={form.control}
826+
name="display.absolute_line_numbers"
827+
render={({ field }) => (
828+
<div className="flex flex-col space-y-1">
829+
<FormItem className={formItemClasses}>
830+
<FormLabel>Absolute line numbers</FormLabel>
831+
<FormControl>
832+
<Checkbox
833+
data-testid="absolute-line-numbers-checkbox"
834+
checked={field.value}
835+
onCheckedChange={field.onChange}
836+
/>
837+
</FormControl>
838+
<FormMessage />
839+
<IsOverridden
840+
userConfig={config}
841+
name="display.absolute_line_numbers"
842+
/>
843+
</FormItem>
844+
845+
<FormDescription>
846+
Display line numbers relative to the Python script file
847+
instead of relative to each cell.
848+
</FormDescription>
849+
</div>
850+
)}
851+
/>
824852
</SettingGroup>
825853
<SettingGroup title="Outputs">
826854
<FormField

frontend/src/components/editor/cell/code/cell-editor.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ import { LanguageToggles } from "./language-toggle";
4545

4646
export interface CellEditorProps
4747
extends Pick<CellRuntimeState, "status">,
48-
Pick<CellData, "id" | "code" | "serializedEditorState" | "config"> {
48+
Pick<
49+
CellData,
50+
"id" | "code" | "serializedEditorState" | "config" | "lineno"
51+
> {
4952
runCell: () => void;
5053
theme: Theme;
5154
showPlaceholder: boolean;
@@ -88,6 +91,7 @@ const CellEditorInternal = ({
8891
languageAdapter,
8992
setLanguageAdapter,
9093
showLanguageToggles = true,
94+
lineno,
9195
}: CellEditorProps) => {
9296
const [aiCompletionCell, setAiCompletionCell] = useAtom(aiCompletionCellAtom);
9397
const deleteCell = useDeleteCellCallback();
@@ -188,6 +192,7 @@ const CellEditorInternal = ({
188192
diagnosticsConfig: userConfig.diagnostics,
189193
displayConfig: userConfig.display,
190194
inlineAiTooltip: userConfig.ai?.inline_tooltip ?? false,
195+
cellLineno: lineno,
191196
});
192197

193198
extensions.push(

frontend/src/components/editor/notebook-cell.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ const EditableCellComponent = ({
622622
showHiddenCode={showHiddenCode}
623623
languageAdapter={languageAdapter}
624624
setLanguageAdapter={setLanguageAdapter}
625+
lineno={cellData.lineno}
625626
/>
626627
<CellRightSideActions
627628
className={cn(
@@ -1099,6 +1100,7 @@ const SetupCellComponent = ({
10991100
languageAdapter={"python"}
11001101
setLanguageAdapter={Functions.NOOP}
11011102
showLanguageToggles={false}
1103+
lineno={cellData.lineno}
11021104
/>
11031105
<CellRightSideActions
11041106
edited={cellData.edited}

frontend/src/core/cells/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export function createCell({
2020
edited = false,
2121
config,
2222
serializedEditorState = null,
23+
lineno,
24+
endLineno,
2325
}: Partial<CellData> & { id: CellId }): CellData {
2426
return {
2527
id: id,
@@ -34,6 +36,8 @@ export function createCell({
3436
lastCodeRun: lastCodeRun,
3537
lastExecutionTime: lastExecutionTime,
3638
serializedEditorState: serializedEditorState,
39+
lineno: lineno,
40+
endLineno: endLineno,
3741
};
3842
}
3943

@@ -85,6 +89,10 @@ export interface CellData {
8589
config: CellConfig;
8690
/** serialized state of the underlying editor */
8791
serializedEditorState: SerializedEditorState | null;
92+
/** starting line number in the Python script file */
93+
lineno?: number;
94+
/** ending line number in the Python script file */
95+
endLineno?: number;
8896
}
8997

9098
export interface CellRuntimeState {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* Copyright 2024 Marimo. All rights reserved. */
2+
3+
import type { Extension } from "@codemirror/state";
4+
import { lineNumbers } from "@codemirror/view";
5+
6+
/**
7+
* Creates a CodeMirror extension that displays absolute line numbers
8+
* based on the cell's position in the Python script file.
9+
*
10+
* @param lineOffset - The starting line number of the cell in the Python file (0-indexed)
11+
* @returns CodeMirror extension for absolute line numbers
12+
*/
13+
export function absoluteLineNumbers(lineOffset: number): Extension {
14+
return lineNumbers({
15+
formatNumber: (lineNo: number) => {
16+
// lineNo is 1-indexed within the cell
17+
// lineOffset is the absolute starting line (0-indexed)
18+
// We want to display: lineOffset + lineNo
19+
const absoluteLineNumber = lineOffset + lineNo;
20+
21+
// Add a visual indicator (*) on the first line to show absolute mode is active
22+
if (lineNo === 1) {
23+
return `${absoluteLineNumber}*`;
24+
}
25+
26+
return String(absoluteLineNumber);
27+
},
28+
});
29+
}

frontend/src/core/codemirror/cm.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import type {
4848
} from "../config/config-schema";
4949
import type { HotkeyProvider } from "../hotkeys/hotkeys";
5050
import { store } from "../state/jotai";
51+
import { absoluteLineNumbers } from "./absolute-line-numbers/extension";
5152
import { requestEditCompletion } from "./ai/request";
5253
import { cellBundle } from "./cells/extensions";
5354
import type { CodemirrorCellActions } from "./cells/state";
@@ -81,8 +82,12 @@ export interface CodeMirrorSetupOpts {
8182
hotkeys: HotkeyProvider;
8283
lspConfig: LSPConfig;
8384
diagnosticsConfig: DiagnosticsConfig;
84-
displayConfig: Pick<DisplayConfig, "reference_highlighting">;
85+
displayConfig: Pick<
86+
DisplayConfig,
87+
"reference_highlighting" | "absolute_line_numbers"
88+
>;
8589
inlineAiTooltip: boolean;
90+
cellLineno?: number;
8691
}
8792

8893
function getPlaceholderType(opts: CodeMirrorSetupOpts) {
@@ -180,9 +185,17 @@ export const basicBundle = (opts: CodeMirrorSetupOpts): Extension[] => {
180185
cellId,
181186
lspConfig,
182187
diagnosticsConfig,
188+
displayConfig,
189+
cellLineno,
183190
} = opts;
184191
const placeholderType = getPlaceholderType(opts);
185192

193+
// Use absolute line numbers if enabled and we have a valid line number
194+
const useAbsoluteLineNumbers =
195+
displayConfig.absolute_line_numbers &&
196+
cellLineno !== undefined &&
197+
cellLineno > 0;
198+
186199
return [
187200
///// View
188201
EditorView.lineWrapping,
@@ -191,7 +204,7 @@ export const basicBundle = (opts: CodeMirrorSetupOpts): Extension[] => {
191204
highlightActiveLine(),
192205
highlightActiveLineGutter(),
193206
highlightSpecialChars(),
194-
lineNumbers(),
207+
useAbsoluteLineNumbers ? absoluteLineNumbers(cellLineno) : lineNumbers(),
195208
rectangularSelection(),
196209
tooltips({
197210
// Having fixed position prevents tooltips from being repositioned

frontend/src/core/config/config-schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export const UserConfigSchema = z
141141
}),
142142
locale: z.string().nullable().optional(),
143143
reference_highlighting: z.boolean().prefault(false),
144+
absolute_line_numbers: z.boolean().prefault(false),
144145
})
145146
.prefault({}),
146147
package_management: z

frontend/src/core/edit-app.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
useCellActions,
2727
} from "./cells/cells";
2828
import { CellEffects } from "./cells/effects";
29+
import { useResolvedMarimoConfig } from "./config/config";
2930
import type { AppConfig, UserConfig } from "./config/config-schema";
3031
import { RuntimeState } from "./kernel/RuntimeState";
3132
import { getSessionId } from "./kernel/session";
@@ -66,7 +67,9 @@ export const EditApp: React.FC<AppProps> = ({
6667
const hasCells = useAtomValue(hasCellsAtom);
6768
const filename = useFilename();
6869
const setLastSavedNotebook = useSetAtom(lastSavedNotebookAtom);
69-
const { sendComponentValues, sendInterrupt } = useRequestClient();
70+
const { sendComponentValues, sendInterrupt, saveUserConfig } =
71+
useRequestClient();
72+
const [config, setConfig] = useResolvedMarimoConfig();
7073

7174
const isEditing = viewState.mode === "edit";
7275
const isPresenting = viewState.mode === "present";
@@ -132,6 +135,18 @@ export const EditApp: React.FC<AppProps> = ({
132135
useHotkey("global.expandAllSections", () => {
133136
expandAllCells();
134137
});
138+
useHotkey("global.toggleAbsoluteLineNumbers", () => {
139+
const newConfig = {
140+
...config,
141+
display: {
142+
...config.display,
143+
absolute_line_numbers: !config.display.absolute_line_numbers,
144+
},
145+
};
146+
saveUserConfig({ config: newConfig }).then(() => {
147+
setConfig(newConfig);
148+
});
149+
});
135150

136151
const editableCellsArray = (
137152
<CellArray

frontend/src/core/hotkeys/hotkeys.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,11 @@ const DEFAULT_HOT_KEY = {
344344
group: "Editing",
345345
key: "Mod-Shift-\\",
346346
},
347+
"global.toggleAbsoluteLineNumbers": {
348+
name: "Toggle absolute line numbers",
349+
group: "Other",
350+
key: "Mod-Shift-A",
351+
},
347352
"global.expandAllSections": {
348353
name: "Expand all sections",
349354
group: "Editing",

frontend/src/core/kernel/handlers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export function handleKernelReady(
5454
last_execution_time = {},
5555
app_config,
5656
capabilities,
57+
linenos = [],
58+
end_linenos = [],
5759
} = data;
5860
const lastExecutedCode = last_executed_code || {};
5961
const lastExecutionTime = last_execution_time || {};
@@ -78,6 +80,8 @@ export function handleKernelReady(
7880
lastCodeRun: lastExecutedCode[cellId] ?? null,
7981
lastExecutionTime: lastExecutionTime[cellId] ?? null,
8082
config: configs[i],
83+
lineno: linenos[i],
84+
endLineno: end_linenos[i],
8185
});
8286
});
8387

0 commit comments

Comments
 (0)