-
Notifications
You must be signed in to change notification settings - Fork 142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Go to definition functionality added to hotkey and context menu #1411
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @robmck1995 - I've reviewed your changes and they look great!
Here's what I looked at during the review
- 🟡 General issues: 5 issues found
- 🟢 Security: all looks good
- 🟡 Testing: 3 issues found
- 🟡 Complexity: 1 issue found
Help me be more useful! Please click 👍 or 👎 on each comment to tell me if it was helpful.
const focusByVariableName = useCallback(() => { | ||
if (editorViewRef.current) { | ||
const { state } = editorViewRef.current; | ||
const variableName = getWordUnderCursor(state); | ||
const focusCellId = getCellIdOfDefinition(variables, variableName); | ||
|
||
if (focusCellId) { | ||
focusCellAtDefinition({ | ||
cellId: focusCellId, | ||
variableName: variableName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Missing dependency in useCallback hook.
The focusByVariableName
function uses getWordUnderCursor
and getCellIdOfDefinition
, but these are not included in the dependency array. This could lead to stale closures if these functions change.
const focusCellId = variable.declaredBy[0]; | ||
return focusCellId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Potential issue with multiple declarations.
The getCellIdOfDefinition
function only returns the first cell ID where the variable is declared. If a variable is declared in multiple cells, this might not be the desired behavior. Consider handling multiple declarations or documenting this behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is probably fine as multiple definitions, while supported, is not a state the notebook should stay in? So the first definition is probably fine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea this is ok - you can define something twice, but its an error and going to the first one will be ok
{ | ||
label: "Go to Definition", | ||
icon: <SearchIcon size={13} strokeWidth={1.5} />, | ||
handle: () => { | ||
const { getEditorView } = props; | ||
const editorView = getEditorView(); | ||
if (editorView) { | ||
const variableName = getWordUnderCursor(editorView?.state); | ||
const focusCellId = getCellIdOfDefinition(variables, variableName); | ||
if (focusCellId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Error handling for undefined variable.
In the 'Go to Definition' action, if getWordUnderCursor
returns an undefined or empty string, the function should handle this case gracefully, possibly with a user notification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that getWordUnderCursor
should always return some string. And getCellIdofDefinition
will only return a value if the variable is defined in the variables array so I think this should not cause errors, but not 100% sure about this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i can play around with some cases - vscode does always show you Go To Definition
, but does no-op when you don't have a work under it
} else if (codeFocus) { | ||
goToDefinition(editor, codeFocus); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Clarify the behavior of goToDefinition.
The goToDefinition
function is called if codeFocus
is neither '<>' nor '<>'. Ensure that goToDefinition
handles all edge cases and that its behavior is well-documented.
@@ -38,6 +39,7 @@ | |||
createBelow, | |||
moveUp, | |||
moveDown, | |||
focusByVariableName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (complexity): Consider keeping the splitCell
functionality and modularizing keybinding logic.
The new code introduces increased complexity and removes existing functionality. Here are the main points:
- Increased Interface Complexity: The
MovementCallbacks
interface now includes an additional methodfocusByVariableName
, which increases the surface area of the interface and makes it harder to maintain. - Removed Functionality: The
splitCell
functionality has been removed, which might be critical. Removing such functionality without proper context or replacement can lead to incomplete features or broken functionality. - Keybinding Changes: The keybinding for
splitCell
has been replaced withgoToDefinition
. This change should be clearly documented and justified. The new keybinding logic adds complexity by introducing a new behavior that developers need to understand and maintain. - Code Duplication: The new keybinding logic for
focusByVariableName
is similar to the existing keybinding logic, leading to code duplication and making the codebase harder to maintain and more prone to bugs.
To address these issues, consider the following approach:
- Keep the
splitCell
Functionality: Retain thesplitCell
functionality if it is still needed and add the new functionality without removing the existing one. - Modularize Keybinding Logic: Extract common keybinding logic into reusable functions to reduce duplication.
Here is a revised code snippet:
import { CellId, HTMLCellId } from "@/core/cells/ids";
import { Extension, Prec } from "@codemirror/state";
import { formatKeymapExtension } from "../extensions";
import { CellActions } from "@/core/cells/cells";
import { getEditorCodeAsPython } from "../language/utils";
import { formattingChangeEffect } from "../format";
import { closeCompletion, completionStatus } from "@codemirror/autocomplete";
import { isAtEndOfEditor, isAtStartOfEditor } from "../utils";
export interface MovementCallbacks
extends Pick<CellActions, "sendToTop" | "sendToBottom" | "moveToNextCell"> {
onRun: () => void;
deleteCell: () => void;
createAbove: () => void;
createBelow: () => void;
moveUp: () => void;
moveDown: () => void;
focusUp: () => void;
focusDown: () => void;
toggleHideCode: () => boolean;
aiCellCompletion: () => boolean;
focusByVariableName?: () => void; // Optional if not always needed
splitCell?: (params: { cellId: CellId; cursorPos: number }) => void; // Optional if not always needed
}
/**
* Extensions for cell movement
*/
export function cellMovementBundle(
cellId: CellId,
callbacks: MovementCallbacks,
): Extension[] {
const {
onRun,
deleteCell,
createAbove,
createBelow,
moveUp,
moveDown,
focusUp,
focusDown,
sendToTop,
sendToBottom,
moveToNextCell,
toggleHideCode,
aiCellCompletion,
focusByVariableName,
splitCell,
} = callbacks;
const hotkeys: KeyBinding[] = [
{
key: HOTKEYS.getHotkey("cell.run").key,
preventDefault: true,
stopPropagation: true,
run: (ev) => {
onRun();
return true;
},
},
{
key: HOTKEYS.getHotkey("cell.goToDefinition").key,
preventDefault: true,
stopPropagation: true,
run: () => {
if (focusByVariableName) {
focusByVariableName();
}
return true;
},
},
{
key: HOTKEYS.getHotkey("cell.splitCell").key,
preventDefault: true,
stopPropagation: true,
run: (ev) => {
if (splitCell) {
const cursorPos = ev.state.selection.main.head;
splitCell({ cellId, cursorPos });
requestAnimationFrame(() => {
ev.contentDOM.blur();
moveToNextCell({ cellId, before: false }); // focus new cell
});
}
return true;
},
},
];
// Highest priority so that we can override the default keymap
return [Prec.highest(keymap.of(hotkeys))];
}
export interface CodeCallbacks {
updateCellCode: CellActions["updateCellCode"];
}
/**
* Extensions for cell code editing
*/
export function cellCodeEditingBundle(
cellId: CellId,
callbacks: CodeCallbacks,
): Extension[] {
const { updateCellCode } = callbacks;
// Add your code editing extensions here
return [];
}
This approach maintains the existing functionality while adding the new feature in a less complex and more maintainable way.
frontend/src/core/hotkeys/hotkeys.ts
Outdated
"cell.goToDefinition": { | ||
name: "Go to Definition", | ||
group: "Navigation", | ||
key: "Mod-Shift-,", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vscode's Go To Definition is F12
. Maybe we can just keep it like that?
@@ -14,7 +15,7 @@ export function focusAndScrollCellIntoView({ | |||
cellId: CellId; | |||
cell: RefObject<CellHandle>; | |||
config: CellConfig; | |||
codeFocus: "top" | "bottom" | undefined; | |||
codeFocus: string | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can still keep this as codeFocus: "<<top>>" | "<<bottom>>" | string | undefined;
which can help document the signature
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually might be better to just create a new field for variableName
- otherwise we lose type-safety downstream
frontend/src/core/cells/cells.ts
Outdated
@@ -231,6 +231,25 @@ const { | |||
scrollKey: null, | |||
}; | |||
}, | |||
focusCellAtDefinition: ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though its kinda similar to focusCell
, I think we can make this a function outside of the reducer in core/codemirror/go-to-definition
. You can access the store by store.get(atomName)
.
@@ -410,3 +434,29 @@ const CellCodeMirrorEditor = React.forwardRef( | |||
CellCodeMirrorEditor.displayName = "CellCodeMirrorEditor"; | |||
|
|||
export const CellEditor = memo(CellEditorInternal); | |||
|
|||
export const getWordUnderCursor = (state: EditorState) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we can move all this logic to another file core/codemirror/go-to-definition.ts
.
we should be able to just expose one function goToDefinition(view: EditorView)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect
{ | ||
label: "Go to Definition", | ||
icon: <SearchIcon size={13} strokeWidth={1.5} />, | ||
handle: () => { | ||
const { getEditorView } = props; | ||
const editorView = getEditorView(); | ||
if (editorView) { | ||
const variableName = getWordUnderCursor(editorView?.state); | ||
const focusCellId = getCellIdOfDefinition(variables, variableName); | ||
if (focusCellId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i can play around with some cases - vscode does always show you Go To Definition
, but does no-op when you don't have a work under it
@@ -52,6 +52,7 @@ export function cellMovementBundle( | |||
toggleHideCode, | |||
aiCellCompletion, | |||
} = callbacks; | |||
const variables = useVariables() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can use store.get(variables)
and can likely push this into goToDefinition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add, thanks for the tip
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice contrib!
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.6.1-dev16 |
Fixes #1126
For this one, I set the hotkey to Mod+Shift+, but let me know if there's a better one.
I'm unsure if this is structured correctly, specifically, the utility functions specified in
cell-editor.tsx
. Let me know if there is a better way to structure it. Basically, I wasn't sure how to ensure we could use the same functionality with the right-click menu and the hotkeys. The solution works, but there is a bit of code repetition.Created this with the help of Glide! The change in this document only covers adding the hotkey, I manually added the right-click after as it was a small change:
https://glide.agenticlabs.com/sharing?userId=65bae76b7fbfb1c20ac1f3b8&taskId=ghM1mqX