diff --git a/.github/ISSUE_TEMPLATE/workflows/callstack-reviewer.yml b/.github/ISSUE_TEMPLATE/workflows/callstack-reviewer.yml new file mode 100644 index 000000000..1bca86841 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/workflows/callstack-reviewer.yml @@ -0,0 +1,31 @@ +name: Callstack.ai PR Review + +on: + workflow_dispatch: + inputs: + config: + type: string + description: "config for reviewer" + required: true + head: + type: string + description: "head commit sha" + required: true + base: + type: string + description: "base commit sha" + required: false + +jobs: + callstack_pr_review_job: + runs-on: ubuntu-latest + steps: + - name: Review PR + uses: callstackai/action@v1.0.7 + with: + config: ${{ inputs.config }} + head: ${{ inputs.head }} + openai_key: ${{ secrets.OPENAI_KEY_2 }} + export: /code/chats.json + channel: pre-release + tag: develop diff --git a/.github/workflows/callstack-reviewer.yml b/.github/workflows/callstack-reviewer.yml new file mode 100644 index 000000000..1bca86841 --- /dev/null +++ b/.github/workflows/callstack-reviewer.yml @@ -0,0 +1,31 @@ +name: Callstack.ai PR Review + +on: + workflow_dispatch: + inputs: + config: + type: string + description: "config for reviewer" + required: true + head: + type: string + description: "head commit sha" + required: true + base: + type: string + description: "base commit sha" + required: false + +jobs: + callstack_pr_review_job: + runs-on: ubuntu-latest + steps: + - name: Review PR + uses: callstackai/action@v1.0.7 + with: + config: ${{ inputs.config }} + head: ${{ inputs.head }} + openai_key: ${{ secrets.OPENAI_KEY_2 }} + export: /code/chats.json + channel: pre-release + tag: develop diff --git a/website/css/style.css b/website/css/style.css index 87e3b9823..0ded3a466 100644 --- a/website/css/style.css +++ b/website/css/style.css @@ -347,7 +347,7 @@ li.entry { } .CodeMirror .marked, -.entry.focused { +.entry.highlighted { border-radius: 2px; background-color: rgba(255,240,6,0.4); } diff --git a/website/src/components/ASTOutput.js b/website/src/components/ASTOutput.js index ab5f478db..a7fa8f421 100644 --- a/website/src/components/ASTOutput.js +++ b/website/src/components/ASTOutput.js @@ -2,9 +2,8 @@ import PropTypes from 'prop-types'; import React from 'react'; import cx from 'classnames'; import visualizations from './visualization'; -import getFocusPath from './getFocusPath'; -const {useState, useMemo} = React; +const {useState} = React; function formatTime(time) { if (!time) { @@ -16,17 +15,9 @@ function formatTime(time) { return `${(time / 1000).toFixed(2)}s`; } -export default function ASTOutput({parser, parseResult={}, cursor=null}) { +export default function ASTOutput({parseResult={}, position=null}) { const [selectedOutput, setSelectedOutput] = useState(0); const {ast=null} = parseResult; - - const focusPath = useMemo( - () => ast && cursor != null ? - getFocusPath(parseResult.ast, cursor, parser) : - [], - [ast, cursor, parser], - ); - let output; if (parseResult.error) { @@ -40,7 +31,7 @@ export default function ASTOutput({parser, parseResult={}, cursor=null}) { { React.createElement( visualizations[selectedOutput], - {parseResult, focusPath} + {parseResult, position} ) } @@ -68,15 +59,14 @@ export default function ASTOutput({parser, parseResult={}, cursor=null}) { {formatTime(parseResult.time)} - {output} + {output} ); } ASTOutput.propTypes = { - parser: PropTypes.object.isRequired, parseResult: PropTypes.object, - cursor: PropTypes.any, + position: PropTypes.number, }; class ErrorBoundary extends React.Component { diff --git a/website/src/components/getFocusPath.js b/website/src/components/getFocusPath.js deleted file mode 100644 index b8d7b9f31..000000000 --- a/website/src/components/getFocusPath.js +++ /dev/null @@ -1,45 +0,0 @@ -function isInRange(range, pos) { - return pos >= range[0] && pos <= range[1]; -} - -export function nodeToRange(parser, node) { - let range = parser.nodeToRange(node); - if (range) { - return range; - } - if (node.length > 0) { - // check first and last child - let rangeFirst = node[0] && parser.nodeToRange(node[0]); - let rangeLast = node[node.length - 1] && - parser.nodeToRange(node[node.length - 1]); - if (rangeFirst && rangeLast) { - return [rangeFirst[0], rangeLast[1]]; - } - } -} - -export default function getFocusPath(node, pos, parser, seen = new Set()) { - seen.add(node); - - let path = []; - let range = nodeToRange(parser, node); - if (range) { - if (isInRange(range, pos)) { - path.push(node); - } else { - return []; - } - } - for (let {value} of parser.forEachProperty(node)) { - if (value && typeof value === 'object' && !seen.has(value)) { - let childPath = getFocusPath(value, pos, parser, seen); - if (childPath.length > 0) { - // if current wasn't added, add it now - childPath = range ? childPath : [node].concat(childPath); - path = path.concat(childPath); - break; - } - } - } - return path; -} diff --git a/website/src/components/visualization/SelectedNodeContext.js b/website/src/components/visualization/SelectedNodeContext.js new file mode 100644 index 000000000..6b680f4ea --- /dev/null +++ b/website/src/components/visualization/SelectedNodeContext.js @@ -0,0 +1,32 @@ +import React from 'react'; + +const SelectedNodeContext = React.createContext(); + +function useSelectedNode() { + const context = React.useContext(SelectedNodeContext); + if (!context) { + throw new Error('useSelectedNode must be used within a SelectedNodeContext'); + } + return context; +} + +let unselectCallback; + +function setSelectedNode(node, cb) { + if (unselectCallback) { + unselectCallback(); + } + if (node) { + global.$node = node; + unselectCallback = cb; + } else { + unselectCallback = null; + delete global.$node; + } +} + +function SelectedNodeProvider(props) { + return ; +} + +export {SelectedNodeProvider, useSelectedNode}; diff --git a/website/src/components/visualization/Tree.js b/website/src/components/visualization/Tree.js index 7b2d27491..57b200a99 100644 --- a/website/src/components/visualization/Tree.js +++ b/website/src/components/visualization/Tree.js @@ -4,10 +4,12 @@ import React from 'react'; import PubSub from 'pubsub-js'; import {logEvent} from '../../utils/logger'; import {treeAdapterFromParseResult} from '../../core/TreeAdapter.js'; +import {SelectedNodeProvider} from './SelectedNodeContext.js'; +import focusNodes from './focusNodes.js' import './css/tree.css' -const {useReducer, useMemo} = React; +const {useReducer, useMemo, useRef, useLayoutEffect} = React; const STORAGE_KEY = 'tree_settings'; @@ -48,12 +50,18 @@ function makeCheckbox(name, settings, updateSettings) { ); } -export default function Tree({focusPath, parseResult}) { +export default function Tree({parseResult, position}) { const [settings, updateSettings] = useReducer(reducer, null, initSettings); const treeAdapter = useMemo( () => treeAdapterFromParseResult(parseResult, settings), [parseResult.treeAdapter, settings], ); + const rootElement = useRef(); + + focusNodes('init'); + useLayoutEffect(() => { + focusNodes('focus', rootElement); + }); return (
@@ -73,21 +81,22 @@ export default function Tree({focusPath, parseResult}) { ))}
-