Skip to content
Open
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
2 changes: 0 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: lts/-1
cache: pnpm

- name: 📦 Install dependencies
run: pnpm install --frozen-lockfile
Expand All @@ -42,7 +41,6 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: lts/-1
cache: pnpm

- run: npx changelogithub
env:
Expand Down
1 change: 1 addition & 0 deletions shared/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const filterTree = z.object({
startsWith: z.string(),
sourceFileName: z.string(),
position: z.literal('').or(z.number()),
excludePathIncludes: z.string().default(''),
})
export type FilterTree = z.infer<typeof filterTree>

Expand Down
2 changes: 1 addition & 1 deletion src/handleMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function handleMessage(panel: vscode.WebviewPanel, message: unknown): voi
log(...data.value)
break
case 'filterTree': {
showTree(data.startsWith, data.sourceFileName, data.position, false)
showTree(data.startsWith, data.sourceFileName, data.position, false, data.excludePathIncludes)
break
}
case 'saveOpen': {
Expand Down
19 changes: 14 additions & 5 deletions src/traceTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { TraceData, TraceLine, TypeLine } from '../shared/src/traceData'
import { getWorkspacePath } from './storage'
import { postMessage } from './webview'
import { traceFiles } from './appState'
import { isPathExcluded, parsePathExcludes } from './treeFilters'

export interface Tree { id: number, line: TraceLine, children: Tree[], types: TypeLine[], childCnt: number, childTypeCnt: number, typeCnt: number }
function getRoot(): Tree {
Expand Down Expand Up @@ -80,13 +81,21 @@ export async function processTraceFiles() {
traceTree = toTree(Object.values(traceFiles.value).flat(1), workspacePath)
}

export function filterTree(startsWith: string, sourceFileName: string, position: number | '', tree = traceTree): Tree[] {
export function filterTree(startsWith: string, sourceFileName: string, position: number | '', excludePathIncludes = '', tree = traceTree): Tree[] {
if (position === '')
position = 0

const pathExcludes = parsePathExcludes(excludePathIncludes)
return filterTreeInner(startsWith, sourceFileName, position, pathExcludes, tree)
}

function filterTreeInner(startsWith: string, sourceFileName: string, position: number, pathExcludes: readonly string[], tree = traceTree): Tree[] {
if (!tree)
return []

if (isPathExcluded(tree.line.args?.path, pathExcludes))
return []

if (
('name' in tree.line && tree.line.name.startsWith(startsWith))
&& (!sourceFileName || ((tree.line.args?.path ?? '').endsWith(sourceFileName)))
Expand All @@ -95,21 +104,21 @@ export function filterTree(startsWith: string, sourceFileName: string, position:
return [tree]
}

return tree.children.map(child => filterTree(startsWith, sourceFileName, position, child)).flat()
return tree.children.map(child => filterTreeInner(startsWith, sourceFileName, position, pathExcludes, child)).flat()
}

export const treeIdNodes = new Map<number, Tree>()
let showTreeInterval: undefined | ReturnType<typeof setInterval>
export function showTree(startsWith: string, sourceFileName: string, position: number | '', updateUi = true, tree = traceTree) {
export function showTree(startsWith: string, sourceFileName: string, position: number | '', updateUi = true, excludePathIncludes = '', tree = traceTree) {
if (showTreeInterval) {
clearInterval(showTreeInterval)
showTreeInterval = undefined
}

const nodes = filterTree(startsWith, sourceFileName, position, tree)
const nodes = filterTree(startsWith, sourceFileName, position, excludePathIncludes, tree)
const skinnyNodes = nodes.map(x => ({ ...x, children: [], types: [] }))
if (updateUi)
postMessage({ message: 'filterTree', startsWith, sourceFileName, position })
postMessage({ message: 'filterTree', startsWith, sourceFileName, position, excludePathIncludes })

postMessage({ message: 'showTree', nodes: [], step: 'start' })

Expand Down
14 changes: 14 additions & 0 deletions src/treeFilters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function parsePathExcludes(input: string): string[] {
return input
.split(/[,\n]/)
.map(value => value.trim().replace(/\\/g, '/').toLowerCase())
.filter(Boolean)
}

export function isPathExcluded(path: string | undefined, excludes: readonly string[]) {
if (!path || excludes.length === 0)
return false

const normalizedPath = path.replace(/\\/g, '/').toLowerCase()
return excludes.some(exclude => normalizedPath.includes(exclude))
}
16 changes: 16 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import { describe, expect, it } from 'vitest'
import { isPathExcluded, parsePathExcludes } from '../src/treeFilters'

describe('should', () => {
it('exported', () => {
expect(1).toEqual(1)
})

it('parses path exclude filters', () => {
expect(parsePathExcludes('node_modules, src/generated\nlib.dom.d.ts'))
.toEqual(['node_modules', 'src/generated', 'lib.dom.d.ts'])
})

it('matches excluded paths case-insensitively', () => {
const excludes = parsePathExcludes('node_modules,lib.dom.d.ts')

expect(isPathExcluded('packages/app/node_modules/typescript/lib/lib.dom.d.ts', excludes))
.toBe(true)

expect(isPathExcluded('src/components/Button.tsx', excludes))
.toBe(false)
})
})
9 changes: 7 additions & 2 deletions ui/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const sendMesage = useNuxtApp().$sendMessage

const sortOptions = ['Timestamp', 'Duration', 'Types', 'Total Types'] as const

const filters = useState('treeFilters', () => ({ startsWith: 'check', sourceFileName: '', position: 0 as number | '' }))
const filters = useState('treeFilters', () => ({ startsWith: 'check', sourceFileName: '', position: 0 as number | '', excludePathIncludes: '' }))

function setStartsWith(event: any) {
filters.value.startsWith = event.target.value
Expand All @@ -21,13 +21,17 @@ function setPosition(event: any) {
filters.value.position = +event.target.value
}

function setExcludePathIncludes(event: any) {
filters.value.excludePathIncludes = event.target.value
}

function handleMessage(e: MessageEvent<unknown>) {
const message = Messages.message.safeParse(e.data)
if (!message.success)
return

if (message.data.message === 'gotoTracePosition')
filters.value = { startsWith: '', position: message.data.position, sourceFileName: message.data.fileName }
filters.value = { startsWith: '', position: message.data.position, sourceFileName: message.data.fileName, excludePathIncludes: filters.value.excludePathIncludes }

else if (message.data.message === 'filterTree')
filters.value = message.data
Expand Down Expand Up @@ -59,6 +63,7 @@ onMounted(() => {
<VTextField v-model="filters.startsWith" label="Trace Name" @change="setStartsWith" />
<VTextField v-model="filters.sourceFileName" label="Source File" @change="setSourceFileName" />
<VTextField v-model="filters.position" label="Position" type="number" @change="setPosition" />
<VTextField v-model="filters.excludePathIncludes" label="Exclude Paths" @change="setExcludePathIncludes" />
<vscode-button class="w-full" @click="doFilters">
Filter Trace <UIcon name="heroicons:magnifying-glass-circle" :dynamic="true" size="20" />
</vscode-button>
Expand Down