protected function renderContext()

in src/lint/renderer/ArcanistConsoleLintRenderer.php [86:275]


  protected function renderContext(
    ArcanistLintMessage $message,
    $data,
    array $line_map) {

    $context = 3;

    $message = $message->newTrimmedMessage();

    $original = $message->getOriginalText();
    $replacement = $message->getReplacementText();

    $line = $message->getLine();
    $char = $message->getChar();

    $old = $data;
    $old_lines = phutil_split_lines($old);
    $old_impact = substr_count($original, "\n") + 1;
    $start = $line;

    if ($message->isPatchable()) {
      $patch_offset = $line_map[$line] + ($char - 1);

      $new = substr_replace(
        $old,
        $replacement,
        $patch_offset,
        strlen($original));
      $new_lines = phutil_split_lines($new);

      // Figure out how many "-" and "+" lines we have by counting the newlines
      // for the relevant patches. This may overestimate things if we are adding
      // or removing entire lines, but we'll adjust things below.
      $new_impact = substr_count($replacement, "\n") + 1;

      // If this is a change on a single line, we'll try to highlight the
      // changed character range to make it easier to pick out.
      if ($old_impact === 1 && $new_impact === 1) {
        $old_lines[$start - 1] = substr_replace(
          $old_lines[$start - 1],
          $this->highlightText($original),
          $char - 1,
          strlen($original));

        $new_lines[$start - 1] = substr_replace(
          $new_lines[$start - 1],
          $this->highlightText($replacement),
          $char - 1,
          strlen($replacement));
      }

      // If lines at the beginning of the changed line range are actually the
      // same, shrink the range. This happens when a patch just adds a line.
      do {
        $old_line = idx($old_lines, $start - 1, null);
        $new_line = idx($new_lines, $start - 1, null);

        if ($old_line !== $new_line) {
          break;
        }

        $start++;
        $old_impact--;
        $new_impact--;

        // We can end up here if a patch removes a line which occurs before
        // another identical line.
        if ($old_impact <= 0 || $new_impact <= 0) {
          break;
        }
      } while (true);

      // If the lines at the end of the changed line range are actually the
      // same, shrink the range. This happens when a patch just removes a
      // line.
      if ($old_impact > 0 && $new_impact > 0) {
        do {
          $old_suffix = idx($old_lines, $start + $old_impact - 2, null);
          $new_suffix = idx($new_lines, $start + $new_impact - 2, null);

          if ($old_suffix !== $new_suffix) {
            break;
          }

          $old_impact--;
          $new_impact--;

          // We can end up here if a patch removes a line which occurs after
          // another identical line.
          if ($old_impact <= 0 || $new_impact <= 0) {
            break;
          }
        } while (true);
      }

    } else {

      // If we have "original" text and it is contained on a single line,
      // highlight the affected area. If we don't have any text, we'll mark
      // the character with a caret (below, in rendering) instead.
      if ($old_impact == 1 && strlen($original)) {
        $old_lines[$start - 1] = substr_replace(
          $old_lines[$start - 1],
          $this->highlightText($original),
          $char - 1,
          strlen($original));
      }

      $old_impact = 0;
      $new_impact = 0;
    }

    $out = array();

    $head = max(1, $start - $context);
    for ($ii = $head; $ii < $start; $ii++) {
      $out[] = array(
        'text' => $old_lines[$ii - 1],
        'number' => $ii,
      );
    }

    for ($ii = $start; $ii < $start + $old_impact; $ii++) {
      $out[] = array(
        'text' => $old_lines[$ii - 1],
        'number' => $ii,
        'type' => '-',
        'chevron' => ($ii == $start),
      );
    }

    for ($ii = $start; $ii < $start + $new_impact; $ii++) {
      // If the patch was at the end of the file and ends with a newline, we
      // won't have an actual entry in the array for the last line, even though
      // we want to show it in the diff.
      $out[] = array(
        'text' => idx($new_lines, $ii - 1, ''),
        'type' => '+',
        'chevron' => ($ii == $start),
      );
    }

    $cursor = $start + $old_impact;
    $foot = min(count($old_lines), $cursor + $context);
    for ($ii = $cursor; $ii <= $foot; $ii++) {
      $out[] = array(
        'text' => $old_lines[$ii - 1],
        'number' => $ii,
        'chevron' => ($ii == $cursor),
      );
    }

    $result = array();

    $seen_chevron = false;
    foreach ($out as $spec) {
      if ($seen_chevron) {
        $chevron = false;
      } else {
        $chevron = !empty($spec['chevron']);
        if ($chevron) {
          $seen_chevron = true;
        }
      }

      // If the line doesn't actually end in a newline, add one so the layout
      // doesn't mess up. This can happen when the last line of the old file
      // didn't have a newline at the end.
      $text = $spec['text'];
      if (!preg_match('/\n\z/', $spec['text'])) {
        $text .= "\n";
      }

      $result[] = $this->renderLine(
        idx($spec, 'number'),
        $text,
        $chevron,
        idx($spec, 'type'));

      // If this is just a message and does not have a patch, put a little
      // caret underneath the line to point out where the issue is.
      if ($chevron) {
        if (!$message->isPatchable() && !strlen($original)) {
          $result[] = $this->renderCaret($char)."\n";
        }
      }
    }

    return implode('', $result);
  }