code = $line;
}
/**
* Append a nested block.
*
* @param WpHtmlApiBlock $block Nested block.
*
* @return void
* @throws \Error When the block cannot be converted into a code block.
*/
public function append( WpHtmlApiBlock $block ): void {
if ( $block instanceof WpHtmlApiBlockParagraph ) {
$this->code = $block->lines[0] ?? null;
return;
}
throw new \Error( 'Cannot add this block to a code block.' );
}
/**
* Infer the code language from the current HTML processor token.
*
* @param \WP_HTML_Processor $processor HTML processor.
*
* @return void
*/
public function infer_language( \WP_HTML_Processor $processor ): void {
if ( isset( $this->language ) ) {
return;
}
$language_map = [
'c++' => 'cpp',
'eix' => 'elixir',
'erl' => 'erlang',
'golang' => 'go',
'md' => 'markdown',
'js' => 'javascript',
'patch' => 'diff',
'py' => 'python',
'python2' => 'python',
'python3' => 'python',
'rs' => 'rust',
'ts' => 'typescript',
];
$known_languages = [ 'asm', 'apl', 'bash', 'c', 'cpp', 'clojure', 'clojurescript', 'commonlisp', 'diff', 'elixir', 'erlang', 'go', 'html', 'javascript', 'lisp', 'php', 'python', 'rust', 'scheme', 'typescript' ];
$known_rejects = [ 'nil', 'src' ];
$try_slug = function ( $trial ) use ( $language_map, $known_languages, $known_rejects ) {
if ( in_array( $trial, $known_rejects, true ) || in_array( $this->language, $known_languages, true ) ) {
return;
}
$trial = $language_map[ $trial ] ?? $trial;
if ( in_array( $trial, $known_languages, true ) ) {
$this->language = $trial;
return;
}
$this->language_fallback = $trial;
};
switch ( $processor->get_token_name() ) {
case 'PRE':
case 'CODE':
$class_list = $processor->class_list();
if ( ! isset( $class_list ) ) {
break;
}
foreach ( $class_list as $class_name ) {
if ( str_starts_with( $class_name, 'language-' ) ) {
$try_slug( substr( $class_name, 9 ) );
}
if ( str_starts_with( $class_name, 'pre-' ) || str_starts_with( $class_name, 'src-' ) ) {
$try_slug( substr( $class_name, 4 ) );
return;
}
$try_slug( $class_name );
}
break;
}
}
/**
* Flush the code block into markdown.
*
* @param WpHtmlApiRendererOptions $options Renderer options.
*
* @return string
*/
public function flush( WpHtmlApiRendererOptions $options ): string {
if ( ! isset( $this->code ) || $this->code->is_empty() ) {
return '';
}
$previous_indent = $options->indent;
$options->indent[] = ' ';
$slug = $this->language ?? $this->language_fallback ?? '';
$indent = implode( '', $options->indent );
$indent_length = mb_strwidth( $indent );
$soft_limit = $options->soft_line_wrap;
$options->soft_line_wrap = max( 1, $soft_limit - $indent_length );
$prefix = "{$indent}```{$slug}\n";
$buffer = '';
foreach ( explode( "\n", $this->code->raw_buffer() ) as $line ) {
$chunk = "{$indent}{$line}\n";
$is_all_whitespace = strspn( $chunk, " \t\f\r\n" ) === strlen( $chunk );
$buffer .= $is_all_whitespace ? "\n" : $chunk;
}
$buffer = trim( $buffer, "\n" );
$suffix = "\n{$indent}```\n";
$options->soft_line_wrap = $soft_limit;
$options->indent = $previous_indent;
return "{$prefix}{$buffer}{$suffix}";
}
/**
* Determine whether the code block is empty.
*
* @return bool
*/
public function is_empty(): bool {
return null === $this->code || $this->code->is_empty();
}
}