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
1 change: 1 addition & 0 deletions media/icons/methods/delete.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions media/icons/methods/get.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions media/icons/methods/head.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions media/icons/methods/options.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions media/icons/methods/patch.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions media/icons/methods/post.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions media/icons/methods/put.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions media/icons/methods/websocket.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 11 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
trackTreeViewVisible,
} from "./utils/telemetry"
import {
METHOD_ICONS,
getMethodSvgIcon,
type PathOperationTreeItem,
PathOperationTreeProvider,
} from "./vscode/pathOperationTreeProvider"
Expand Down Expand Up @@ -151,6 +151,7 @@ export async function activate(context: vscode.ExtensionContext) {
}

const pathOperationProvider = new PathOperationTreeProvider(
context.extensionUri,
apps,
groupApps(apps),
)
Expand Down Expand Up @@ -302,7 +303,12 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
watcher,
treeView,
registerCommands(pathOperationProvider, codeLensProvider, groupApps),
registerCommands(
context.extensionUri,
pathOperationProvider,
codeLensProvider,
groupApps,
),
{ dispose: () => clearInterval(telemetryFlushInterval) },
)
}
Expand Down Expand Up @@ -379,6 +385,7 @@ function registerCloudCommands(
}

function registerCommands(
extensionUri: vscode.Uri,
pathOperationProvider: PathOperationTreeProvider,
codeLensProvider: TestCodeLensProvider,
groupApps: (
Expand Down Expand Up @@ -419,7 +426,8 @@ function registerCommands(
.map((route) => {
const path = stripLeadingDynamicSegments(route.path)
return {
label: `$(${METHOD_ICONS[route.method]}) ${route.method.toUpperCase()} ${path}`,
label: `${route.method.toUpperCase()} ${path}`,
iconPath: getMethodSvgIcon(extensionUri, route.method),
description: route.functionName,
detail: vscode.Uri.parse(route.location.filePath)
.fsPath.replace(workspacePrefix, "")
Expand Down
35 changes: 24 additions & 11 deletions src/test/providers/pathOperationTreeProvider.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as assert from "node:assert"
import { Uri } from "vscode"
import type {
AppDefinition,
RouteDefinition,
Expand Down Expand Up @@ -133,11 +134,13 @@ suite("getRouteLabel", () => {
})
})

const testExtUri = Uri.file("/test-extension")

suite("PathOperationTreeProvider", () => {
let provider: PathOperationTreeProvider

setup(() => {
provider = new PathOperationTreeProvider(mockApps)
provider = new PathOperationTreeProvider(testExtUri, mockApps)
})

test("getChildren returns apps at root level", () => {
Expand Down Expand Up @@ -173,7 +176,7 @@ suite("PathOperationTreeProvider", () => {
makeRoute("POST", "/users"),
makeRoute("PUT", "/users"),
]
const p = new PathOperationTreeProvider([app])
const p = new PathOperationTreeProvider(testExtUri, [app])
const children = p.getChildren(p.getChildren()[0])
const labels = children.map((c) =>
c.type === "route" ? getRouteLabel(c.route) : "",
Expand Down Expand Up @@ -344,7 +347,7 @@ suite("PathOperationTreeProvider", () => {
location: { filePath: "users.py", line: 10, column: 0 },
},
]
const p = new PathOperationTreeProvider([app])
const p = new PathOperationTreeProvider(testExtUri, [app])
const appItem = p.getChildren()[0]
const route = p.getChildren(appItem).find((c) => c.type === "route")!
const treeItem = p.getTreeItem(route)
Expand All @@ -363,7 +366,7 @@ suite("PathOperationTreeProvider", () => {
})

test("getChildren returns message when no apps", () => {
const emptyProvider = new PathOperationTreeProvider([])
const emptyProvider = new PathOperationTreeProvider(testExtUri, [])
const roots = emptyProvider.getChildren()
assert.strictEqual(roots.length, 1, "Should return one message item")
assert.strictEqual(roots[0].type, "message")
Expand All @@ -380,7 +383,7 @@ suite("PathOperationTreeProvider", () => {
})

test("getTreeItem for message type", () => {
const emptyProvider = new PathOperationTreeProvider([])
const emptyProvider = new PathOperationTreeProvider(testExtUri, [])
const msg = emptyProvider.getChildren()[0]
assert.strictEqual(
emptyProvider.getTreeItem(msg).label,
Expand All @@ -390,7 +393,12 @@ suite("PathOperationTreeProvider", () => {

test("getTreeItem for workspace type", () => {
const wsRoots = groupAppsByWorkspace(mockApps)
const wsProvider = new PathOperationTreeProvider(mockApps, wsRoots)

const wsProvider = new PathOperationTreeProvider(
testExtUri,
mockApps,
wsRoots,
)
const workspace = wsProvider
.getChildren()
.find((r) => r.type === "workspace")
Expand Down Expand Up @@ -420,7 +428,8 @@ suite("PathOperationTreeProvider", () => {
}
const app = makeApp("app", "main.py")
app.routers = [parentRouter]
const p = new PathOperationTreeProvider([app])

const p = new PathOperationTreeProvider(testExtUri, [app])
assert.strictEqual(
p.getTreeItem({ type: "router", router: childRouter }).label,
"/settings",
Expand All @@ -438,11 +447,11 @@ suite("PathOperationTreeProvider", () => {
})

test("dispose cleans up", () => {
new PathOperationTreeProvider([]).dispose()
new PathOperationTreeProvider(testExtUri, []).dispose()
})

test("setApps updates apps and refreshes tree", () => {
const p = new PathOperationTreeProvider([])
const p = new PathOperationTreeProvider(testExtUri, [])
assert.strictEqual(p.getChildren()[0].type, "message")
p.setApps(mockApps)
assert.strictEqual(p.getChildren()[0].type, "app")
Expand Down Expand Up @@ -473,7 +482,11 @@ suite("PathOperationTreeProvider", () => {

test("getParent returns workspace for app in multi-root", () => {
const wsRoots = groupAppsByWorkspace(mockApps)
const wsProvider = new PathOperationTreeProvider(mockApps, wsRoots)
const wsProvider = new PathOperationTreeProvider(
testExtUri,
mockApps,
wsRoots,
)
const workspace = wsProvider
.getChildren()
.find((r) => r.type === "workspace")!
Expand All @@ -500,7 +513,7 @@ suite("PathOperationTreeProvider", () => {
}
const app = makeApp("app", "main.py")
app.routers = [parentRouter]
const p = new PathOperationTreeProvider([app])
const p = new PathOperationTreeProvider(testExtUri, [app])
const parent = p.getParent({ type: "router", router: childRouter })
assert.strictEqual(parent?.type, "router")
})
Expand Down
34 changes: 21 additions & 13 deletions src/vscode/pathOperationTreeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ export type PathOperationTreeItem =
| { type: "route"; route: RouteDefinition }
| { type: "message"; text: string }

export function getMethodSvgIcon(extensionUri: Uri, method: RouteMethod): Uri {
return Uri.joinPath(
extensionUri,
"media",
"icons",
"methods",
`${method.toLowerCase()}.svg`,
)
}
const METHOD_ORDER: Record<RouteMethod, number> = {
GET: 0,
POST: 1,
Expand All @@ -34,17 +43,6 @@ const METHOD_ORDER: Record<RouteMethod, number> = {
WEBSOCKET: 7,
}

export const METHOD_ICONS: Record<RouteMethod, string> = {
GET: "arrow-right",
POST: "plus",
PUT: "edit",
DELETE: "trash",
PATCH: "pencil",
OPTIONS: "settings-gear",
HEAD: "eye",
WEBSOCKET: "broadcast",
}

export function getAppLabel(app: AppDefinition): string {
if (app.name !== "app") return app.name
const pathParts = app.filePath.split("/")
Expand Down Expand Up @@ -132,7 +130,14 @@ export class PathOperationTreeProvider
private routersExpanded = false
private toggleCount = 0

constructor(apps: AppDefinition[] = [], roots?: PathOperationTreeItem[]) {
private extensionUri: Uri

constructor(
extensionUri: Uri,
apps: AppDefinition[] = [],
roots?: PathOperationTreeItem[],
) {
this.extensionUri = extensionUri
this.apps = apps
this.roots = roots ?? apps.map((app) => ({ type: "app" as const, app }))
}
Expand Down Expand Up @@ -267,7 +272,10 @@ export class PathOperationTreeProvider
case "route": {
const routeItem = new TreeItem(getRouteLabel(element.route))
routeItem.description = element.route.functionName
routeItem.iconPath = new ThemeIcon(METHOD_ICONS[element.route.method])
routeItem.iconPath = getMethodSvgIcon(
this.extensionUri,
element.route.method,
)
routeItem.contextValue = "route"
const tooltipPath = stripLeadingDynamicSegments(element.route.path)
const docstringSection = element.route.docstring
Expand Down
Loading