MingaEditor.Viewport (Minga v0.1.0)

Copy Markdown View Source

Viewport logic for scrolling the visible region of a buffer.

The viewport defines which lines and columns are currently visible in the terminal. When the cursor moves outside the viewport, it scrolls to keep the cursor visible.

Summary

Types

t()

A viewport representing the visible terminal region.

Functions

Scrolls so the cursor line is at the bottom of the viewport (zb in vim).

Centers the viewport on the given cursor line (zz in vim).

Returns the number of columns available for buffer content after the gutter.

Returns the number of content rows (total rows minus reserved).

Computes how many buffer lines fit in display_rows screen rows, accounting for decorations that consume extra display rows.

Adjusts the raw row count for a line spacing multiplier.

Number of rows reserved for the footer (modeline + minibuffer).

Computes the gutter width for line numbers based on total line count.

Creates a new viewport with the given dimensions and default reserved rows (2).

Creates a new viewport with the given dimensions and explicit reserved rows.

Scrolls the viewport down by one line without moving the cursor.

Scrolls the viewport down by one line with an explicit scroll margin.

Scrolls the viewport up by one line without moving the cursor.

Scrolls the viewport up by one line with an explicit scroll margin.

Scrolls the viewport to keep the cursor visible.

Scrolls the viewport with a scroll margin.

Scrolls so the cursor line is at the top of the viewport (zt in vim).

Returns the range of visible lines as {first_line, last_line} (inclusive).

Types

t()

@type t() :: %MingaEditor.Viewport{
  cols: pos_integer(),
  left: non_neg_integer(),
  reserved: non_neg_integer(),
  rows: pos_integer(),
  top: non_neg_integer()
}

A viewport representing the visible terminal region.

  • top — first visible buffer line (0-indexed)
  • left — first visible display column (0-indexed, in terminal columns).
             Wide characters (CJK, emoji) occupy 2 display columns, so
             horizontal scroll advances by display columns, not grapheme counts.
  • rows — total rows in this viewport (including reserved rows)
  • cols — total columns in this viewport
  • reserved — rows reserved for non-content elements (modeline, minibuffer).
             Defaults to `footer_rows()` (2) for the terminal-level viewport.
             Set to 0 for per-window viewports where Layout already excluded
             the modeline from the content rect.

Functions

bottom_on(vp, cursor_line, total_lines, margin \\ 0)

@spec bottom_on(t(), non_neg_integer(), non_neg_integer(), non_neg_integer()) :: t()

Scrolls so the cursor line is at the bottom of the viewport (zb in vim).

Respects scroll_margin by placing the cursor margin lines from the bottom.

center_on(vp, cursor_line, total_lines)

@spec center_on(t(), non_neg_integer(), non_neg_integer()) :: t()

Centers the viewport on the given cursor line (zz in vim).

Returns the updated viewport.

content_cols(viewport, line_count)

@spec content_cols(t(), non_neg_integer()) :: pos_integer()

Returns the number of columns available for buffer content after the gutter.

Subtracts gutter_width(line_count) from the viewport's total columns, clamped to at least 1.

content_rows(viewport)

@spec content_rows(t()) :: pos_integer()

Returns the number of content rows (total rows minus reserved).

effective_page_lines(cursor_line, display_rows, decorations, total_lines)

@spec effective_page_lines(
  non_neg_integer(),
  pos_integer(),
  Minga.Core.Decorations.t(),
  non_neg_integer()
) :: pos_integer()

Computes how many buffer lines fit in display_rows screen rows, accounting for decorations that consume extra display rows.

Walks forward from cursor_line, counting each buffer line as 1 display row plus any virtual lines and block decorations attached to it. Stops when the display row budget is exhausted. Returns the number of buffer lines traversed.

When there are no decorations, this returns display_rows (the fast path).

effective_rows(raw_rows, line_spacing \\ 1.0)

@spec effective_rows(pos_integer(), number()) :: pos_integer()

Adjusts the raw row count for a line spacing multiplier.

When line_spacing > 1.0, each line takes more vertical space, so fewer lines fit on screen. Returns floor(rows / line_spacing), clamped to at least 1. Returns the input unchanged when spacing is 1.0 (the TUI default).

The caller reads Config.get(:line_spacing) and passes it here. This keeps Viewport a pure module with no config dependency.

gutter_width(line_count)

@spec gutter_width(non_neg_integer()) :: pos_integer()

Computes the gutter width for line numbers based on total line count.

Returns max(digits(line_count), 2) + 1 — at least 2 digits plus a trailing space separator. For example: 1–99 lines → 3, 100–999 → 4.

new(rows, cols)

@spec new(pos_integer(), pos_integer()) :: t()

Creates a new viewport with the given dimensions and default reserved rows (2).

new(rows, cols, reserved)

@spec new(pos_integer(), pos_integer(), non_neg_integer()) :: t()

Creates a new viewport with the given dimensions and explicit reserved rows.

scroll_line_down(vp, cursor_line, total_lines)

@spec scroll_line_down(t(), non_neg_integer(), non_neg_integer()) ::
  {t(), non_neg_integer()}

Scrolls the viewport down by one line without moving the cursor.

The cursor line is clamped to remain visible and respect scroll_margin. When scrolling down, the cursor is pushed away from the top edge to maintain the margin, matching vim's scrolloff behavior for Ctrl-E. Returns {updated_viewport, clamped_cursor_line}.

scroll_line_down(vp, cursor_line, total_lines, margin)

@spec scroll_line_down(t(), non_neg_integer(), non_neg_integer(), non_neg_integer()) ::
  {t(), non_neg_integer()}

Scrolls the viewport down by one line with an explicit scroll margin.

scroll_line_up(vp, cursor_line, total_lines)

@spec scroll_line_up(t(), non_neg_integer(), non_neg_integer()) ::
  {t(), non_neg_integer()}

Scrolls the viewport up by one line without moving the cursor.

The cursor line is clamped to remain visible and respect scroll_margin. When scrolling up, the cursor is pushed away from the bottom edge to maintain the margin, matching vim's scrolloff behavior for Ctrl-Y. Returns {updated_viewport, clamped_cursor_line}.

scroll_line_up(vp, cursor_line, total_lines, margin)

@spec scroll_line_up(t(), non_neg_integer(), non_neg_integer(), non_neg_integer()) ::
  {t(), non_neg_integer()}

Scrolls the viewport up by one line with an explicit scroll margin.

scroll_to_cursor(vp, arg)

@spec scroll_to_cursor(
  t(),
  {non_neg_integer(), non_neg_integer()}
) :: t()

Scrolls the viewport to keep the cursor visible.

Returns a new viewport adjusted so that the cursor position {line, col} is within the visible area. col must be a display column (terminal columns, not grapheme count) — wide characters count as 2. Reserves footer rows for the modeline and minibuffer.

scroll_to_cursor(vp, arg, buf)

@spec scroll_to_cursor(
  t(),
  {non_neg_integer(), non_neg_integer()},
  pid() | non_neg_integer()
) :: t()

Scrolls the viewport with a scroll margin.

Accepts either a buffer pid (reads scroll_margin from the buffer's local options) or an explicit integer margin. The margin keeps n lines visible above and below the cursor when possible. When the file is shorter than 2 * margin + 1, the margin shrinks to fit.

top_on(vp, cursor_line, total_lines, margin \\ 0)

@spec top_on(t(), non_neg_integer(), non_neg_integer(), non_neg_integer()) :: t()

Scrolls so the cursor line is at the top of the viewport (zt in vim).

Respects scroll_margin by placing the cursor margin lines from the top.

visible_range(viewport)

@spec visible_range(t()) :: {non_neg_integer(), non_neg_integer()}

Returns the range of visible lines as {first_line, last_line} (inclusive).