Documentation
¶
Overview ¶
Package view holds nem's view state: a Window (a buffer plus the cursor and viewport belonging to one view of it) and the tree of splits that arranges windows on screen.
It imports text and the standard library, and nothing else. A window knows where point is and which lines are visible; the split tree knows how to divide a rectangle. Neither knows what a terminal is, which is why both stay testable without one and why this package is separate from ui.
Index ¶
- Constants
- Variables
- func PosInRow(b *text.Buffer, r RowPos, width int, col text.ColIdx) text.Pos
- func RowSpan(b *text.Buffer, r RowPos, width int) (from, to text.RuneIdx, col text.ColIdx)
- func TextHeight(r Rect) int
- type Anchor
- type Leaf
- type Node
- type PanelReq
- type Rect
- type RowPos
- type Span
- type Split
- type Tree
- type Window
Constants ¶
const ( // Minimums include the one-cell border a panel always draws, so a placement // that reports ok can show at least one line of content in at least four // columns. Sizing these to the border alone would let PlacePanel succeed for // a panel that structurally cannot display anything - placement and drawing // would then disagree about what "usable" means. MinPanelWidth = 6 // 4 content columns MinPanelHeight = 3 // 1 content row )
Minimum usable panel size.
Three rows is the least that can show a prompt plus one candidate; a one-row panel says nothing the echo row does not already say better, so it is never worth drawing. Four columns is the least that can show a truncated candidate and still read as a list rather than as noise.
const ( MinWindowHeight = 2 MinWindowWidth = 8 )
Minimum usable pane size. A window needs one row of text plus its modeline, and enough columns that a modeline is not pure ellipsis.
const DividerWidth = 1
DividerWidth is the width of the column drawn between side-by-side panes.
const GoalColUnset text.ColIdx = -1
GoalColUnset marks a window whose goal column has not been established. The goal column is set by horizontal motion and preserved by vertical motion, so moving down through a short line and out the other side returns to the original column.
Variables ¶
var ( // ErrTooSmall is returned by Split when the frame cannot accommodate two // panes of at least MinWindowWidth by MinWindowHeight. ErrTooSmall = errors.New("view: not enough room to split") // ErrNotFound is returned when a window is not present in the tree. ErrNotFound = errors.New("view: window not in tree") // ErrSoleWindow is returned by Delete when only one window remains. ErrSoleWindow = errors.New("view: cannot delete the sole window") )
Functions ¶
func PosInRow ¶ added in v0.11.0
PosInRow is the position col columns into row r: on the cluster at that column, or the row's last one when it is shorter. The last row of a line can take its end, as a line can; any other row ends before the next begins, since that position is the next row's.
func RowSpan ¶ added in v0.11.0
RowSpan is where row r starts and ends, as rune indices of its line, and the column it starts at.
func TextHeight ¶
TextHeight returns the rows of r available for buffer text, which is all of them but the last: the modeline owns the bottom row of every window.
Types ¶
type Anchor ¶
type Anchor int
Anchor says where a panel wants to sit.
const ( // AnchorUnset is the zero value and is not a placement. PlacePanel refuses // it, so a caller that forgets to set Anchor gets no panel and a failing // test rather than a silently point-anchored one positioned at 0,0 because // PtX and PtY were also left unset. AnchorUnset Anchor = iota // AnchorPoint opens the panel next to point, which is where the eye already // is. Used by completion and by prefix-key discovery. AnchorPoint // AnchorBottom sits the panel flush to the bottom of the frame, which is the // emacs-shaped rendering selected by completion-style = "bottom". AnchorBottom // AnchorCenter centres the panel in the frame. AnchorCenter )
type Node ¶
type Node interface {
// contains filtered or unexported methods
}
Node is one position in the split tree: either a Leaf holding a window or a Split dividing its rectangle between two children. The interface is closed — only this package can implement it.
type PanelReq ¶
type PanelReq struct {
W, H int
Anchor Anchor
// Frame is the area a panel may occupy, which is the screen minus the echo
// row. Excluding that row here rather than special-casing it below is what
// makes "never cover the echo row" reduce to "stay inside Frame".
Frame Rect
// PtX and PtY are point's position on screen, used by AnchorPoint.
PtX, PtY int
}
PanelReq is a request to place a panel of a preferred size.
W and H include any border the caller intends to draw: placement deals in screen cells and has no opinion about what goes in them.
type Rect ¶
type Rect struct{ X, Y, W, H int }
Rect is a rectangle of terminal cells.
func PlacePanel ¶
PlacePanel returns where a panel should be drawn, and whether it should be drawn at all.
Panels are not part of the split tree - they are drawn over the tiled frame - so this is the only geometry they need, and keeping it here rather than in ui means it is testable without a terminal alongside the rest of the layout arithmetic.
The returned rect is always wholly inside Frame and never degenerate. A request larger than the frame is shrunk to it; a request smaller than a drawable panel is raised to the minimum, because handing back something unusable is worse than handing back slightly more than was asked for.
ok is false only when the frame itself cannot hold a panel of the minimum size. The caller is then expected to render some other way rather than draw a box too small to read.
type RowPos ¶ added in v0.11.0
type RowPos struct{ Line, Row int }
RowPos is a row on screen of a wrapped buffer: a line, and which of the rows it folds into.
type Split ¶
Split divides its rectangle between two children.
Vertical means the panes sit side by side, as C-x 3 makes them, with A on the left. A horizontal split stacks them, as C-x 2 makes them, with A above. Ratio is A's share of the space available after any divider, clamped to [0,1].
type Tree ¶
type Tree struct {
Root Node
// contains filtered or unexported fields
}
Tree arranges windows over the frame.
Layout records the frame size it was last given, and Split consults it to refuse a split that would produce an unusable pane. A tree that has never been laid out has no size to check against, so Split allows anything — that is the startup case, before the screen dimensions are known.
func (*Tree) Delete ¶
Delete removes target's pane, giving its space to the sibling that shared the split. The sole remaining window cannot be deleted.
func (*Tree) DeleteOthers ¶
DeleteOthers makes keep the only window, filling the frame. A window not in the tree is ignored rather than allowed to empty it.
func (*Tree) Dividers ¶
Dividers returns the rectangles of the divider columns for a frame of w by h cells, one per vertical split.
A vertical split reserves one column between its panes for the divider, so a pane's rectangle never includes it. A horizontal split needs no divider: the upper window's modeline already separates the two. Together with the window rectangles from Layout, these cover the frame exactly.
func (*Tree) Layout ¶
Layout assigns every window a rectangle within a frame of w by h cells, and records the size for Split's benefit.
Windows and dividers together account for every cell of the frame exactly once; see Dividers.
func (*Tree) Split ¶
Split divides target's pane in two and returns the new window, which shows the same buffer at the same point — that is how two views onto one buffer arise, and each then moves independently.
It refuses rather than produce a pane too small to use, and leaves the tree untouched when it does.
type Window ¶
type Window struct {
Buf *text.Buffer
Pt text.Pos
Top int // first visible buffer line
TopRow int // first visible row of Top, when lines are wrapped
LeftCol text.ColIdx // leftmost visible display column
GoalCol text.ColIdx // GoalColUnset when not established
// Drawn is what the last frame drew here: the buffer, and the lines of
// it on screen. The renderer uses it to let go of the layout of lines
// that have since scrolled away.
Drawn Span
}
Window is one view of a buffer: which buffer, where point is in it, and which lines are currently on screen.
Point lives here rather than in the buffer because several windows may show the same buffer, each with its own cursor. The buffer keeps only the point to restore when a window next visits it.
func NewWindow ¶
NewWindow returns a window showing b from its first line, with point at the origin and no goal column.
func (*Window) ScrollToPoint ¶
ScrollToPoint adjusts Top so that point is visible in a text area of textHeight rows, keeping margin rows of context above and below it where the buffer allows.
margin is clamped to (textHeight-1)/2: a margin of half the window or more would otherwise demand context on both sides that cannot both be satisfied, and the viewport would thrash on every vertical move. Top never goes negative and never scrolls past the last screenful of the buffer, so a buffer shorter than the viewport always sits at the top.
It is the render path's job to call this before drawing; afterwards point is guaranteed to lie within [Top, Top+textHeight).
func (*Window) ScrollToPointHorizontally ¶
ScrollToPointHorizontally adjusts LeftCol so point is visible in a text area textWidth columns wide. It is the horizontal twin of ScrollToPoint, and lives here for the same reason: a viewport is this window's state, not the renderer's.
One column is reserved for the truncation marker whenever the line runs past the right edge. That reservation is viewport geometry rather than drawing, exactly as TextHeight reserving the modeline row is - the renderer is told how much room it has, and only decides what to put there.
Afterwards point is guaranteed to lie within [LeftCol, LeftCol+textWidth).
A single pass suffices, which is not obvious: whether the marker is needed depends on where we scroll to, and where we scroll to depends on the marker, so this looks like it should iterate. It does not, because re-running can only widen usable - the marker stops being needed once we have scrolled far enough right - and a wider usable makes the scroll-right test strictly weaker, while the scroll-left test depends only on left, which is already settled. A sweep over two million combinations of width, line length, point column and starting offset found no case where a second pass moved the result.
func (*Window) ScrollToPointWrapped ¶ added in v0.11.0
ScrollToPointWrapped is ScrollToPoint for a window whose lines are wrapped width columns wide: it moves Top and TopRow, a row at a time, so point's row is in view with margin rows around it, and never scrolls past the last screenful.
func (*Window) SetTop ¶ added in v0.11.0
SetTop puts line at the top of the window, from its first row.
func (*Window) Visit ¶
Visit switches this window to b, saving point into the outgoing buffer so returning to it later lands where you left. This is switch-to-buffer's mechanism.
Visiting the buffer already shown is a no-op, so it cannot lose the current position. Otherwise the incoming buffer's saved point is clamped into range, since the buffer may have shrunk since it was stored.
The view comes back as well as point: the first line that was on screen is restored, so C-x b back to a file shows it as it was left, rather than with point's line jerked to the top. A buffer never shown before starts at its first line, which is what keeps a listing's header in view. Drawing scrolls from there only as far as keeping point visible needs.