function Source()

in sapp/ui/frontend/src/Source.js [167:295]


function Source(
  props: $ReadOnly<{|path: string, location: string, titos?: string|}>,
): React$Node {
  var line = null;

  const {loading, error, data} = useQuery(SourceQuery, {
    variables: {path: props.path},
  });
  const file = data?.file?.edges[0]?.node;

  var content = <div />;
  if (error) {
    content = (
      <Tooltip title={error.toString()}>
        <Alert message={`No file found for ${props.path}`} type="info" />
      </Tooltip>
    );
  } else if (loading) {
    content = (
      <div style={{height: '12em', textAlign: 'center', paddingTop: '5em'}}>
        <Text type="secondary">
          <LoadingOutlined />
          <br />
          Loading {props.path}...
        </Text>
      </div>
    );
  } else {
    const source = file.contents;
    const lines = source.split('\n');
    // Potential mismatch between line numbers in bytecode and source in MT issues
    // can break the frontend, display the error message in that scenario
    if (lines.length < props.location.split('|')[0]) {
      content = (
        <Alert message={`${props.path} cannot be displayed because of mismatch in line numbers in the source code and the issue. This could be caused by inaccuracies in decompiled bytecode (if viewing Mariana Trench results) or by viewing a file which has been edited after it was processed by your static analyzer.`} type="info" />
      );
    } else {
      const range = parseRanges(props.location, lines)[0];
      line = range.from.line;
      const titos = parseRanges(props.titos, lines);
      const fileExtension = props.path.split('.').pop();
      const mode = modes[fileExtension] || modes["py"];

      const ranges = [...titos, range].sort(
        (left, right) => left.from.line - right.from.line,
      );

      const layout = computeLayout(ranges, lines);

      // React codemirror is horribly broken so store a reference to underlying
      // JS implementation.
      var editor = null;

      content = (
        <CodeMirror
          value={source}
          options={{lineNumbers: true, readOnly: 'true', mode}}
          editorDidMount={nativeEditor => {
            editor = nativeEditor;

            editor.markText(range.from, range.to, {
              className: 'Source-selection',
              attributes: {
                title: Documentation.source.toNextFrame,
              },
            });

            titos.forEach(range => {
              nativeEditor.markText(range.from, range.to, {
                className: 'Source-tito',
                attributes: {
                  title: Documentation.source.tito,
                },
              });
            });

            layout.folds.forEach(fold => {
              nativeEditor.foldCode(fold.line, {
                rangeFinder: _ => fold.range,
                widget: `Hiding ${fold.range.to.line -
                  fold.line} lines. Click to expand...`,
              });
            });
            const textHeight = editor.defaultTextHeight();
            editor.setSize(null, layout.totalLines * textHeight);
            const offset = editor.heightAtLine(
              ranges[ranges.length - 1].from.line - layout.totalLines + 2,
              'local',
            );
            editor.scrollTo(
              0,
              offset - (linesPerFold + 2) * layout.folds.length * textHeight,
            );
          }}
        />
      );
    }
  }

  return (
    <>
      <div class="source-menu">
        <Tooltip title="Open in Editor" placement="bottom">
          <Button
            size="small"
            icon={<EditOutlined />}
            type="text"
            onClick={() => {
              window.location = file.editor_link;
            }}
            disabled={loading || error || !Boolean(file.editor_link)}
          />
        </Tooltip>
        <Tooltip title="Reset Scroll" placement="bottom">
          <Button
            size="small"
            icon={<SelectOutlined />}
            type="text"
            onClick={() =>
              editor && editor.scrollIntoView({line: line || 0, ch: 0})
            }
            disabled={loading || error}
          />
        </Tooltip>
      </div>
      <div class="source">{content}</div>
    </>
  );
}