Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ function FilePaneContent({ context, workspaceId }: FilePaneProps) {
document={document}
filePath={filePath}
workspaceId={workspaceId}
initialLine={data.line}
initialColumn={data.column}
cursorRequestId={data.cursorRequestId}
onChangeView={handleChangeView}
onForceView={handleForceView}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export interface ViewProps {
document: SharedFileDocument;
filePath: string;
workspaceId: string;
initialLine?: number;
initialColumn?: number;
cursorRequestId?: string;
onChangeView: (viewId: string) => void;
onForceView: (viewId: string) => void;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { detectLanguage } from "shared/detect-language";
import type { ViewProps } from "../../types";
import { CodeEditor } from "./components/CodeEditor";

export function CodeView({ document, filePath }: ViewProps) {
export function CodeView({
document,
filePath,
initialLine,
initialColumn,
cursorRequestId,
}: ViewProps) {
if (document.content.kind !== "text") {
return null;
}
Expand All @@ -12,6 +18,9 @@ export function CodeView({ document, filePath }: ViewProps) {
key={document.id}
value={document.content.value}
language={detectLanguage(filePath)}
initialLine={initialLine}
initialColumn={initialColumn}
cursorRequestId={cursorRequestId}
onChange={(next) => document.setContent(next)}
onSave={() => void document.save()}
fillHeight
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
indentOnInput,
} from "@codemirror/language";
import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
import { Compartment, EditorState } from "@codemirror/state";
import { Compartment, EditorSelection, EditorState } from "@codemirror/state";
import {
drawSelection,
dropCursor,
Expand All @@ -26,7 +26,7 @@ import {
import { colorPicker } from "@replit/codemirror-css-color-picker";
import { cn } from "@superset/ui/utils";
import { useQuery } from "@tanstack/react-query";
import { type MutableRefObject, useEffect, useRef } from "react";
import { type MutableRefObject, useEffect, useRef, useState } from "react";
import { electronTrpcClient } from "renderer/lib/trpc-client";
import { useResolvedTheme } from "renderer/stores/theme";
import {
Expand All @@ -44,6 +44,9 @@ import { getCodeSyntaxHighlighting } from "./syntax-highlighting";
interface CodeEditorProps {
value: string;
language: string;
initialLine?: number;
initialColumn?: number;
cursorRequestId?: string;
readOnly?: boolean;
fillHeight?: boolean;
className?: string;
Expand All @@ -55,6 +58,9 @@ interface CodeEditorProps {
export function CodeEditor({
value,
language,
initialLine,
initialColumn,
cursorRequestId,
readOnly = false,
fillHeight = true,
className,
Expand All @@ -64,6 +70,7 @@ export function CodeEditor({
}: CodeEditorProps) {
const containerRef = useRef<HTMLDivElement | null>(null);
const viewRef = useRef<EditorView | null>(null);
const [viewReady, setViewReady] = useState(false);
const languageCompartment = useRef(new Compartment()).current;
const themeCompartment = useRef(new Compartment()).current;
const editableCompartment = useRef(new Compartment()).current;
Expand Down Expand Up @@ -160,6 +167,7 @@ export function CodeEditor({
if (editorRef) {
editorRef.current = adapter;
}
setViewReady(true);

return () => {
if (editorRef?.current === adapter) {
Expand Down Expand Up @@ -258,6 +266,28 @@ export function CodeEditor({
};
}, [language, languageCompartment]);

useEffect(() => {
if (initialLine === undefined || cursorRequestId === undefined) {
return;
}
const view = viewRef.current;
if (!view || !viewReady) {
return;
}

const safeLine = Math.max(1, Math.min(initialLine, view.state.doc.lines));
const lineInfo = view.state.doc.line(safeLine);
const col = initialColumn ?? 1;
const offset = Math.min(col - 1, lineInfo.length);
const anchor = lineInfo.from + Math.max(0, offset);

view.dispatch({
selection: EditorSelection.cursor(anchor),
scrollIntoView: true,
});
view.focus();
}, [cursorRequestId, viewReady, initialLine, initialColumn]);

return (
<div
ref={containerRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,23 +231,34 @@ function WorkspaceContent({
// sidebar open into a single `openFilePane(filePath, openInNewTab)`; the
// fork keeps two variants so memo tabs can forward the derived title.
const openFilePane = useCallback(
(filePath: string, displayName?: string) => {
(
filePath: string,
displayName?: string,
location?: { line?: number; column?: number },
) => {
recordRecentlyViewed(filePath);
const state = store.getState();
const cursorRequestId =
location?.line !== undefined ? crypto.randomUUID() : undefined;
const active = state.getActivePane();
if (
active?.pane.kind === "file" &&
(active.pane.data as FilePaneData).filePath === filePath
) {
if (
displayName &&
(active.pane.data as FilePaneData).displayName !== displayName
) {
const activeData = active.pane.data as FilePaneData;
const shouldUpdateData =
(displayName && activeData.displayName !== displayName) ||
location?.line !== undefined ||
location?.column !== undefined;
if (shouldUpdateData) {
state.setPaneData({
paneId: active.pane.id,
data: {
...(active.pane.data as FilePaneData),
displayName,
...activeData,
displayName: displayName ?? activeData.displayName,
line: location?.line,
column: location?.column,
cursorRequestId,
Comment thread
MocA-Love marked this conversation as resolved.
} as FilePaneData,
});
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand All @@ -261,6 +272,9 @@ function WorkspaceContent({
filePath,
mode: "editor",
displayName,
line: location?.line,
column: location?.column,
cursorRequestId,
} as FilePaneData,
},
});
Expand Down Expand Up @@ -506,7 +520,7 @@ function WorkspaceContent({
navigate,
openFilePaths: openFilePathsList,
recentFilePaths: recentFilePathsList,
onSelectFile: ({ close, filePath, targetWorkspaceId }) => {
onSelectFile: ({ close, filePath, targetWorkspaceId, line, column }) => {
close();
if (targetWorkspaceId !== workspaceId) {
void navigate({
Expand All @@ -515,7 +529,7 @@ function WorkspaceContent({
});
return;
}
openFilePane(filePath);
openFilePane(filePath, undefined, { line, column });
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ export interface FilePaneData {
/** FORK NOTE: carried for memo tabs so the tab title shows the
* memo-derived displayName instead of the random filename. */
displayName?: string;
line?: number;
column?: number;
cursorRequestId?: string;
language?: string;
viewId?: string;
forceViewId?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { CommandPalette } from "./CommandPalette";
export { parseQuickOpenQuery } from "./parseQuickOpenQuery";
export { useCommandPalette } from "./useCommandPalette";
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { describe, expect, it } from "bun:test";
import { parseQuickOpenQuery } from "./parseQuickOpenQuery";

describe("parseQuickOpenQuery", () => {
it("returns the query as-is when no line suffix", () => {
expect(parseQuickOpenQuery("foo.ts")).toEqual({
searchQuery: "foo.ts",
});
});

it("returns the query as-is for empty string", () => {
expect(parseQuickOpenQuery("")).toEqual({
searchQuery: "",
});
});

it("parses file:line", () => {
expect(parseQuickOpenQuery("foo.ts:123")).toEqual({
searchQuery: "foo.ts",
line: 123,
});
});

it("parses file:line:column", () => {
expect(parseQuickOpenQuery("foo.ts:123:45")).toEqual({
searchQuery: "foo.ts",
line: 123,
column: 45,
});
});

it("parses path with directories and line", () => {
expect(parseQuickOpenQuery("src/components/App.tsx:42")).toEqual({
searchQuery: "src/components/App.tsx",
line: 42,
});
});

it("returns query as-is when line is zero", () => {
expect(parseQuickOpenQuery("foo.ts:0")).toEqual({
searchQuery: "foo.ts:0",
});
});

it("returns query as-is when line is negative", () => {
expect(parseQuickOpenQuery("foo.ts:-5")).toEqual({
searchQuery: "foo.ts:-5",
});
});

it("returns query as-is for non-numeric suffix", () => {
expect(parseQuickOpenQuery("foo.ts:abc")).toEqual({
searchQuery: "foo.ts:abc",
});
});

it("returns query as-is when path part is empty", () => {
expect(parseQuickOpenQuery(":123")).toEqual({
searchQuery: ":123",
});
});

it("trims whitespace", () => {
expect(parseQuickOpenQuery(" foo.ts:10 ")).toEqual({
searchQuery: "foo.ts",
line: 10,
});
});

it("returns query as-is for column zero", () => {
expect(parseQuickOpenQuery("foo.ts:10:0")).toEqual({
searchQuery: "foo.ts:10:0",
});
});

it("handles Windows-style paths", () => {
expect(parseQuickOpenQuery("src\\utils\\helpers.ts:99")).toEqual({
searchQuery: "src\\utils\\helpers.ts",
line: 99,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
interface ParsedQuickOpenQuery {
searchQuery: string;
line?: number;
column?: number;
}

export function parseQuickOpenQuery(query: string): ParsedQuickOpenQuery {
const trimmedQuery = query.trim();
if (!trimmedQuery) {
return { searchQuery: "" };
}

// Try to extract :line[:column] from the end of the query.
// We need to handle paths like "foo.ts:123" and "foo.ts:123:45"
// but NOT "C:\path" (drive letter colon).
const lastColon = trimmedQuery.lastIndexOf(":");
if (lastColon <= 0) {
return { searchQuery: trimmedQuery };
}

const afterLastColon = trimmedQuery.slice(lastColon + 1);
if (!/^\d+$/.test(afterLastColon)) {
return { searchQuery: trimmedQuery };
}

const maybeColumn = Number.parseInt(afterLastColon, 10);

// Check if there's another colon:digits before this one (line:column pattern)
const beforeLastColon = trimmedQuery.slice(0, lastColon);
const secondLastColon = beforeLastColon.lastIndexOf(":");
let pathPart: string;
let line: number;
let column: number | undefined;

if (
secondLastColon > 0 &&
/^\d+$/.test(beforeLastColon.slice(secondLastColon + 1))
) {
// Pattern: path:line:column
const maybeLine = Number.parseInt(
beforeLastColon.slice(secondLastColon + 1),
10,
);
pathPart = beforeLastColon.slice(0, secondLastColon).trim();
line = maybeLine;
column = maybeColumn;

if (!pathPart || line <= 0 || column <= 0) {
return { searchQuery: trimmedQuery };
}
} else {
// Pattern: path:line
pathPart = beforeLastColon.trim();
line = maybeColumn;

if (!pathPart || line <= 0) {
return { searchQuery: trimmedQuery };
}
}

return {
searchQuery: pathPart,
line,
column,
};
}
Loading
Loading