Add syntax highlighting to editor buffer display

This commit is contained in:
Jared Miller 2026-02-07 23:01:55 -05:00
parent a799b6716c
commit 0574457404
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
3 changed files with 110 additions and 2 deletions

View file

@ -19,6 +19,7 @@ async def cmd_edit(player: Player, args: str) -> None:
player.editor = Editor( player.editor = Editor(
save_callback=save_callback, save_callback=save_callback,
content_type="text", content_type="text",
color_depth=player.color_depth,
) )
player.mode_stack.append("editor") player.mode_stack.append("editor")
await player.send("Entering editor. Type :h for help.\r\n") await player.send("Entering editor. Type :h for help.\r\n")

View file

@ -3,6 +3,8 @@
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field from dataclasses import dataclass, field
from mudlib.render.highlight import highlight
@dataclass @dataclass
class EditorResponse: class EditorResponse:
@ -26,6 +28,7 @@ class Editor:
undo_stack: list[list[str]] = field(default_factory=list) undo_stack: list[list[str]] = field(default_factory=list)
save_callback: Callable[[str], Awaitable[None]] | None = None save_callback: Callable[[str], Awaitable[None]] | None = None
content_type: str = "text" # hint for syntax highlighting later content_type: str = "text" # hint for syntax highlighting later
color_depth: str = "16" # "16", "256", or "truecolor"
dirty: bool = False dirty: bool = False
def __init__( def __init__(
@ -33,10 +36,12 @@ class Editor:
initial_content: str = "", initial_content: str = "",
save_callback: Callable[[str], Awaitable[None]] | None = None, save_callback: Callable[[str], Awaitable[None]] | None = None,
content_type: str = "text", content_type: str = "text",
color_depth: str = "16",
): ):
"""Initialize editor with optional content.""" """Initialize editor with optional content."""
self.save_callback = save_callback self.save_callback = save_callback
self.content_type = content_type self.content_type = content_type
self.color_depth = color_depth
self.cursor = 0 self.cursor = 0
self.undo_stack = [] self.undo_stack = []
self.dirty = False self.dirty = False
@ -62,7 +67,20 @@ class Editor:
if not self.buffer: if not self.buffer:
return "(empty buffer)" return "(empty buffer)"
# Calculate padding for line numbers # Use syntax highlighting if content_type is not "text"
if self.content_type != "text":
content = "\n".join(self.buffer)
highlighted = highlight(
content,
language=self.content_type,
color_depth=self.color_depth,
line_numbers=True,
)
# If highlight() returned the original (unknown language), fall through
if "\033[" in highlighted:
return highlighted
# Fall back to plain line-number formatting
max_line_num = len(self.buffer) max_line_num = len(self.buffer)
padding = len(str(max_line_num)) padding = len(str(max_line_num))
@ -81,7 +99,22 @@ class Editor:
start = max(0, line_num - 1 - context) start = max(0, line_num - 1 - context)
end = min(len(self.buffer), line_num + context) end = min(len(self.buffer), line_num + context)
# Calculate padding for line numbers # Use syntax highlighting if content_type is not "text"
if self.content_type != "text":
# Get the context window
context_lines = self.buffer[start:end]
content = "\n".join(context_lines)
highlighted = highlight(
content,
language=self.content_type,
color_depth=self.color_depth,
line_numbers=True,
)
# If highlight() returned highlighted content, use it
if "\033[" in highlighted:
return highlighted
# Fall back to plain line-number formatting
max_line_num = end max_line_num = end
padding = len(str(max_line_num)) padding = len(str(max_line_num))

View file

@ -344,3 +344,77 @@ async def test_line_numbers_padded():
# Find lines with numbers # Find lines with numbers
assert any(" 1 line 1" in line for line in lines) assert any(" 1 line 1" in line for line in lines)
assert any("11 line 11" in line for line in lines) assert any("11 line 11" in line for line in lines)
# Syntax highlighting tests
@pytest.mark.asyncio
async def test_view_buffer_with_python_includes_ansi():
"""Viewing buffer with content_type=python includes ANSI codes."""
editor = Editor(
initial_content="def foo():\n return 42",
content_type="python",
color_depth="16",
)
response = await editor.handle_input(":")
# Should contain ANSI escape codes from syntax highlighting
assert "\033[" in response.output
# Should contain the code
assert "def" in response.output
assert "foo" in response.output
@pytest.mark.asyncio
async def test_view_buffer_with_text_no_ansi():
"""Viewing buffer with content_type=text does NOT include ANSI codes."""
editor = Editor(
initial_content="plain text line 1\nplain text line 2", content_type="text"
)
response = await editor.handle_input(":")
# Should NOT contain ANSI escape codes (plain line numbers)
assert "\033[" not in response.output
# Should contain plain line numbers
assert "1 plain text line 1" in response.output
assert "2 plain text line 2" in response.output
@pytest.mark.asyncio
async def test_view_specific_line_with_python_includes_ansi():
"""Viewing a specific line with python content includes ANSI codes."""
content = "\n".join([f"def func{i}():\n return {i}" for i in range(10)])
editor = Editor(
initial_content=content,
content_type="python",
color_depth="16",
)
response = await editor.handle_input(":5")
# Should contain ANSI escape codes from syntax highlighting
assert "\033[" in response.output
# Should contain code from the context window
assert "def" in response.output
@pytest.mark.asyncio
async def test_color_depth_passed_to_highlight():
"""Color depth is passed through to highlight function."""
editor = Editor(
initial_content="x = 1\ny = 2",
content_type="python",
color_depth="256",
)
response = await editor.handle_input(":")
# Should contain ANSI codes (256-color formatter)
assert "\033[" in response.output
# Should contain the code
assert "x" in response.output
assert "y" in response.output
@pytest.mark.asyncio
async def test_empty_buffer_with_python_content_type():
"""Empty buffer with content_type=python still works."""
editor = Editor(content_type="python", color_depth="16")
response = await editor.handle_input(":")
# Should show empty buffer message
assert "empty" in response.output.lower()