diff --git a/package.json b/package.json index ecc4dbd..b8c840f 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,19 @@ "command": "fastapi-vscode.reportIssue", "title": "Report Issue...", "category": "FastAPI" + }, + { + "command": "fastapi-vscode.searchEndpoints", + "title": "Search Endpoints...", + "category": "FastAPI", + "icon": "$(search)" + } + ], + "keybindings": [ + { + "command": "fastapi-vscode.searchEndpoints", + "key": "ctrl+shift+e", + "mac": "cmd+shift+e" } ], "menus": { diff --git a/src/extension.ts b/src/extension.ts index 3c5c513..dd4ca8d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,15 +11,14 @@ import type { SourceLocation } from "./core/types" import { type EndpointTreeItem, EndpointTreeProvider, + METHOD_ICONS, } from "./providers/endpointTreeProvider" import { TestCodeLensProvider } from "./providers/testCodeLensProvider" -import { vscodeFileSystem } from "./providers/vscodeFileSystem" import { disposeLogger, log } from "./utils/logger" let parserService: Parser | null = null function navigateToLocation(location: SourceLocation): void { - // filePath is now a URI string, parse it back to vscode.Uri const uri = vscode.Uri.parse(location.filePath) const position = new vscode.Position(location.line - 1, location.column) vscode.window.showTextDocument(uri, { @@ -138,6 +137,43 @@ function registerCommands( }, ), + vscode.commands.registerCommand( + "fastapi-vscode.searchEndpoints", + async () => { + const workspacePrefix = + vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? "" + const items = endpointProvider + .getAllRoutes() + .map((route) => { + const path = stripLeadingDynamicSegments(route.path) + return { + label: `$(${METHOD_ICONS[route.method]}) ${route.method.toUpperCase()} ${path}`, + description: route.functionName, + detail: vscode.Uri.parse(route.location.filePath) + .fsPath.replace(workspacePrefix, "") + .replace(/^\//, ""), + route, + sortKey: `${path} ${route.method}`, + } + }) + .sort((a, b) => a.sortKey.localeCompare(b.sortKey)) + + if (items.length === 0) { + vscode.window.showInformationMessage( + "No FastAPI endpoints found in the workspace.", + ) + return + } + + const selected = await vscode.window.showQuickPick(items, { + placeHolder: "Search FastAPI endpoints...", + }) + if (selected) { + navigateToLocation(selected.route.location) + } + }, + ), + vscode.commands.registerCommand( "fastapi-vscode.copyEndpointPath", (item: EndpointTreeItem) => { diff --git a/src/providers/endpointTreeProvider.ts b/src/providers/endpointTreeProvider.ts index 645940a..29d18a4 100644 --- a/src/providers/endpointTreeProvider.ts +++ b/src/providers/endpointTreeProvider.ts @@ -28,7 +28,7 @@ const defaultGrouping: GroupingFunction = (apps) => apps.map((app) => ({ type: "app" as const, app })) /** Method icons for route display */ -const METHOD_ICONS: Record = { +export const METHOD_ICONS: Record = { GET: "arrow-right", POST: "plus", PUT: "edit", @@ -56,6 +56,23 @@ export class EndpointTreeProvider this.groupApps = groupApps ?? defaultGrouping } + getApps(): AppDefinition[] { + return this.apps + } + + /** Returns all routes from all apps, including nested router routes. */ + getAllRoutes(): RouteDefinition[] { + const collectFromRouters = ( + routers: RouterDefinition[], + ): RouteDefinition[] => + routers.flatMap((r) => [...r.routes, ...collectFromRouters(r.children)]) + + return this.apps.flatMap((app) => [ + ...app.routes, + ...collectFromRouters(app.routers), + ]) + } + setApps(apps: AppDefinition[], groupApps?: GroupingFunction): void { this.apps = apps if (groupApps) {