"""
Code Display Component
Renders source code with syntax highlighting and line number support.
Usage:
In instance_display config:
fields:
- key: source_code
type: code
display_options:
language: python
show_line_numbers: true
max_height: 500
"""
from typing import Dict, Any, List, Optional
import html
import logging
from .base import BaseDisplay
logger = logging.getLogger(__name__)
class CodeDisplay(BaseDisplay):
"""
Display type for source code with syntax highlighting.
Uses Pygments for server-side highlighting or can use client-side
highlighting with highlight.js.
"""
name = "code"
required_fields = ["key"]
optional_fields = {
"language": None, # Language for syntax highlighting
"show_line_numbers": True, # Show line numbers
"max_height": 500, # Max container height
"max_width": None, # Max container width
"wrap_lines": False, # Wrap long lines
"highlight_lines": None, # List of line numbers to highlight
"start_line": 1, # Starting line number
"theme": "default", # Color theme
"copy_button": True, # Show copy to clipboard button
}
description = "Source code display with syntax highlighting"
supports_span_target = True
def render(self, field_config: Dict[str, Any], data: Any) -> str:
"""
Render source code content.
Args:
field_config: Display configuration
data: Either a dict with extracted content or raw code string
Returns:
HTML string for rendering
"""
options = self.get_display_options(field_config)
field_key = field_config.get("key", "code")
# Handle different data formats
if isinstance(data, dict):
# Pre-extracted FormatOutput data
if "rendered_html" in data:
return self._wrap_content(data["rendered_html"], options, field_key)
code = data.get("text", "")
language = data.get("metadata", {}).get("language", options.get("language"))
elif isinstance(data, str):
code = data
language = options.get("language")
else:
return f'
{self._render_copy_button() if options.get("copy_button") else ""}
{content}
'''
def _render_copy_button(self) -> str:
"""
Render copy to clipboard button.
"""
return '''
'''
def _render_code(
self,
code: str,
language: Optional[str],
options: Dict[str, Any],
field_key: str = "code",
is_span_target: bool = False
) -> str:
"""
Render code with line numbers and optional highlighting.
When is_span_target is True, uses a simpler pre/code structure
that works better with span annotation positioning.
"""
# For span targets, use simpler rendering for accurate position calculation
if is_span_target:
return self._render_code_simple(code, language, options, field_key)
lines = code.split("\n")
parts = []
show_line_numbers = options.get("show_line_numbers", True)
start_line = options.get("start_line", 1)
highlight_lines = set(options.get("highlight_lines") or [])
wrap_lines = options.get("wrap_lines", False)
wrap_class = "code-wrap" if wrap_lines else "code-nowrap"
lang_class = f"language-{language}" if language else ""
content_classes = ["code-content", wrap_class]
content_class_str = " ".join(content_classes)
parts.append(f'
')
parts.append(f'
')
for i, line in enumerate(lines):
line_num = i + start_line
line_classes = ["code-line"]
if line_num in highlight_lines:
line_classes.append("highlighted-line")
escaped_line = html.escape(line) if line else " "
parts.append(f'
')
if show_line_numbers:
parts.append(
f'
{line_num}
'
)
parts.append(
f'
'
f'{escaped_line}'
f'
'
)
parts.append('
')
parts.append('
')
parts.append('
')
return "\n".join(parts)
def _render_code_simple(
self,
code: str,
language: Optional[str],
options: Dict[str, Any],
field_key: str
) -> str:
"""
Render code in a simple pre/code format for span annotation.
This avoids table structures that interfere with text position calculations.
Uses a flat text structure without data-original-text so the span system
uses DOM textContent directly (which matches what the user sees and selects).
"""
lang_class = f"language-{language}" if language else ""
# Escape code for HTML but preserve structure
escaped_code = html.escape(code)
# Don't use data-original-text - let span system use DOM textContent
# This ensures canonical text matches what user sees and selects
return f'''
{escaped_code}
'''
def get_css_classes(self, field_config: Dict[str, Any]) -> List[str]:
"""Get CSS classes for the display container."""
classes = super().get_css_classes(field_config)
options = self.get_display_options(field_config)
if field_config.get("span_target"):
classes.append("span-target-code")
language = options.get("language")
if language:
classes.append(f"language-{language}")
theme = options.get("theme", "default")
classes.append(f"code-theme-{theme}")
return classes
def get_data_attributes(
self,
field_config: Dict[str, Any],
data: Any
) -> Dict[str, str]:
"""Get data attributes for JavaScript initialization."""
attrs = super().get_data_attributes(field_config, data)
options = self.get_display_options(field_config)
language = options.get("language")
if language:
attrs["language"] = language
if isinstance(data, dict) and "metadata" in data:
meta = data["metadata"]
if "language" in meta:
attrs["language"] = meta["language"]
if "line_count" in meta:
attrs["line-count"] = str(meta["line_count"])
return attrs
def get_js_init(self) -> Optional[str]:
"""
Return JavaScript initialization code for code displays.
"""
return '''
if (typeof initCodeDisplays === 'function') {
initCodeDisplays();
}
'''