Skip to content

Commit

Permalink
feat: go-to-definition with cmd+click (#1425)
Browse files Browse the repository at this point in the history
  • Loading branch information
mscolnick committed May 21, 2024
1 parent 82df495 commit 69bd014
Show file tree
Hide file tree
Showing 17 changed files with 630 additions and 170 deletions.
13 changes: 11 additions & 2 deletions docs/guides/editor_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ marimo ships with the following IDE-like panels that help provide an overview
of your notebook:

1. **errors**: view errors in each cell;
2. **variables**: explore variable values, see where they are defined and used;
3. **dependency graph**: view dependencies between cells;
2. **variables**: explore variable values, see where they are defined and used, with go-to-definition;
3. **dependency graph**: view dependencies between cells, drill-down on nodes and edges;
4. **table of contents**: corresponding to your markdown;
5. **logs**: a continuous stream of stdout and stderr.
6. **snippets** - searchable snippets to copy directly into your notebook
7. **documentation** - move your text cursor over a symbol to see its documentation

<div align="center">
<figure>
Expand All @@ -55,6 +57,12 @@ through the cell context menu.</figcaption>
</figure>
</div>

## Go-to-definition

- Click on a variable in the editor to see where it's defined and used
- `Cmd/Ctrl-Click` on a variable to jump to its definition
- Right-click on a variable to see a context menu with options to jump to its definition

## Keyboard shortcuts

We've kept some well-known keyboard shortcuts for notebooks (`Ctrl-Enter`,
Expand Down Expand Up @@ -127,6 +135,7 @@ A non-exhausted list of settings:
- Formatting rules
- GitHub Copilot
- Autoreloading/Hot-reloading
- Outputs above or below code cells

## Send feedback

Expand Down
12 changes: 6 additions & 6 deletions frontend/src/components/dependency-graph/panels.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* Copyright 2024 Marimo. All rights reserved. */
import React, { memo } from "react";
import { Edge, Panel } from "reactflow";
import { type Edge, Panel } from "reactflow";
import { Button } from "../ui/button";
import {
Rows3Icon,
Expand All @@ -13,18 +13,18 @@ import {
SettingsIcon,
MoreVerticalIcon,
} from "lucide-react";
import { GraphLayoutView, GraphSelection, GraphSettings } from "./types";
import type { GraphLayoutView, GraphSelection, GraphSettings } from "./types";
import { CellLink } from "../editor/links/cell-link";
import { CellLinkList } from "../editor/links/cell-link-list";
import { VariableName } from "../variables/common";
import { Variable, Variables } from "@/core/variables/types";
import type { Variable, Variables } from "@/core/variables/types";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { Checkbox } from "../ui/checkbox";
import { Label } from "../ui/label";
import { ConnectionCellActionsDropdown } from "../editor/cell/cell-actions";
import { getCellEditorView } from "@/core/cells/cells";
import { CellId } from "@/core/cells/ids";
import { goToDefinition } from "@/core/codemirror/find-replace/search-highlight";
import type { CellId } from "@/core/cells/ids";
import { goToVariableDefinition } from "@/core/codemirror/go-to-definition/commands";

interface Props {
view: GraphLayoutView;
Expand Down Expand Up @@ -120,7 +120,7 @@ export const GraphSelectionPanel: React.FC<{
const highlightInCell = (cellId: CellId, variableName: string) => {
const editorView = getCellEditorView(cellId);
if (editorView) {
goToDefinition(editorView, variableName);
goToVariableDefinition(editorView, variableName);
}
};

Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/editor/cell/cell-context-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* Copyright 2024 Marimo. All rights reserved. */
import React, { Fragment } from "react";
import {
CellActionButtonProps,
type CellActionButtonProps,
useCellActionButtons,
} from "../actions/useCellActionButton";
import {
Expand All @@ -12,15 +12,15 @@ import {
ContextMenuSeparator,
} from "@/components/ui/context-menu";
import { renderMinimalShortcut } from "@/components/shortcuts/renderShortcut";
import { ActionButton } from "../actions/types";
import type { ActionButton } from "../actions/types";
import {
ClipboardPasteIcon,
CopyIcon,
ImageIcon,
ScissorsIcon,
SearchIcon,
} from "lucide-react";
import { goToDefinition } from "@/core/codemirror/go-to-definition";
import { goToDefinitionAtCursorPosition } from "@/core/codemirror/go-to-definition/utils";

interface Props extends CellActionButtonProps {
children: React.ReactNode;
Expand Down Expand Up @@ -90,7 +90,7 @@ export const CellActionsContextMenu = ({ children, ...props }: Props) => {
const { getEditorView } = props;
const editorView = getEditorView();
if (editorView) {
goToDefinition(editorView);
goToDefinitionAtCursorPosition(editorView);
}
},
},
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/components/variables/variables-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@ import {
TableCell,
Table,
} from "../ui/table";
import { Variable, Variables } from "@/core/variables/types";
import { CellId } from "@/core/cells/ids";
import type { Variable, Variables } from "@/core/variables/types";
import type { CellId } from "@/core/cells/ids";
import { CellLink } from "@/components/editor/links/cell-link";
import { cn } from "@/utils/cn";
import { SquareEqualIcon, WorkflowIcon } from "lucide-react";
import {
useReactTable,
getCoreRowModel,
flexRender,
ColumnDef,
SortingState,
type ColumnDef,
type SortingState,
getSortedRowModel,
ColumnSort,
type ColumnSort,
getFilteredRowModel,
} from "@tanstack/react-table";
import { DataTableColumnHeader } from "../data-table/column-header";
import { sortBy } from "lodash-es";
import { getCellEditorView } from "@/core/cells/cells";
import { goToDefinition } from "@/core/codemirror/find-replace/search-highlight";
import { goToVariableDefinition } from "@/core/codemirror/go-to-definition/commands";
import { SearchInput } from "../ui/input";
import { CellLinkList } from "../editor/links/cell-link-list";
import { VariableName } from "./common";
Expand Down Expand Up @@ -122,7 +122,7 @@ const COLUMNS = [
const highlightInCell = (cellId: CellId) => {
const editorView = getCellEditorView(cellId);
if (editorView) {
goToDefinition(editorView, name);
goToVariableDefinition(editorView, name);
}
};

Expand Down
32 changes: 7 additions & 25 deletions frontend/src/core/cells/cells.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { clamp } from "@/utils/math";
import type { LayoutState } from "../layout/layout";
import { notebookIsRunning } from "./utils";
import {
getEditorCodeAsPython,
splitEditor,
updateEditorCodeFromPython,
} from "../codemirror/language/utils";

Expand Down Expand Up @@ -627,38 +627,18 @@ const {
cellLogs: [],
};
},
splitCell: (state, action: { cellId: CellId; cursorPos: number }) => {
const { cellId, cursorPos } = action;
splitCell: (state, action: { cellId: CellId }) => {
const { cellId } = action;
const index = state.cellIds.indexOf(cellId);
const cell = state.cellData[cellId];
const cellHandle = state.cellHandles[cellId].current;

if (cellHandle?.editorView == null) {
// TODO: Because of this we can't do the reducer tests like for the other functions
return state;
}

// Figure out if we're at the start or end of a line to adjust the cursor positions
const isCursorAtLineStart =
cell.code.length > 0 && cell.code[cursorPos - 1] === "\n";
const isCursorAtLineEnd =
cell.code.length > 0 && cell.code[cursorPos] === "\n";

const beforeAdjustedCursorPos = isCursorAtLineStart
? cursorPos - 1
: cursorPos;
const afterAdjustedCursorPos = isCursorAtLineEnd
? cursorPos + 1
: cursorPos;

const beforeCursorCode = getEditorCodeAsPython(
cellHandle.editorView,
0,
beforeAdjustedCursorPos,
);
const afterCursorCode = getEditorCodeAsPython(
const { beforeCursorCode, afterCursorCode } = splitEditor(
cellHandle.editorView,
afterAdjustedCursorPos,
);

updateEditorCodeFromPython(cellHandle.editorView, beforeCursorCode);
Expand All @@ -673,7 +653,9 @@ const {
[cellId]: {
...cell,
code: beforeCursorCode,
edited: beforeCursorCode.trim() !== cell.lastCodeRun,
edited:
Boolean(beforeCursorCode) &&
beforeCursorCode.trim() !== cell.lastCodeRun?.trim(),
},
[newCellId]: createCell({
id: newCellId,
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/core/cells/scrollCellIntoView.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* Copyright 2024 Marimo. All rights reserved. */
import { RefObject } from "react";
import type { RefObject } from "react";
import { Logger } from "../../utils/Logger";
import { CellId, HTMLCellId } from "./ids";
import { CellHandle } from "@/components/editor/Cell";
import { CellConfig } from "./types";
import { goToDefinition } from "../codemirror/find-replace/search-highlight";
import { type CellId, HTMLCellId } from "./ids";
import type { CellHandle } from "@/components/editor/Cell";
import type { CellConfig } from "./types";
import { goToVariableDefinition } from "../codemirror/go-to-definition/commands";

export function focusAndScrollCellIntoView({
cellId,
Expand Down Expand Up @@ -57,7 +57,7 @@ export function focusAndScrollCellIntoView({
},
});
} else if (variableName) {
goToDefinition(editor, variableName);
goToVariableDefinition(editor, variableName);
}
}

Expand Down
7 changes: 3 additions & 4 deletions frontend/src/core/codemirror/cells/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getEditorCodeAsPython } from "../language/utils";
import { formattingChangeEffect } from "../format";
import { closeCompletion, completionStatus } from "@codemirror/autocomplete";
import { isAtEndOfEditor, isAtStartOfEditor } from "../utils";
import { goToDefinition } from "../go-to-definition";
import { goToDefinitionAtCursorPosition } from "../go-to-definition/utils";

export interface MovementCallbacks
extends Pick<
Expand Down Expand Up @@ -246,7 +246,7 @@ export function cellMovementBundle(
preventDefault: true,
stopPropagation: true,
run: (ev) => {
goToDefinition(ev);
goToDefinitionAtCursorPosition(ev);
return true;
},
},
Expand All @@ -255,8 +255,7 @@ export function cellMovementBundle(
preventDefault: true,
stopPropagation: true,
run: (ev) => {
const cursorPos = ev.state.selection.main.head;
splitCell({ cellId, cursorPos });
splitCell({ cellId });
requestAnimationFrame(() => {
ev.contentDOM.blur();
moveToNextCell({ cellId, before: false }); // focus new cell
Expand Down
15 changes: 9 additions & 6 deletions frontend/src/core/codemirror/cm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,20 @@ import {
EditorView,
} from "@codemirror/view";

import { EditorState, Extension, Prec } from "@codemirror/state";
import { EditorState, type Extension, Prec } from "@codemirror/state";
import { oneDark } from "@codemirror/theme-one-dark";

import { CompletionConfig, KeymapConfig } from "../config/config-schema";
import { Theme } from "../../theme/useTheme";
import type { CompletionConfig, KeymapConfig } from "../config/config-schema";
import type { Theme } from "../../theme/useTheme";

import { findReplaceBundle } from "./find-replace/extension";
import {
CodeCallbacks,
MovementCallbacks,
type CodeCallbacks,
type MovementCallbacks,
cellCodeEditingBundle,
cellMovementBundle,
} from "./cells/extensions";
import { CellId } from "../cells/ids";
import type { CellId } from "../cells/ids";
import { keymapBundle } from "./keymaps/keymaps";
import { scrollActiveLineIntoView } from "./extensions";
import { copilotBundle } from "./copilot/extension";
Expand All @@ -58,6 +58,7 @@ import {
clickablePlaceholderExtension,
smartPlaceholderExtension,
} from "./placeholder/extensions";
import { goToDefinitionBundle } from "./go-to-definition/extension";

export interface CodeMirrorSetupOpts {
cellId: CellId;
Expand Down Expand Up @@ -91,6 +92,8 @@ export const setupCodeMirror = ({
cellCodeEditingBundle(cellId, cellCodeCallbacks),
// Comes last so that it can be overridden
basicBundle(completionConfig, theme),
// Underline cmd+clickable placeholder
goToDefinitionBundle(),
showPlaceholder
? Prec.highest(smartPlaceholderExtension("import marimo as mo"))
: enableAI
Expand Down
61 changes: 3 additions & 58 deletions frontend/src/core/codemirror/find-replace/search-highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import { RangeSetBuilder, StateEffect, StateField } from "@codemirror/state";
import {
Decoration,
ViewPlugin,
DecorationSet,
type DecorationSet,
EditorView,
ViewUpdate,
type ViewUpdate,
} from "@codemirror/view";
import { QueryType, asQueryCreator } from "./query";
import { type QueryType, asQueryCreator } from "./query";
import { store } from "@/core/state/jotai";
import { findReplaceAtom } from "./state";
import { getAllEditorViews } from "@/core/cells/cells";
import { syntaxTree } from "@codemirror/language";

const setSearchQuery = StateEffect.define<SearchQuery>();

Expand Down Expand Up @@ -147,57 +146,3 @@ export const highlightTheme = EditorView.baseTheme({
backgroundColor: "#6199ff88 !important",
},
});

/**
* This function will select the first occurrence of the given variable name.
*/
export function goToDefinition(
view: EditorView,
variableName: string,
): boolean {
const state = view.state;
const tree = syntaxTree(state);

let found = false;
let from = 0;

tree.iterate({
enter: (node) => {
if (found) {
return false;
} // Stop traversal if found

// Check if the node is an identifier and matches the variable name
if (
node.name === "VariableName" &&
state.doc.sliceString(node.from, node.to) === variableName
) {
from = node.from;
found = true;
return false; // Stop traversal
}

// Skip comments and strings
if (node.name === "Comment" || node.name === "String") {
return false;
}
},
});

if (found) {
view.focus();
view.dispatch({
selection: {
anchor: from,
head: from,
},
// Unfortunately, EditorView.scrollIntoView does
// not support smooth scrolling.
effects: EditorView.scrollIntoView(from, {
y: "center",
}),
});
return true;
}
return false;
}
Loading

0 comments on commit 69bd014

Please sign in to comment.