NAME

Term::Fabulous::Document - A document: a list of widgets with the sections and the layout a document view shows them with

SYNOPSIS

use Term::Fabulous::Document;
use Term::Fabulous::Widget::DocumentView;
use Term::Fabulous::Widget::Table;

my $document = Term::Fabulous::Document->new( source => 'snapback.md' );
$document->heading( 1, 'snapback' );
$document->paragraph( { text => 'Copies files to a backup disk.', marks => [ [ 0, 6, 'bold' ] ] } );
$document->heading( 2, 'Usage' );
$document->code( "snapback ~/Documents /mnt/backup", language => 'shell' );
$document->list_item( "\x{2022}", { text => 'Keeps the last 14 backups.', marks => [ [ 15, 17, 'code' ] ] } );
$document->paragraph( { markup => 'See [link=faq.md]the FAQ[/].' } );

# Any widget is an item: a table, a picture, a form.
$document->add( Term::Fabulous::Widget::Table->new( id => 'releases', columns => [...], rows => [...] ) );

my $view = Term::Fabulous::Widget::DocumentView->new( id => 'manual', document => $document );

DESCRIPTION

A document is what a Term::Fabulous::Widget::DocumentView shows: an ordered list of items, each a widget, with a few options that say how the view lays the item out, and the sections and anchors that links lead to. A parser makes one from a file: it calls the "FACTORIES" for the common items, headings, paragraphs, list items, terms, code and rules, and "add" for any other widget. The document knows nothing of the screen; it can be made and inspected without a terminal.

An item is any widget without a parent: a Term::Fabulous::Widget::Text, a box of several widgets, a table, a form with buttons. The view searches the texts of every item through Term::Fabulous::Role::Searchable, selects and follows the links of every Term::Fabulous::Widget::Text in them, and lets the user reach their interactive widgets with Tab.

Every item belongs to the document while the document exists, and is shown by one view at a time: the view wraps each item in a box of its own (see "How items are shown" in Term::Fabulous::Widget::DocumentView).

Worked examples are in Term::Fabulous::Cookbook::Documents and Term::Fabulous::Cookbook::DocumentTechniques: "Show a document built in Perl (Document, factories, marks)" in Term::Fabulous::Cookbook::Documents builds a document with the factories, "Turn Markdown into a document (Markdent)" in Term::Fabulous::Cookbook::Documents is a reader, and "Put any widget into a document (a table and a picture)" in Term::Fabulous::Cookbook::DocumentTechniques adds widgets of other kinds.

Item options

"add" and every factory take these options, which the view applies when it shows the item:

gap

The blank rows above the item, a non-negative integer. Default: 1 for "add"; the factories choose (see "FACTORIES"). The first item of a document never has blank rows above it.

indent

Columns by which the item is indented, on top of the view's own indent. Default: 0.

flush

A boolean: the item ignores the view's indent and starts at the left edge (plus its own indent). Default: 0; headings are flush.

quote

The number of block quotes the item is in, a non-negative integer. The view draws a bar on its left for each. Default: 0.

rows

The rows the item takes, when it is known before the item is laid out and the item's own estimate is not good enough. Default: none; the view asks the widget ("estimated_rows" in Term::Fabulous::Widget). A wrong number does no harm beyond a scrollbar that jumps a little when the item comes into view.

section

[ $level, $title ]: the item starts a section, with a level from 1 to 6 and a title. Sections make the table of contents and the section of Term::Fabulous::Event::ViewChange, and "find_section" finds them. Headings start sections.

anchor

A name links can lead to ("find_section"), such as the text of a term.

An unknown option and an invalid value die.

CONSTRUCTOR

new

my $document = Term::Fabulous::Document->new( source => $path, look => 'My::Look' );

Creates an empty document. Both parameters are optional:

source

A string: where the document came from, a file name for instance, for the program to show or to resolve relative links with. Default: none.

look

The name of a loaded Term::Fabulous::Document::Look class, whose kinds the factories style marks with, and whose code_block and clipped styles code blocks paint with. Default: Term::Fabulous::Document::Look. A reader with mark kinds of its own names a subclass (see "Changing and adding kinds" in Term::Fabulous::Document::Look).

FACTORIES

Each factory makes an item, adds it with the given "Item options", and returns the widget. A text is given as one of:

  • a string, the plain text;

  • { text => $text, marks => [ [ $start, $end, $kind ], [ $start, $end, 'link', $target ], ... ] }: the text and its marks, ranges of character offsets (the start and the offset after the end) with a kind of the document's look (bold, italic, code, file, ...; see "KINDS" in Term::Fabulous::Document::Look) or link with a target, any defined value that LinkActivate hands back;

  • { markup => $markup }: markup as Term::Fabulous::Text::Markup reads it, styles and links included. Its tags are style strings and palette tokens, not the kinds of the look: [bold] and [link=faq.md] work, [code] dies.

Marks may overlap, links may not. A kind the look does not know, a mark past the text and an invalid shape die. The styles are palette tokens of the theme ("token_style" in Term::Fabulous::Document::Look), so a theme switch recolors the document without any work.

Unless the gap option says otherwise, a factory puts one blank row above its item, with two exceptions: none between the items of a list (a list item whose position is above 1, right after another list item), nor before the first item of a list nested in the list item above it (indented deeper); and none between a term and the item after it, if that item is not a term and is indented deeper than the term, its definition.

heading

my $widget = $document->heading( $level, $text, %options );

A heading of level 1 to 6, in the style of its level (heading1 to heading6); a first-level heading has a horizontal rule under it. It is flush, and starts a section with its text as the title. Returns the Text, or for level 1 a Term::Fabulous::Widget::Box that holds the Text and a Term::Fabulous::Widget::Divider.

paragraph

my $text = $document->paragraph( $text, %options );

A paragraph: a Term::Fabulous::Widget::Text that the view wraps to its width.

term

my $text = $document->term( $text, %options );

The term of a definition list, in the term style, with its text as an anchor. Give the paragraph that defines it a deeper indent, and it follows the term without a blank row.

list_item

my $row = $document->list_item( $marker, $text, marker_width => 3, position => 1, %options );

An item of a list: the marker (a bullet or a number, in the marker style, which a search passes over) in a column of its own, and the text, whose lines all start after the marker. marker_width is the columns from the start of the marker to the start of the text, at least one more than the marker takes (the default); give all items of a list the same width so that their texts line up. position counts the items of a list from 1 (the default); an item with position 1 starts a new list, with a blank row above. A nested list is a list with a deeper indent: the parent's indent plus its marker_width.

code

my $block = $document->code( $code, language => 'perl', %options );

A Term::Fabulous::Widget::CodeBlock: code that keeps its lines and never wraps, in the document look's code_block style. The optional language goes to the view's highlighter.

rule

my $divider = $document->rule(%options);

A horizontal rule, a Term::Fabulous::Widget::Divider across the view.

METHODS

add

my $widget = $document->add( $widget, %options );

Adds a widget as the next item, with the "Item options", and returns it. The widget must be an element or a text without a parent, and not an item already; anything else dies. Items can be added while a view shows the document: the view shows them from its next frame.

items, count, item

my @widgets = $document->items;
my $count   = $document->count;
my $widget  = $document->item($index);

The items, their number, and the item with an index from 0. An index that is no item dies.

options_of

my $options = $document->options_of($index);    # { gap, indent, flush, quote, rows }

How the view lays the item out, as a new hash.

kind_of

my $kind = $document->kind_of($index);    # 'heading', 'paragraph', 'term', 'item', 'code', 'rule' or 'widget'

What made an item: the factory (item for "list_item"), or widget for an item given to "add".

index_of

my $index = $document->index_of($widget);    # or undef

The index of an item, or undef for a widget that is no item (one inside an item, say).

texts, searchables

my @texts       = $document->texts($index);
my @searchables = $document->searchables($index);

The Term::Fabulous::Widget::Text widgets of an item, the item itself included, in tree order (where its links are), and the widgets of the item that answer a search ("searchables_in" in Term::Fabulous::Role::Searchable). The marker of a list item is one of its texts: a Term::Fabulous::Document::Marker, a Text that a search passes over, since bullets and numbers are not content.

code_blocks

my @blocks = $document->code_blocks;

The code blocks the "code" factory made, which the view gives its highlighter.

sections, section_count

foreach my $section ( $document->sections ) {
	say '  ' x ( $section->{level} - 1 ), $section->{title};    # a table of contents
}

The sections in the order of the document, each as a new hash with the item index it starts at, its level and its title, and their number. "Show a table of contents beside the document (sections, KDL)" in Term::Fabulous::Cookbook::DocumentTechniques lists them beside the document.

item_of_section, level_of_section, title_of_section

my $index = $document->item_of_section(3);

The item a section starts at, its level and its title, by the number of the section from 0. A number that is no section dies.

section_at

my $number = $document->section_at($index);    # or undef

The number of the section an item is in: the last section that starts at or before it. undef for an item before the first section.

find_section

my $index = $document->find_section('Methods');    # or undef

The item a link to a section leads to. The name is compared, with whitespace squeezed, in four rounds: exactly, then ignoring case, then as a title or anchor that starts with the name as a word (open finds open FILEHANDLE,EXPR), then the same ignoring case. In each round the titles of the sections come before the anchors, so an anchor that matches exactly wins over a title that matches only when case is ignored. undef when nothing matches or the name is empty. "Follow links: jump to sections, open other pages, go back (LinkActivate)" in Term::Fabulous::Cookbook::Documents uses it to follow a link to a section.

find_text

foreach my $match ( $document->find_text('needle') ) {
	my ( $index, $widget, $key, $start, $end ) = @$match;
}

Every occurrence of the text in the texts the searchable widgets of the items show, in the order of the document: the item, the widget that shows the text, the text's key (see "search_texts" in Term::Fabulous::Role::Searchable) and the offsets of the match. A query without capital letters ignores case; it is text, not a pattern. Matches do not overlap. The view's search builds on it; see "Search a document and show the reader's position (search, ViewChange)" in Term::Fabulous::Cookbook::Documents.

source, look

The values of the constructor parameters.

shown_by

my $view = $document->shown_by;    # or undef

The view that shows the document. A document is shown by one view at a time; giving it to a second one dies.

SEE ALSO

Term::Fabulous::Widget::DocumentView, Term::Fabulous::Document::Look, Term::Fabulous::Role::Searchable, Term::Fabulous::Cookbook::Documents, Term::Fabulous::Cookbook::DocumentTechniques.