Skip to content
Snippets Groups Projects
ASTview.tsx 7.16 KiB
Newer Older
// --------------------------------------------------------------------------
// --- AST Source Code
// --------------------------------------------------------------------------

import React from 'react';
import * as Server from 'frama-c/server';
import * as States from 'frama-c/states';
import { key } from 'dome/data/json';
import { RichTextBuffer } from 'dome/text/buffers';
import { Text } from 'dome/text/editors';
import { IconButton } from 'dome/controls/buttons';
import { Component, TitleBar } from 'frama-c/LabViews';
import { printFunction, markerInfo } from 'api/kernel/ast';
import { getCallers } from 'api/plugins/eva/general';
import 'codemirror/mode/clike/clike';
import 'codemirror/theme/ambiance.css';
import 'codemirror/theme/solarized.css';
const THEMES = [
  { id: 'default', label: 'Default' },
  { id: 'ambiance', label: 'Ambiance' },
  { id: 'solarized light', label: 'Solarized Light' },
  { id: 'solarized dark', label: 'Solarized Dark' },
// --------------------------------------------------------------------------
// --- Pretty Printing (Browser Console)
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// --- Rich Text Printer
// --------------------------------------------------------------------------

async function loadAST(
  buffer: RichTextBuffer, theFunction?: string, theMarker?: string,
) {
  buffer.clear();
  if (theFunction) {
    buffer.log('// Loading', theFunction, '');
    (async () => {
        const data = await Server.send(printFunction, theFunction);
        buffer.operation(() => {
          buffer.clear();
            buffer.log('// No code for function', theFunction);
          if (theMarker)
            buffer.scroll(theMarker, undefined);
        });
          `Fail to retrieve the AST of function '${theFunction}' ` +
          `and marker '${theMarker}':`, err,
/** Compute the [[functionName]] caller locations. */
async function functionCallers(functionName: string) {
    const data = await Server.send(getCallers, functionName);
    const locations = data.map(([fct, marker]) => ({ function: fct, marker }));
    PP.error(`Fail to retrieve callers of function '${functionName}':`, err);
// --------------------------------------------------------------------------
// --- AST Printer
// --------------------------------------------------------------------------

const ASTview = () => {
  // Hooks
  const buffer = React.useMemo(() => new RichTextBuffer(), []);
  const printed = React.useRef<string | undefined>();
  const [selection, updateSelection] = States.useSelection();
  const multipleSelections = selection?.multiple.allSelections;
  const [theme, setTheme] = Dome.useGlobalSetting('ASTview.theme', 'default');
  const [fontSize, setFontSize] = Dome.useGlobalSetting('ASTview.fontSize', 12);
  const [wrapText, setWrapText] = Dome.useSwitch('ASTview.wrapText', false);
  const markersInfo = States.useSyncArray(markerInfo).getArray();
  const theFunction = selection?.current?.function;
  const theMarker = selection?.current?.marker;
Loïc Correnson's avatar
Loïc Correnson committed

  // Hook: async loading
    if (printed.current !== theFunction) {
      printed.current = theFunction;
      loadAST(buffer, theFunction, theMarker);
  React.useEffect(() => {
    const decorator = (marker: string) => {
      if (multipleSelections?.some((location) => location?.marker === marker))
        return 'highlighted-marker';
      return undefined;
    };
    buffer.setDecorator(decorator);
  }, [buffer, multipleSelections]);
  // Hook: marker scrolling
    if (theMarker) buffer.scroll(theMarker, undefined);
  }, [buffer, theMarker]);
Loïc Correnson's avatar
Loïc Correnson committed
  // Callbacks
  const zoomIn = () => fontSize < 48 && setFontSize(fontSize + 2);
  const zoomOut = () => fontSize > 4 && setFontSize(fontSize - 2);
  function onTextSelection(id: key<'#markerInfo'>) {
    if (selection.current) {
      const location = { ...selection.current, marker: id };
      updateSelection({ location });
  async function onContextMenu(id: key<'#markerInfo'>) {
    const selectedMarkerInfo = markersInfo.find((e) => e.key === id);
    if (selectedMarkerInfo?.var === 'function') {
      if (selectedMarkerInfo.kind === 'declaration') {
        if (selectedMarkerInfo?.name) {
          const locations = await functionCallers(selectedMarkerInfo.name);
          const locationsByFunction = _.groupBy(locations, (e) => e.function);
          _.forEach(locationsByFunction,
            (e) => {
              const callerName = e[0].function;
              items.push({
                label:
                  `Go to caller ${callerName} ` +
                  `${e.length > 1 ? `(${e.length} call sites)` : ''}`,
                onClick: () => updateSelection({
                  locations,
                  index: locations.findIndex((l) => l.function === callerName),
                }),
              });
            });
        }
      } else {
        items.push({
          label: `Go to definition of ${selectedMarkerInfo.name}`,
          onClick: () => {
            const location = { function: selectedMarkerInfo.name };
            updateSelection({ location });
          },
        });
  // Theme Popup
  const selectTheme = (id?: string) => id && setTheme(id);
  const checkTheme =
    (th: { id: string }) => ({ checked: th.id === theme, ...th });
  const themePopup =
    () => Dome.popupMenu(THEMES.map(checkTheme), selectTheme);
Loïc Correnson's avatar
Loïc Correnson committed
  // Component
  return (
      <TitleBar>
        <IconButton
          icon="ZOOM.OUT"
          onClick={zoomOut}
          disabled={!theFunction}
          title="Decrease font size"
        />
        <IconButton
          icon="ZOOM.IN"
          onClick={zoomIn}
          disabled={!theFunction}
          title="Increase font size"
        />
        <IconButton
          icon="PAINTBRUSH"
          onClick={themePopup}
          title="Choose theme"
        />
        <IconButton
          icon="WRAPTEXT"
          selected={wrapText}
          onClick={setWrapText}
          title="Wrap text"
      </TitleBar>
      <Text
        buffer={buffer}
        mode="text/x-csrc"
        theme={theme}
        fontSize={fontSize}
        lineWrapping={wrapText}
        selection={theMarker}
        onSelection={onTextSelection}
        onContextMenu={onContextMenu}
};

// --------------------------------------------------------------------------
// --- Export Component
// --------------------------------------------------------------------------

export default () => (
  <Component
    id="frama-c.astview"
    label="AST"
Loïc Correnson's avatar
Loïc Correnson committed
    title="Normalized source code representation"
  </Component>
);

// --------------------------------------------------------------------------