Skip to content
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

feat: go-to-definition with cmd+click #1425

Merged
merged 5 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Refactored splitCell function

The refactoring of the splitCell function to use splitEditor is a good improvement. However, ensure that splitEditor handles all edge cases correctly, especially with different cursor positions and document states.

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

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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Type import for RefObject

Using import type for RefObject is a good practice to ensure that only the type information is imported, which can help with tree-shaking and reducing bundle size.

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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Unused import removed

The removal of the syntaxTree import is appropriate since it is no longer used in this file. This helps in keeping the code clean and avoiding unnecessary imports.


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
Loading