182
src/components/AstExplorer.tsx
Normal file
182
src/components/AstExplorer.tsx
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
|
||||||
|
export function AstExplorer() {
|
||||||
|
const { ast, setHighlightRange, selectionRanges } = useStore((s) => ({
|
||||||
|
ast: s.ast,
|
||||||
|
setHighlightRange: s.setHighlightRange,
|
||||||
|
selectionRanges: s.selectionRanges,
|
||||||
|
}))
|
||||||
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
selectionRanges.codeBasedSelections?.[0]?.range
|
||||||
|
)
|
||||||
|
const node = getNodeFromPath(ast, pathToNode).node
|
||||||
|
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative" style={{ width: '300px' }}>
|
||||||
|
<div className="">
|
||||||
|
filter out keys:<div className="w-2 inline-block"></div>
|
||||||
|
{['start', 'end', 'type'].map((key) => {
|
||||||
|
return (
|
||||||
|
<label key={key} className="inline-flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox"
|
||||||
|
checked={filterKeys.includes(key)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (filterKeys.includes(key)) {
|
||||||
|
setFilterKeys(filterKeys.filter((k) => k !== key))
|
||||||
|
} else {
|
||||||
|
setFilterKeys([...filterKeys, key])
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="mr-2">{key}</span>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="h-full relative"
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
setHighlightRange([0, 0])
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<pre className=" text-xs overflow-y-auto" style={{ width: '300px' }}>
|
||||||
|
<DisplayObj obj={ast} filterKeys={filterKeys} node={node} />
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DisplayBody({
|
||||||
|
body,
|
||||||
|
filterKeys,
|
||||||
|
node,
|
||||||
|
}: {
|
||||||
|
body: { start: number; end: number; [key: string]: any }[]
|
||||||
|
filterKeys: string[]
|
||||||
|
node: any
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{body.map((b, index) => {
|
||||||
|
return (
|
||||||
|
<div className="my-2" key={index}>
|
||||||
|
<DisplayObj obj={b} filterKeys={filterKeys} node={node} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DisplayObj({
|
||||||
|
obj,
|
||||||
|
filterKeys,
|
||||||
|
node,
|
||||||
|
}: {
|
||||||
|
obj: { start: number; end: number; [key: string]: any }
|
||||||
|
filterKeys: string[]
|
||||||
|
node: any
|
||||||
|
}) {
|
||||||
|
const { setHighlightRange, setCursor2 } = useStore((s) => ({
|
||||||
|
setHighlightRange: s.setHighlightRange,
|
||||||
|
setCursor2: s.setCursor2,
|
||||||
|
}))
|
||||||
|
const ref = useRef<HTMLPreElement>(null)
|
||||||
|
const [hasCursor, setHasCursor] = useState(false)
|
||||||
|
const [isCollapsed, setIsCollapsed] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
node?.start === obj?.start &&
|
||||||
|
node?.end === obj?.end &&
|
||||||
|
node.type === obj?.type
|
||||||
|
) {
|
||||||
|
ref?.current?.scrollIntoView?.({ behavior: 'smooth', block: 'center' })
|
||||||
|
setHasCursor(true)
|
||||||
|
} else {
|
||||||
|
setHasCursor(false)
|
||||||
|
}
|
||||||
|
}, [node.start, node.end, node.type])
|
||||||
|
return (
|
||||||
|
<pre
|
||||||
|
ref={ref}
|
||||||
|
className={`ml-2 border-l border-violet-600 pl-1 ${
|
||||||
|
hasCursor ? 'bg-violet-100/25' : ''
|
||||||
|
}`}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
setHighlightRange([obj?.start || 0, obj.end])
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
onMouseMove={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setHighlightRange([obj?.start || 0, obj.end])
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
setCursor2({ type: 'default', range: [obj?.start || 0, obj.end || 0] })
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isCollapsed ? (
|
||||||
|
<button
|
||||||
|
className="m-0 p-0 border-0"
|
||||||
|
onClick={() => setIsCollapsed(false)}
|
||||||
|
>
|
||||||
|
{'>'}type: {obj.type}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span className="flex">
|
||||||
|
{/* <button className="m-0 p-0 border-0 mb-auto" onClick={() => setIsCollapsed(true)}>{'⬇️'}</button> */}
|
||||||
|
<ul className="inline-block">
|
||||||
|
{Object.entries(obj).map(([key, value]) => {
|
||||||
|
if (filterKeys.includes(key)) {
|
||||||
|
return null
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
{`${key}: [`}
|
||||||
|
<DisplayBody
|
||||||
|
body={value}
|
||||||
|
filterKeys={filterKeys}
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
|
{']'}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
value?.end
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
{key}:
|
||||||
|
<DisplayObj
|
||||||
|
obj={value}
|
||||||
|
filterKeys={filterKeys}
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
typeof value === 'string' ||
|
||||||
|
typeof value === 'number'
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
{key}: {value}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</pre>
|
||||||
|
)
|
||||||
|
}
|
@ -6,6 +6,7 @@ import { useState } from 'react'
|
|||||||
import { ActionButton } from '../components/ActionButton'
|
import { ActionButton } from '../components/ActionButton'
|
||||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { isReducedMotion } from 'lang/util'
|
import { isReducedMotion } from 'lang/util'
|
||||||
|
import { AstExplorer } from './AstExplorer'
|
||||||
|
|
||||||
type SketchModeCmd = Extract<
|
type SketchModeCmd = Extract<
|
||||||
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
|
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
|
||||||
@ -94,6 +95,9 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
|||||||
>
|
>
|
||||||
Send sketch mode command
|
Send sketch mode command
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
<div style={{ height: '400px' }} className="overflow-y-auto">
|
||||||
|
<AstExplorer />
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</CollapsiblePanel>
|
</CollapsiblePanel>
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user