Skip to content

xnano.cursor

xnano.cursor

xnano.cursor

A canvas's cursor: where it is, how it looks, and how to move it.

Classes:

  • Cursor –

    The one cursor a canvas has.

Cursor

Cursor(canvas: Canvas[object, object])

Bases: _SealedAttributes

The one cursor a canvas has.

Get it from Canvas.cursor or from context.cursor inside a hook. A canvas draws no cursor until it is given a position, which a text field does to show where typing goes. Any thread may change it, a change is drawn on the next frame even when no cell changed, and a canvas on the screen gives the user's own cursor back when it closes. Opening a canvas on the screen keeps its cursor as it was.

Moving is relative to where the cursor is: up, down, left and right move it by a count of cells, and move moves it along both axes at once. The cursor stays inside the canvas, so moving past an edge stops at the edge. A cursor with no position moves from the top-left cell. Each of these returns the cursor, so they chain.

Attributes:

  • position (Point | None) –

    The cell the cursor is at as a Point, or None when it has none. Assign a Point, an (x, y) pair or None.

  • shape (CursorShape | None) –

    'block', 'bar' or 'underline'; None keeps the user's own shape.

  • is_blinking (bool) –

    Whether the cursor blinks. Setting it without a shape uses a block.

  • is_visible (bool) –

    Whether the cursor is shown when it has a position.

Raises:

  • TypeError –

    is_blinking or is_visible is assigned a value that is not True or False.

Examples:

import xnano
from xnano_core import Point

with xnano.new(width=10, height=3) as canvas:
    canvas.cursor.shape = 'bar'
    canvas.cursor.home().right(4).down()
    assert canvas.cursor.position == Point(4, 1)

Create the canvas's cursor. Use canvas.cursor instead of calling this.

Parameters:

Methods:

  • move –

    Move the cursor by a number of cells along each axis.

  • up –

    Move the cursor up by count rows, stopping at the top.

  • down –

    Move the cursor down by count rows, stopping at the bottom.

  • left –

    Move the cursor left by count columns, stopping at the left edge.

  • right –

    Move the cursor right by count columns, stopping at the right edge.

  • home –

    Put the cursor in the top-left cell.

  • show –

    Make the cursor visible, in the top-left cell when it has no position yet.

  • hide –

    Hide the cursor. Its position is kept, so show brings it back.

  • follow –

    Keep the cursor at a place inside a placement, region or buffer, wherever it goes.

  • unfollow –

    Stop following a placement or buffer, leaving the cursor where it is.

  • __setattr__ –

    Set an attribute, or raise AttributeError for an unknown public name.

Source code in xnano/cursor.py
def __init__(self, canvas: Canvas[object, object]) -> None:
    """
    Create the canvas's cursor. Use ``canvas.cursor`` instead of calling this.

    Args:
        canvas: The canvas the cursor belongs to.
    """
    self._canvas = canvas
    self._seal()

position property writable

position: Point | None

The cell the cursor is at, or None when it has none.

shape property writable

shape: CursorShape | None

'block', 'bar' or 'underline'; None keeps the user's own shape.

is_blinking property writable

is_blinking: bool

Whether the cursor blinks. Setting it without a shape uses a block.

is_visible property writable

is_visible: bool

Whether the cursor is shown when it has a position.

move

move(x: int = 0, y: int = 0) -> Cursor

Move the cursor by a number of cells along each axis.

Parameters:

  • x (int, default: 0 ) –

    Columns to move right; a negative number moves left.

  • y (int, default: 0 ) –

    Rows to move down; a negative number moves up.

Returns:

  • Cursor –

    The cursor, so calls can chain.

Raises:

  • TypeError –

    An amount is not a whole number.

  • ValueError –

    An amount is outside the range of a cell number.

Examples:

import xnano
from xnano_core import Point

with xnano.new(width=10, height=3) as canvas:
    canvas.cursor.position = (2, 1)
    canvas.cursor.move(x=3, y=-1)
    assert canvas.cursor.position == Point(5, 0)
    canvas.cursor.move(x=-99)
    assert canvas.cursor.position == Point(0, 0)
Source code in xnano/cursor.py
def move(self, x: int = 0, y: int = 0) -> Cursor:
    """
    Move the cursor by a number of cells along each axis.

    Args:
        x: Columns to move right; a negative number moves left.
        y: Rows to move down; a negative number moves up.

    Returns:
        The cursor, so calls can chain.

    Raises:
        TypeError: An amount is not a whole number.
        ValueError: An amount is outside the range of a cell number.

    Examples:
        ```python
        import xnano
        from xnano_core import Point

        with xnano.new(width=10, height=3) as canvas:
            canvas.cursor.position = (2, 1)
            canvas.cursor.move(x=3, y=-1)
            assert canvas.cursor.position == Point(5, 0)
            canvas.cursor.move(x=-99)
            assert canvas.cursor.position == Point(0, 0)
        ```
    """
    horizontal = check_cells(x, 'x')
    vertical = check_cells(y, 'y')
    source = self._source
    current = source.position
    column, row = (0, 0) if current is None else (current.x, current.y)
    size = self._canvas.size
    column = min(max(column + horizontal, 0), max(size.width - 1, 0))
    row = min(max(row + vertical, 0), max(size.height - 1, 0))
    source.position = (column, row)
    self._canvas._cursor_follow = None
    return self

up

up(count: int = 1) -> Cursor

Move the cursor up by count rows, stopping at the top.

Parameters:

  • count (int, default: 1 ) –

    How many rows to move.

Raises:

Source code in xnano/cursor.py
def up(self, count: int = 1) -> Cursor:
    """
    Move the cursor up by ``count`` rows, stopping at the top.

    Args:
        count: How many rows to move.

    Raises:
        TypeError: ``count`` is not a whole number.
        ValueError: ``count`` is negative.
    """
    return self.move(y=-check_count(count, verb='up', opposite='down'))

down

down(count: int = 1) -> Cursor

Move the cursor down by count rows, stopping at the bottom.

Parameters:

  • count (int, default: 1 ) –

    How many rows to move.

Raises:

Source code in xnano/cursor.py
def down(self, count: int = 1) -> Cursor:
    """
    Move the cursor down by ``count`` rows, stopping at the bottom.

    Args:
        count: How many rows to move.

    Raises:
        TypeError: ``count`` is not a whole number.
        ValueError: ``count`` is negative.
    """
    return self.move(y=check_count(count, verb='down', opposite='up'))

left

left(count: int = 1) -> Cursor

Move the cursor left by count columns, stopping at the left edge.

Parameters:

  • count (int, default: 1 ) –

    How many columns to move.

Raises:

Source code in xnano/cursor.py
def left(self, count: int = 1) -> Cursor:
    """
    Move the cursor left by ``count`` columns, stopping at the left edge.

    Args:
        count: How many columns to move.

    Raises:
        TypeError: ``count`` is not a whole number.
        ValueError: ``count`` is negative.
    """
    return self.move(x=-check_count(count, verb='left', opposite='right'))

right

right(count: int = 1) -> Cursor

Move the cursor right by count columns, stopping at the right edge.

Parameters:

  • count (int, default: 1 ) –

    How many columns to move.

Raises:

Source code in xnano/cursor.py
def right(self, count: int = 1) -> Cursor:
    """
    Move the cursor right by ``count`` columns, stopping at the right edge.

    Args:
        count: How many columns to move.

    Raises:
        TypeError: ``count`` is not a whole number.
        ValueError: ``count`` is negative.
    """
    return self.move(x=check_count(count, verb='right', opposite='left'))

home

home() -> Cursor

Put the cursor in the top-left cell.

Source code in xnano/cursor.py
def home(self) -> Cursor:
    """Put the cursor in the top-left cell."""
    self._source.position = (0, 0)
    self._canvas._cursor_follow = None
    return self

show

show() -> Cursor

Make the cursor visible, in the top-left cell when it has no position yet.

Examples:

import xnano
from xnano_core import Point

with xnano.new(width=10, height=3) as canvas:
    canvas.cursor.show()
    assert canvas.cursor.is_visible
    assert canvas.cursor.position == Point(0, 0)
Source code in xnano/cursor.py
def show(self) -> Cursor:
    """
    Make the cursor visible, in the top-left cell when it has no position yet.

    Examples:
        ```python
        import xnano
        from xnano_core import Point

        with xnano.new(width=10, height=3) as canvas:
            canvas.cursor.show()
            assert canvas.cursor.is_visible
            assert canvas.cursor.position == Point(0, 0)
        ```
    """
    source = self._source
    if source.position is None:
        source.position = (0, 0)
    source.is_visible = True
    return self

hide

hide() -> Cursor

Hide the cursor. Its position is kept, so show brings it back.

Source code in xnano/cursor.py
def hide(self) -> Cursor:
    """Hide the cursor. Its position is kept, so [`show`][xnano.cursor.Cursor.show] brings it back."""
    self._source.is_visible = False
    return self

follow

follow(target: Placement[object, object] | Region | Buffer, *, x: int = 0, y: int = 0) -> Cursor

Keep the cursor at a place inside a placement, region or buffer, wherever it goes.

The cursor hides while the target, or anything above it, is hidden, and it loses its position when the target is removed. Assigning position or calling move stops the following.

Parameters:

Raises:

  • TerminalClosedError –

    The target is a placement, or a region of one, that was already removed, or its canvas is closed.

  • TypeError –

    The target is not a placement, a region or a buffer, or x or y is not a whole number.

  • ValueError –

    x or y is outside the range of a cell number.

Examples:

import xnano
from xnano_core import Point

with xnano.new(width=20, height=3) as canvas:
    field = canvas.write('name', x=5, y=1)
    canvas.cursor.follow(field, x=4)
    field.x = 8
    assert canvas.cursor.position == Point(12, 1)
Source code in xnano/cursor.py
def follow(
    self,
    target: Placement[object, object] | Region | _core.Buffer,
    *,
    x: int = 0,
    y: int = 0,
) -> Cursor:
    """
    Keep the cursor at a place inside a placement, region or buffer, wherever it goes.

    The cursor hides while the target, or anything above it, is hidden, and it loses its
    position when the target is removed. Assigning ``position`` or calling
    [`move`][xnano.cursor.Cursor.move] stops the following.

    Args:
        target: The [`Placement`][xnano.placement.Placement],
            [`Region`][xnano.region.Region] or buffer to follow.
        x: The column inside the target.
        y: The row inside the target.

    Raises:
        TerminalClosedError: The target is a placement, or a region of one, that was
            already removed, or its canvas is closed.
        TypeError: The target is not a placement, a region or a buffer, or ``x`` or ``y``
            is not a whole number.
        ValueError: ``x`` or ``y`` is outside the range of a cell number.

    Examples:
        ```python
        import xnano
        from xnano_core import Point

        with xnano.new(width=20, height=3) as canvas:
            field = canvas.write('name', x=5, y=1)
            canvas.cursor.follow(field, x=4)
            field.x = 8
            assert canvas.cursor.position == Point(12, 1)
        ```
    """
    x = check_cells(x, 'x')
    y = check_cells(y, 'y')
    if isinstance(target, _core.Buffer):
        buffer = target
    elif hasattr(target, 'core_buffer'):
        require_attached = getattr(target, '_require_attached', None)
        if require_attached is not None:
            require_attached()
        buffer = target.core_buffer  # type: ignore[union-attr]
    else:
        if not hasattr(target, '_inset'):
            raise TypeError(
                f'follow() takes a placement, a region or a buffer, but got {target!r}. '
                f'Pass what canvas.write(...) returned, for example '
                f'canvas.cursor.follow(canvas.write("name")).'
            )
        region = cast('Region', target)
        require_owner_attached = getattr(region._owner, '_require_attached', None)
        if require_owner_attached is not None:
            require_owner_attached()
        buffer = region._buffer
        left, top, _, _ = region._inset
        x = check_cells(x + left, 'x')
        y = check_cells(y + top, 'y')
    self._source.follow(buffer, x=x, y=y)
    self._canvas._cursor_follow = (buffer, x, y)
    return self

unfollow

unfollow() -> Cursor

Stop following a placement or buffer, leaving the cursor where it is.

Source code in xnano/cursor.py
def unfollow(self) -> Cursor:
    """Stop following a placement or buffer, leaving the cursor where it is."""
    source = self._source
    current = source.position
    source.follow(None)
    self._canvas._cursor_follow = None
    if current is not None:
        source.position = current
    return self

__setattr__

__setattr__(name: str, value: object) -> None

Set an attribute, or raise AttributeError for an unknown public name.

Source code in xnano/_attributes.py
def __setattr__(self, name: str, value: object) -> None:
    """Set an attribute, or raise ``AttributeError`` for an unknown public name."""
    if (
        name[:1] != '_'
        and self.__dict__.get('_is_sealed')
        and not hasattr(type(self), name)
        and name not in self.__dict__
    ):
        kind = type(self).__name__
        settable = sorted(
            key
            for key in dir(type(self))
            if key[:1] != '_' and _is_settable(type(self), key)
        )
        close = difflib.get_close_matches(name, settable, n=1)
        hint = f' Did you mean {close[0]!r}?' if close else ''
        raise AttributeError(
            f'{kind} has no attribute called {name!r}, so assigning to it would change '
            f'nothing.{hint} The attributes you can assign are: {", ".join(settable)}.'
        )
    object.__setattr__(self, name, value)