Usage

Code Snippet represents source text and presentation hints with four immutable values. Transformations return a new value, so the original snippet remains available to another consumer.

Value Role
CodeSnippet Complete code, provenance, selections, and annotations
CodeLine One line with snippet and original source coordinates
CodeAnnotation Typed metadata attached to a byte range
CodeSegment Contiguous line text with one stable annotation set

All four values implement JsonSerializable.

Snippets

Create a CodeSnippet from code. Supply a language when known, a source name for provenance, and the original first line number when the excerpt came from a larger file.

<?php

use Alto\Code\Snippet\CodeSnippet;

$snippet = CodeSnippet::fromCode(
    "one\ntwo\nthree",
    'php',
    sourceName: 'src/Example.php',
    startLine: 20,
)->selectLines(2);

The code is stored verbatim, including LF, CRLF, or CR line endings. Read it with code(), language(), sourceName(), startLine(), endLine(), and lineCount(). An empty snippet has zero lines.

Lines

lines() returns CodeLine values with two coordinate systems:

Value Coordinate system Example
index One-based position inside the snippet 2
number One-based position in the original source 21 when startLine is 20
code Line content without its line break two
selected Presentation-neutral line marker true
<?php

$line = $snippet->lines()[1];

printf(
    "index=%d source=%d selected=%s code=%s\n",
    $line->index,
    $line->number,
    $line->selected ? 'true' : 'false',
    $line->code,
);

This prints index=2 source=21 selected=true code=two. Selected line numbers must exist inside the snippet.

Annotations

A CodeAnnotation attaches an application-defined type and optional metadata to a half-open byte range: the start offset is included and the end offset is excluded.

<?php

use Alto\Code\Snippet\CodeAnnotation;

$annotated = $snippet->annotate(
    new CodeAnnotation(4, 3, 'focus'),
    new CodeAnnotation(5, 5, 'warning', ['label' => 'Review']),
);

Snippet offsets are relative to code(). CodeLine::annotations() clips an annotation to the line and shifts its offset to the beginning of that line. Use highlight() when callers know the text rather than its byte offset:

<?php

$snippet = CodeSnippet::fromCode('sum + sum')->highlight('sum');

Both occurrences receive a focus annotation. Pass occurrence: 2 to target only the second match. For another annotation type or additional data, use annotateText():

<?php

$snippet = $snippet->annotateText(
    'sum',
    'warning',
    ['label' => 'Check this value'],
    occurrence: 1,
);

Matches are literal, case-sensitive, non-overlapping byte ranges. Missing text leaves the snippet unchanged; empty text, an empty type, or a non-positive occurrence raises InvalidArgumentException.

Annotations may overlap or cross line breaks.

Snippet "abc\ndef": annotation [2, 5)
  line 1 "abc": [2, 3) covers "c"
  line 2 "def": [0, 1) covers "d"

Offsets use PHP byte semantics. For UTF-8, byte positions can differ from character positions. Offsets must be non-negative, lengths positive, types non-blank, and ranges contained in the snippet.

Segments

CodeLine::segments() splits a line wherever its active annotations change. Overlapping annotations remain active together; the consumer decides how to combine their visual styles.

<?php

$overlap = CodeSnippet::fromCode('abcd')->annotate(
    new CodeAnnotation(0, 3, 'focus'),
    new CodeAnnotation(1, 2, 'warning'),
);

foreach ($overlap->lines()[0]->segments() as $segment) {
    $types = array_map(
        static fn(CodeAnnotation $annotation): string => $annotation->type,
        $segment->annotations,
    );
    printf("%d %s [%s]\n", $segment->offset, $segment->text, implode(',', $types));
}

The result is:

0 a [focus]
1 bc [focus,warning]
3 d []

Segment offsets are line-relative. Unannotated text is still a segment; an empty line has no segments.

Transformations

slice($start, $end) projects a half-open byte range from an annotated snippet. Crossing annotations are clipped and shifted. Original line numbers and selections follow the remaining code.

<?php

$slice = $snippet->slice(4, 7);
$line = $slice->lines()[0];

Here $line->code is two, its snippet index is 1, its original source number is 21, and it remains selected.

Use dedent() or its unindent() alias to remove common indentation. indent($spaces) adds spaces to non-empty lines. Both operations preserve source line numbers and move annotations with the surviving text. Annotations inside removed indentation disappear, and annotations crossing it are clipped.

Export

toArray() exports code, language, provenance, selections, lines, and annotations. json_encode() produces the same portable model through JsonSerializable.

<?php

$payload = $snippet->toArray();
$json = json_encode($snippet, JSON_THROW_ON_ERROR);

The package does not provide a JSON import factory or a renderer. Code Slicer can locate source declarations, Code Highlight can tokenize code, and Code Kit can connect those stages to this model.

Invalid input

Invalid source positions, selected lines, slice ranges, annotation ranges, language identifiers, or indentation values raise InvalidArgumentException. Use lineCount() and strlen($snippet->code()) to check the corresponding line and byte boundaries before applying user-provided coordinates.