diff --git a/ivette/src/dome/renderer/controls/gallery.json b/ivette/src/dome/renderer/controls/gallery.json index a0dfa165279f7565753ef23a0bae10ef7a562c32..2f6832c0d669f4a4b457f885c4c0b484286e086c 100644 --- a/ivette/src/dome/renderer/controls/gallery.json +++ b/ivette/src/dome/renderer/controls/gallery.json @@ -185,6 +185,16 @@ "viewBox": "0 0 16 16", "path": "M16 6.204l-5.528-0.803-2.472-5.009-2.472 5.009-5.528 0.803 4 3.899-0.944 5.505 4.944-2.599 4.944 2.599-0.944-5.505 4-3.899z" }, + "VARIABLE": { + "section": "Signs", + "viewBox": "0 0 100 100", + "path": "M70.9 39.7c3.1 0 9.1-2.5 9.1-10.6c0-8.1-5.8-8.5-7.6-8.5c-3.6 0-7.1 2.6-10.2 7.9C59.1 34 55.6 40 55.6 40 l-0.1 0c-0.8-3.8-1.4-7-1.7-8.4C53.2 28.3 49.3 21 41.3 21c-8 0-15.3 4.6-15.3 4.6l0 0c-1.4 0.9-2.3 2.4-2.3 4.1 c0 2.7 2.2 4.9 4.9 4.9c0.8 0 1.5-0.2 2.1-0.5l0 0c0 0 6.1-3.4 7.4 0c0.4 1 0.7 2.2 1.1 3.4c1.6 5.2 3 11.4 4.2 17l-5.2 7.6 c0 0-5.9-2.1-9-2.1S20 62.5 20 70.6s5.8 8.5 7.6 8.5c3.6 0 7.1-2.6 10.2-7.9c3.1-5.5 6.6-11.5 6.6-11.5c1 5 1.9 9 2.4 10.6 c2 5.7 6.6 9.1 12.7 9.1c0 0 6.3 0 13.7-4.2c1.8-0.7 3.1-2.5 3.1-4.5c0-2.7-2.2-4.9-4.9-4.9c-0.8 0-1.5 0.2-2.1 0.5l0 0 c0 0-5.3 3-7.1 0.6c-1.3-2.5-2.4-5.7-3.2-9.7c-0.8-3.6-1.7-7.8-2.5-11.9l5.3-7.7C61.9 37.6 67.8 39.7 70.9 39.7z" + }, + "FUNCTION": { + "section": "Signs", + "viewBox": "0 0 512 512", + "path": "M258.317 237.971h-50.626c3.055-17.94 5.876-34.786 8.06-47.875c7.479-44.859 18.362-89.92 28.792-105.361 c11.671-17.281 34.734-8.966 43.627 14.908c4.946 13.278 19.766 21.026 34.801 20.102c20.039-1.239 35.284-18.485 34.048-38.524 c-0.616-10.03-8.273-23.399-20.487-29.936c-17.264-9.456-37.299-13.859-58.424-13.005c-53.37 2.142-93.577 47.609-108.941 97.773 c-5.796 18.933-14.324 60.825-22.212 101.918h-57.92l-9.092 35.228h60.341c-4.836 25.821-8.952 48.184-11.332 59.977 c-6.975 34.576-16.809 96.653-49.552 100.944c-12.823-4.9-17.614-10.366-22.437-15.678c-6.442-7.104-15.941-11.318-26.254-10.682 c-18.037 1.113-31.759 16.63-30.646 34.668c1.112 18.037 17.257 27.962 34.675 30.65c99.4 8.889 137.624-63.47 152.176-122.985 c3.338-13.655 8.99-44.32 14.702-76.894h47.612L258.317 237.971zM504.553 363.021c-6.206 0-12.41 4.542-11.171 11.36c4.101 22.524-24.638 34.794-47.158 32.953 c-34.776-2.849-45.268-38.504-56.929-74.424c-0.598-1.834-1.186-3.696-1.771-5.564c7.052-12.004 14.146-24.176 19.056-32.778 c11.399-19.977 24.456-39.462 32.106-44.964c-0.479 0.35 18.02-10.128 18.097 14.713c0.022 7.167 5.701 13.439 12.998 15.636 c9.729 2.918 19.98-2.597 22.909-12.326c1.459-4.864 0.168-12.55-4.487-17.793c-6.537-7.51-18.688-15.153-28.861-18.45 c-25.709-8.322-52.747 6.216-68.818 27.333c-4.452 5.851-10.398 15.056-16.589 25.086c-8.85-25.625-19.738-47.385-38.156-53.706 c-42.294-14.523-65.772 19.067-65.772 29.537c0 12.494 34.748-5.676 53.366 39.764c6.134 14.993 10.954 27.619 15.937 41.198 c-8.616 14.838-18.24 31.546-24.392 42.493c-11.269 20.054-24.193 39.624-31.805 45.181c-8.533 6.215-18.069-1.701-18.199-14.594 c-0.066-7.16-5.788-13.396-13.099-15.545c-9.75-2.856-19.966 2.73-22.825 12.473c-1.428 4.886-0.08 12.557 4.61 17.771 c6.586 7.461 18.772 15.048 28.97 18.275c25.757 8.147 52.705-6.565 68.628-27.801c5.112-6.803 14.016-21.166 23.213-36.494 c1.19 3.275 2.408 6.642 3.706 10.212c14.891 40.897 48.061 60.698 85.63 55.665c50.882-6.817 67.012-56.806 68.254-77.258 C512 370.972 510.758 363.021 504.553 363.021z" + }, "SPINNER": { "section": "Signs", "viewBox": "0 0 32 32", diff --git a/ivette/src/dome/renderer/frame/style.css b/ivette/src/dome/renderer/frame/style.css index c4dd8a880576ffc1ee883344afac7302cc74de82..eb28656ef99fe2ee598f050769a51305f0e0be1c 100644 --- a/ivette/src/dome/renderer/frame/style.css +++ b/ivette/src/dome/renderer/frame/style.css @@ -93,8 +93,17 @@ .dome-xSideBar.dome-color-frame { background: var(--background-sidebar) ; -} + .dome-xSideBar-title { + justify-content: space-between; + position:sticky; + top:0; + z-index: 10; + padding: 5px; + border-bottom: solid 1px var(--border-discrete); + background-color:var(--background-sidebar); + } +} /* -------------------------------------------------------------------------- */ /* --- SideBar Section --- */ /* -------------------------------------------------------------------------- */ diff --git a/ivette/src/frama-c/index.tsx b/ivette/src/frama-c/index.tsx index e60638fcb9117c38abdc119aea5465e254a61f14..207f30ac074821adaed64a4f384bef4bfdf7d4af 100644 --- a/ivette/src/frama-c/index.tsx +++ b/ivette/src/frama-c/index.tsx @@ -28,7 +28,7 @@ import React from 'react'; import * as Ivette from 'ivette'; import History from 'frama-c/kernel/History'; -import Globals from 'frama-c/kernel/Globals'; +import Globals, { GlobalsFiles } from 'frama-c/kernel/Globals'; import ASTview from 'frama-c/kernel/ASTview'; import ASTinfo from 'frama-c/kernel/ASTinfo'; import SourceCode from 'frama-c/kernel/SourceCode'; @@ -55,6 +55,14 @@ Ivette.registerSidebar({ children: <Globals /> }); +Ivette.registerSidebar({ + id: 'fc.kernel.files', + label: 'Files', + icon: 'FOLDER', + title: 'Files', + children: <GlobalsFiles /> +}); + Ivette.registerToolbar({ id: 'ivette.history', children: <History /> diff --git a/ivette/src/frama-c/kernel/Globals.tsx b/ivette/src/frama-c/kernel/Globals.tsx index 09a3b264d12d228e480d045ebd9fb49cbc3802a9..c31c70d97ab20e8c591f9da110c37399a7a4d207 100644 --- a/ivette/src/frama-c/kernel/Globals.tsx +++ b/ivette/src/frama-c/kernel/Globals.tsx @@ -31,7 +31,9 @@ import { classes } from 'dome/misc/utils'; import { alpha } from 'dome/data/compare'; import { Section, Item } from 'dome/frame/sidebars'; import { Button } from 'dome/controls/buttons'; -import { Label } from 'dome/controls/labels'; +import { Label, Title } from 'dome/controls/labels'; +import * as Toolbar from 'dome/frame/toolbars'; +import { Hbox } from 'dome/layout/boxes'; import InfiniteScroll from 'react-infinite-scroller'; import * as Ivette from 'ivette'; @@ -108,8 +110,31 @@ type ListProps = { children: JSX.Element[]; } & InfiniteScrollableListProps -function List(props: ListProps): JSX.Element { +type InfiniteScrollListProps = { + children: JSX.Element[]; +} & InfiniteScrollableListProps + +function InfiniteScrollList(props: InfiniteScrollListProps): JSX.Element { const [displayedCount, setDisplayedCount] = React.useState(100); + const { children, scrollableParent } = props; + const count = children.length; + return ( + // @ts-expect-error (incompatibility due to @types/react versions) + <InfiniteScroll + pageStart={0} + loadMore={() => setDisplayedCount(displayedCount + 100)} + hasMore={displayedCount < count} + loader={<Label key={-1}>Loading more...</Label>} + useWindow={false} + getScrollParent={() => scrollableParent.current} + > + {children.slice(0, displayedCount)} + </InfiniteScroll> + ); + +} + +function List(props: ListProps): JSX.Element { const { name, total, filteringMenuItems, children, scrollableParent } = props; const Name = name.charAt(0).toUpperCase() + name.slice(1); const count = children.length; @@ -140,18 +165,9 @@ function List(props: ListProps): JSX.Element { </div>; } else { - contents = - // @ts-expect-error (incompatibility due to @types/react versions) - <InfiniteScroll - pageStart={0} - loadMore={() => setDisplayedCount(displayedCount + 100)} - hasMore={displayedCount < count} - loader={<Label key={-1}>Loading more...</Label>} - useWindow={false} - getScrollParent={() => scrollableParent.current} - > - {children.slice(0, displayedCount)} - </InfiniteScroll>; + contents = <InfiniteScrollList scrollableParent={scrollableParent}> + {children} + </InfiniteScrollList>; } return ( @@ -177,6 +193,7 @@ function List(props: ListProps): JSX.Element { interface FctItemProps { fct: functionsData; current: string | undefined; + icon?: string; } function FctItem(props: FctItemProps): JSX.Element { @@ -191,6 +208,7 @@ function FctItem(props: FctItemProps): JSX.Element { ); return ( <Item + icon={props.icon} className={className} label={name} title={signature} @@ -357,11 +375,13 @@ export function Functions(props: FunctionProps): JSX.Element { function makeVarItem( scope: States.Scope, props: Ast.globalsData, + icon?: string ): JSX.Element { const { name, type, decl } = props; return ( <Item key={decl} + icon={icon} label={name} title={type} selected={decl === scope} @@ -372,65 +392,95 @@ function makeVarItem( type VariablesProps = InfiniteScrollableListProps -export function Variables(props: VariablesProps): JSX.Element { - - // Hooks - const scope = States.useCurrentScope(); - const variables = States.useSyncArrayData(Ast.globals); - +interface VariablesFilterRet { + contextMenuItems: Dome.PopupMenuItem[], + showVariable: (vi: Ast.globalsData) => boolean, +} +export function useVariableFilter(): VariablesFilterRet { // Filter settings function useFlipSettings(label: string, b: boolean): setting { return Dome.useFlipSettings('ivette.globals.' + label, b); } - const stdlib = useFlipSettings('stdlib', false); - const extern = useFlipSettings('extern', true); - const nonExtern = useFlipSettings('non-extern', true); - const isConst = useFlipSettings('const', true); - const nonConst = useFlipSettings('non-const', true); - const volatile = useFlipSettings('volatile', true); - const nonVolatile = useFlipSettings('non-volatile', true); - const ghost = useFlipSettings('ghost', true); - const nonGhost = useFlipSettings('non-ghost', true); - const init = useFlipSettings('init', true); - const nonInit = useFlipSettings('non-init', true); - const source = useFlipSettings('source', true); - const nonSource = useFlipSettings('non-source', false); - - function showVariable(vi: Ast.globalsData): boolean { - const visible = - (stdlib[0] || !vi.stdlib) - && (extern[0] || !vi.extern) && (nonExtern[0] || vi.extern) - && (isConst[0] || !vi.const) && (nonConst[0] || vi.const) - && (volatile[0] || !vi.volatile) && (nonVolatile[0] || vi.volatile) - && (ghost[0] || !vi.ghost) && (nonGhost[0] || vi.ghost) - && (init[0] || !vi.init) && (nonInit[0] || vi.init) - && (source[0] || !vi.source) && (nonSource[0] || vi.source); - return !!visible; - } + const stdlibState = useFlipSettings('stdlib', false); + const externState = useFlipSettings('extern', true); + const nonExternState = useFlipSettings('non-extern', true); + const isConstState = useFlipSettings('const', true); + const nonConstState = useFlipSettings('non-const', true); + const volatileState = useFlipSettings('volatile', true); + const nonVolatileState = useFlipSettings('non-volatile', true); + const ghostState = useFlipSettings('ghost', true); + const nonGhostState = useFlipSettings('non-ghost', true); + const initState = useFlipSettings('init', true); + const nonInitState = useFlipSettings('non-init', true); + const sourceState = useFlipSettings('source', true); + const nonSourceState = useFlipSettings('non-source', false); + + const [stdlib, ] = stdlibState; + const [extern, ] = externState; + const [nonExtern, ] = nonExternState; + const [isConst, ] = isConstState; + const [nonConst, ] = nonConstState; + const [volatile, ] = volatileState; + const [nonVolatile, ] = nonVolatileState; + const [ghost, ] = ghostState; + const [nonGhost, ] = nonGhostState; + const [init, ] = initState; + const [nonInit, ] = nonInitState; + const [source, ] = sourceState; + const [nonSource, ] = nonSourceState; + + const showVariable = React.useMemo(() => { + return (vi: Ast.globalsData): boolean => { + const visible = + (stdlib || !vi.stdlib) + && (extern || !vi.extern) && (nonExtern || vi.extern) + && (isConst || !vi.const) && (nonConst || vi.const) + && (volatile || !vi.volatile) && (nonVolatile || vi.volatile) + && (ghost || !vi.ghost) && (nonGhost || vi.ghost) + && (init || !vi.init) && (nonInit || vi.init) + && (source || !vi.source) && (nonSource || vi.source); + return !!visible; + }; + }, [stdlib, extern, nonExtern, isConst, + nonConst, volatile, nonVolatile, ghost, + nonGhost, init, nonInit, source, nonSource + ]); // Context menu to change filter settings const contextMenuItems: Dome.PopupMenuItem[] = [ - menuItem('Show stdlib variables', stdlib), + menuItem('Show stdlib variables', stdlibState), 'separator', - menuItem('Show extern variables', extern), - menuItem('Show non-extern variables', nonExtern), + menuItem('Show extern variables', externState), + menuItem('Show non-extern variables', nonExternState), 'separator', - menuItem('Show const variables', isConst), - menuItem('Show non-const variables', nonConst), + menuItem('Show const variables', isConstState), + menuItem('Show non-const variables', nonConstState), 'separator', - menuItem('Show volatile variables', volatile), - menuItem('Show non-volatile variables', nonVolatile), + menuItem('Show volatile variables', volatileState), + menuItem('Show non-volatile variables', nonVolatileState), 'separator', - menuItem('Show ghost variables', ghost), - menuItem('Show non-ghost variables', nonGhost), + menuItem('Show ghost variables', ghostState), + menuItem('Show non-ghost variables', nonGhostState), 'separator', - menuItem('Show variables with explicit initializer', init), - menuItem('Show variables without explicit initializer', nonInit), + menuItem('Show variables with explicit initializer', initState), + menuItem('Show variables without explicit initializer', nonInitState), 'separator', - menuItem('Show variables from the source code', source), - menuItem('Show variables generated from analyses', nonSource), + menuItem('Show variables from the source code', sourceState), + menuItem('Show variables generated from analyses', nonSourceState), ]; + return { + contextMenuItems: contextMenuItems, + showVariable: showVariable + }; +} + +export function Variables(props: VariablesProps): JSX.Element { + // Hooks + const scope = States.useCurrentScope(); + const variables = States.useSyncArrayData(Ast.globals); + const { showVariable, contextMenuItems } = useVariableFilter(); + // Filtered const items = variables @@ -533,10 +583,148 @@ export function Types(): JSX.Element { ); } +// -------------------------------------------------------------------------- +// --- Files Section +// -------------------------------------------------------------------------- + +type FilesProps = InfiniteScrollableListProps + +export function Files(props: FilesProps): JSX.Element { + const { scrollableParent } = props; + // Hooks + const scope = States.useCurrentScope(); + const { kind, name } = States.useDeclaration(scope); + + // functions + const ker = States.useSyncArrayProxy(Ast.functions); + const eva = States.useSyncArrayProxy(Eva.functions); + const fcts = React.useMemo(() => computeFcts(ker, eva), [ker, eva]); + const { showFunction, contextMenuItems: contextFctMenuItem } + = useFunctionFilter(); + const [showFcts, flipShowFcts] + = Dome.useFlipSettings('ivette.show.functions', true); + + // Variables + const variables = States.useSyncArrayData(Ast.globals); + const { showVariable, contextMenuItems: contextVarMenuItem } + = useVariableFilter(); + const [showVars, flipShowVars] + = Dome.useFlipSettings('ivette.show.globals', true); + + // Currently selected function. + const current = (scope && kind === 'FUNCTION') ? name : undefined; + + interface InfosFile { + label: string, + fcts: functionsData[], + vars: Ast.globalsData[] + } + + type InfosFileList = {[key: string]: InfosFile }; + const files = React.useMemo(() => { + const newFiles: InfosFileList = {}; + + fcts.filter(showFunction) + .sort((f, g) => alpha(f.name, g.name)) + .forEach((fct) => { + if(newFiles[fct.sloc.file]) newFiles[fct.sloc.file].fcts.push(fct); + else newFiles[fct.sloc.file] + = { label: fct.sloc.base, fcts: [fct], vars: [] }; + }); + + variables.filter(showVariable) + .sort((v1, v2) => alpha(v1.name, v2.name)) + .forEach((elt) => { + if(newFiles[elt.sloc.file]) newFiles[elt.sloc.file].vars.push(elt); + else newFiles[elt.sloc.file] + = { label: elt.sloc.base, fcts: [], vars: [elt] }; + }); + + return newFiles; + }, [fcts, showFunction, variables, showVariable]); + + function getList([path, infos]: [string, InfosFile]): JSX.Element | null { + const { label, fcts, vars } = infos; + const fctsComp: JSX.Element[] = showFcts ? + fcts.map(elt => <FctItem + key={elt.key} + icon="FUNCTION" + fct={elt} + current={current} />) + : []; + const varsComp: JSX.Element[] = showVars ? + vars.map((v) => makeVarItem(scope, v, "VARIABLE")) + : []; + const items = fctsComp.concat(varsComp); + if(items.length === 0) return null; + + return ( + <Section + key={path} + label={label} + title={path} + settings={`frama-c.sidebar.files.${path}`} + className='globals-section' + > + <InfiniteScrollList + scrollableParent={scrollableParent} + >{items}</InfiniteScrollList> + </Section> + ); + } + + return <> + <Hbox className='dome-xSideBar-title' > + <Hbox style={{ flexWrap: 'wrap', alignContent: 'center' }}> + <Title label='Files' /> + </Hbox> + <Hbox> + <Toolbar.ButtonGroup> + <Toolbar.Button + icon="FUNCTION" + title={'Show functions'} + selected={showFcts} + onClick={() => flipShowFcts()} + /> + <Toolbar.Button + icon='TUNINGS' + onClick={() => Dome.popupMenu(contextFctMenuItem)} + /> + </Toolbar.ButtonGroup> + <Toolbar.ButtonGroup> + <Toolbar.Button + icon="VARIABLE" + title={'Show variables'} + selected={showVars} + onClick={() => flipShowVars()} + /> + <Toolbar.Button + icon='TUNINGS' + onClick={() => Dome.popupMenu(contextVarMenuItem)} + /> + </Toolbar.ButtonGroup> + </Hbox> + </Hbox> + + { Object.entries(files).sort((v1, v2) => alpha(v1[1].label, v2[1].label)) + .map(elt => getList(elt)) + } + </>; +} + // -------------------------------------------------------------------------- // --- All globals // -------------------------------------------------------------------------- +export function GlobalsFiles(): JSX.Element { + const scrollableArea = React.useRef<HTMLDivElement>(null); + return ( + <div ref={scrollableArea} className="globals-scrollable-area"> + <Files scrollableParent={scrollableArea} /> + </div> + ); +} + export default function Globals(): JSX.Element { const scrollableArea = React.useRef<HTMLDivElement>(null); return ( diff --git a/ivette/src/frama-c/plugins/eva/EvaSidebar.tsx b/ivette/src/frama-c/plugins/eva/EvaSidebar.tsx index c5de1176197ede9e2c796cd2f12c39a52cbfc583..feb754639cfaea12e9855b769e1c140c8f4ea632 100644 --- a/ivette/src/frama-c/plugins/eva/EvaSidebar.tsx +++ b/ivette/src/frama-c/plugins/eva/EvaSidebar.tsx @@ -254,6 +254,6 @@ Ivette.registerSidebar({ id: 'frama-c.plugins.eva_sidebar', label: 'EVA', icon: 'APPLE', - title: 'Eva Sidebar', + title: 'Eva', children: <EvaSideBar />, }); diff --git a/ivette/src/frama-c/plugins/eva/components/Tools.tsx b/ivette/src/frama-c/plugins/eva/components/Tools.tsx index 0f627d5b2285740e6defc93c12a0901e95ad3a14..c13fe32c852a0c74ade024b8223f1c2196a6bd2f 100644 --- a/ivette/src/frama-c/plugins/eva/components/Tools.tsx +++ b/ivette/src/frama-c/plugins/eva/components/Tools.tsx @@ -60,7 +60,7 @@ export default function EvaTools( const syncToFC = (): void => { remote.commit(); }; return ( - <Hbox className='eva-tools'> + <Hbox className='dome-xSideBar-title eva-tools'> <Hbox className='eva-tools-actions'> <IconButton icon="MEDIA.PLAY" diff --git a/ivette/src/frama-c/plugins/eva/style.css b/ivette/src/frama-c/plugins/eva/style.css index 5345a38cf3e7f84c6d7afb4d99cc146a5a7566a4..2821d3ff355a85cdf99291880aca3a17b77a4432 100644 --- a/ivette/src/frama-c/plugins/eva/style.css +++ b/ivette/src/frama-c/plugins/eva/style.css @@ -358,16 +358,6 @@ tr:first-of-type > .eva-table-callsite-box { /* ------------------ */ /* --- Tools Eva --- */ .eva-tools { - position: sticky; - top: 0px; - z-index: 10; - background-color: var(--background-profound); - padding: 5px; - border-bottom: solid 1px var(--border-discrete); - display: flex; - flex-wrap: wrap; - justify-content: space-between; - align-items: center; .eva-tools-actions { display: flex;