Widget buildCodeArea()

in packages/devtools_app/lib/src/debugger/codeview.dart [232:400]


  Widget buildCodeArea(BuildContext context) {
    final theme = Theme.of(context);

    final lines = <TextSpan>[];

    // Ensure the syntax highlighter has been initialized.
    // TODO(bkonyi): process source for highlighting on a separate thread.
    if (parsedScript.script.source != null) {
      if (parsedScript.script.source.length < 500000 &&
          parsedScript.highlighter != null) {
        final highlighted = parsedScript.highlighter.highlight(context);

        // Look for [TextSpan]s which only contain '\n' to manually break the
        // output from the syntax highlighter into individual lines.
        var currentLine = <TextSpan>[];
        highlighted.visitChildren((span) {
          currentLine.add(span);
          if (span.toPlainText() == '\n') {
            lines.add(
              TextSpan(
                style: theme.fixedFontStyle,
                children: currentLine,
              ),
            );
            currentLine = <TextSpan>[];
          }
          return true;
        });
        lines.add(
          TextSpan(
            style: theme.fixedFontStyle,
            children: currentLine,
          ),
        );
      } else {
        lines.addAll(
          [
            for (final line in parsedScript.script.source.split('\n'))
              TextSpan(
                style: theme.fixedFontStyle,
                text: line,
              ),
          ],
        );
      }
    }

    // Apply the log change-of-base formula, then add 16dp padding for every
    // digit in the maximum number of lines.
    final gutterWidth = CodeView.assumedCharacterWidth * 1.5 +
        CodeView.assumedCharacterWidth *
            (defaultEpsilon + math.log(math.max(lines.length, 100)) / math.ln10)
                .truncateToDouble();

    _updateScrollPosition(animate: false);

    return HistoryViewport(
      history: widget.controller.scriptsHistory,
      generateTitle: (script) => script.uri,
      onTitleTap: () => widget.controller.toggleFileOpenerVisibility(true),
      controls: [
        ScriptPopupMenu(widget.controller),
        ScriptHistoryPopupMenu(
          itemBuilder: _buildScriptMenuFromHistory,
          onSelected: (scriptRef) {
            widget.controller.showScriptLocation(ScriptLocation(scriptRef));
          },
          enabled: widget.controller.scriptsHistory.hasScripts,
        ),
      ],
      contentBuilder: (context, script) {
        if (lines.isNotEmpty) {
          return DefaultTextStyle(
            style: theme.fixedFontStyle,
            child: Expanded(
              child: Scrollbar(
                key: CodeView.debuggerCodeViewVerticalScrollbarKey,
                controller: textController,
                isAlwaysShown: true,
                // Only listen for vertical scroll notifications (ignore those
                // from the nested horizontal SingleChildScrollView):
                notificationPredicate: (ScrollNotification notification) =>
                    notification.depth == 1,
                child: ValueListenableBuilder<StackFrameAndSourcePosition>(
                  valueListenable: widget.controller.selectedStackFrame,
                  builder: (context, frame, _) {
                    final pausedFrame = frame == null
                        ? null
                        : (frame.scriptRef == scriptRef ? frame : null);

                    return Row(
                      children: [
                        ValueListenableBuilder<
                            List<BreakpointAndSourcePosition>>(
                          valueListenable:
                              widget.controller.breakpointsWithLocation,
                          builder: (context, breakpoints, _) {
                            return Gutter(
                              gutterWidth: gutterWidth,
                              scrollController: gutterController,
                              lineCount: lines.length,
                              pausedFrame: pausedFrame,
                              breakpoints: breakpoints
                                  .where((bp) => bp.scriptRef == scriptRef)
                                  .toList(),
                              executableLines: parsedScript.executableLines,
                              onPressed: _onPressed,
                              // Disable dots for possible breakpoint locations.
                              allowInteraction:
                                  !widget.controller.isSystemIsolate,
                            );
                          },
                        ),
                        const SizedBox(width: denseSpacing),
                        Expanded(
                          child: LayoutBuilder(
                            builder: (context, constraints) {
                              final double fileWidth = calculateTextSpanWidth(
                                findLongestTextSpan(lines),
                              );

                              return Scrollbar(
                                key: CodeView
                                    .debuggerCodeViewHorizontalScrollbarKey,
                                isAlwaysShown: true,
                                controller: horizontalController,
                                child: SingleChildScrollView(
                                  scrollDirection: Axis.horizontal,
                                  controller: horizontalController,
                                  child: SizedBox(
                                    height: constraints.maxHeight,
                                    width: fileWidth,
                                    child: Lines(
                                      height: constraints.maxHeight,
                                      debugController: widget.controller,
                                      scrollController: textController,
                                      lines: lines,
                                      pausedFrame: pausedFrame,
                                      searchMatchesNotifier:
                                          widget.controller.searchMatches,
                                      activeSearchMatchNotifier:
                                          widget.controller.activeSearchMatch,
                                    ),
                                  ),
                                ),
                              );
                            },
                          ),
                        ),
                      ],
                    );
                  },
                ),
              ),
            ),
          );
        } else {
          return Expanded(
            child: Center(
              child: Text(
                'No source available',
                style: theme.textTheme.subtitle1,
              ),
            ),
          );
        }
      },
    );
  }