Source code for vimsheet.model.cell
"""Cell and CellFormat dataclasses."""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Literal
[docs]
@dataclass
class CellFormat:
"""Visual formatting applied to a single cell."""
bold: bool = False
italic: bool = False
underline: bool = False
align: Literal["left", "right", "center"] = "right"
fg_color: str | None = None # hex RGB e.g. "#ff0000"
bg_color: str | None = None
num_decimals: int | None = None
num_format: str | None = None # strftime or number pattern
thousands_sep: bool = False
[docs]
def copy(self) -> CellFormat:
"""Return a shallow copy of this format."""
return CellFormat(
bold=self.bold,
italic=self.italic,
underline=self.underline,
align=self.align,
fg_color=self.fg_color,
bg_color=self.bg_color,
num_decimals=self.num_decimals,
num_format=self.num_format,
thousands_sep=self.thousands_sep,
)
[docs]
@dataclass
class Cell:
"""A single spreadsheet cell with content, formatting, and metadata."""
row: int
col: int
# Content
formula: str | None = None # raw formula string e.g. "=SUM(A1:A5)"
value: Any = None # computed/stored value
display: str = "" # formatted string for display
# Metadata
fmt: CellFormat = field(default_factory=CellFormat)
locked: bool = False
comment: str | None = None
history: list[tuple[datetime, Any]] = field(default_factory=list)
[docs]
def is_empty(self) -> bool:
"""Return True if the cell has no content."""
return self.formula is None and self.value is None
[docs]
def record_history(self, value: Any) -> None:
"""Record a value change in the cell's history."""
self.history.append((datetime.now(), value))
[docs]
def format_value(self) -> str:
"""Return a display string for the cell's value, applying format rules."""
if self.value is None:
return ""
fmt = self.fmt
if isinstance(self.value, str):
return self.value
if isinstance(self.value, bool):
return "TRUE" if self.value else "FALSE"
if isinstance(self.value, int | float):
num = float(self.value)
decimals = fmt.num_decimals
if decimals is not None:
formatted = f"{num:.{decimals}f}"
else:
# Remove trailing zeros for clean display
if num == int(num) and abs(num) < 1e15:
formatted = str(int(num))
else:
formatted = str(num)
if fmt.thousands_sep:
# Insert thousands separator
parts = formatted.split(".")
parts[0] = f"{int(parts[0]):,}"
formatted = ".".join(parts)
return formatted
return str(self.value)
[docs]
def copy(self) -> Cell:
"""Return a copy of this cell (same position, same content)."""
return Cell(
row=self.row,
col=self.col,
formula=self.formula,
value=self.value,
display=self.display,
fmt=self.fmt.copy(),
locked=self.locked,
comment=self.comment,
history=list(self.history),
)