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_blockandclippedstyles 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) orlinkwith 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.