Added "table" and "tables" in the DSL

This commit is contained in:
2026-02-07 22:48:51 +01:00
parent 08c8c00e28
commit 6160e91665
18 changed files with 717 additions and 54 deletions

View File

@@ -23,7 +23,7 @@ Example:
"""
from .parser import get_parser
from .transformer import DSLTransformer
from .scopes import ColumnScope, RowScope, CellScope, ScopedRule
from .scopes import ColumnScope, RowScope, CellScope, TableScope, TablesScope, ScopedRule
from .exceptions import DSLError, DSLSyntaxError, DSLValidationError
@@ -61,6 +61,8 @@ __all__ = [
"ColumnScope",
"RowScope",
"CellScope",
"TableScope",
"TablesScope",
"ScopedRule",
# Exceptions
"DSLError",

View File

@@ -105,7 +105,13 @@ class FormattingCompletionEngine(BaseCompletionEngine):
case Context.CELL_ROW:
return self._get_row_index_suggestions()
case Context.TABLE_NAME:
return self._get_table_name_suggestion()
case Context.TABLES_SCOPE:
return [Suggestion(":", "Define global rules for all tables", "syntax")]
# =================================================================
# Rule-level contexts
# =================================================================
@@ -230,7 +236,13 @@ class FormattingCompletionEngine(BaseCompletionEngine):
except Exception:
pass
return []
def _get_table_name_suggestion(self) -> list[Suggestion]:
"""Get table name suggestion (current table only)."""
if self.table_name:
return [Suggestion(f'"{self.table_name}"', f"Current table: {self.table_name}", "table")]
return []
def _get_style_preset_suggestions(self) -> list[Suggestion]:
"""Get style preset suggestions (without quotes)."""
suggestions = []

View File

@@ -25,12 +25,14 @@ class Context(Enum):
NONE = auto()
# Scope-level contexts
SCOPE_KEYWORD = auto() # Start of non-indented line: column, row, cell
SCOPE_KEYWORD = auto() # Start of non-indented line: column, row, cell, table, tables
COLUMN_NAME = auto() # After "column ": column names
ROW_INDEX = auto() # After "row ": row indices
CELL_START = auto() # After "cell ": (
CELL_COLUMN = auto() # After "cell (": column names
CELL_ROW = auto() # After "cell (col, ": row indices
TABLE_NAME = auto() # After "table ": table name
TABLES_SCOPE = auto() # After "tables": colon
# Rule-level contexts
RULE_START = auto() # Start of indented line: style(, format(, format.
@@ -76,10 +78,10 @@ class DetectedScope:
Represents the detected scope from scanning previous lines.
Attributes:
scope_type: "column", "row", "cell", or None
scope_type: "column", "row", "cell", "table", "tables", or None
column_name: Column name (for column and cell scopes)
row_index: Row index (for row and cell scopes)
table_name: DataGrid name (if determinable)
table_name: Table name (for table scope) or DataGrid name
"""
scope_type: str | None = None
@@ -92,7 +94,7 @@ def detect_scope(text: str, current_line: int) -> DetectedScope:
"""
Detect the current scope by scanning backwards from the cursor line.
Looks for the most recent scope declaration (column/row/cell)
Looks for the most recent scope declaration (column/row/cell/table/tables)
that is not indented.
Args:
@@ -138,6 +140,17 @@ def detect_scope(text: str, current_line: int) -> DetectedScope:
return DetectedScope(
scope_type="cell", column_name=column_name, row_index=row_index
)
# Check for table scope
match = re.match(r'^table\s+"([^"]+)"\s*:', line)
if match:
table_name = match.group(1)
return DetectedScope(scope_type="table", table_name=table_name)
# Check for tables scope
match = re.match(r"^tables\s*:", line)
if match:
return DetectedScope(scope_type="tables")
return DetectedScope()
@@ -192,6 +205,14 @@ def detect_context(text: str, cursor: Position, scope: DetectedScope) -> Context
if re.match(r'^cell\s+\(\s*(?:"[^"]*"|[a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*$', line_to_cursor):
return Context.CELL_ROW
# After "table "
if re.match(r"^table\s+", line_to_cursor) and not line_to_cursor.rstrip().endswith(":"):
return Context.TABLE_NAME
# After "tables"
if re.match(r"^tables\s*$", line_to_cursor):
return Context.TABLES_SCOPE
# Start of line or partial keyword
return Context.SCOPE_KEYWORD

View File

@@ -120,6 +120,8 @@ SCOPE_KEYWORDS: list[Suggestion] = [
Suggestion("column", "Define column scope", "keyword"),
Suggestion("row", "Define row scope", "keyword"),
Suggestion("cell", "Define cell scope", "keyword"),
Suggestion("table", "Define table scope", "keyword"),
Suggestion("tables", "Define global scope for all tables", "keyword"),
]
# =============================================================================

View File

@@ -16,10 +16,14 @@ GRAMMAR = r"""
scope_header: column_scope
| row_scope
| cell_scope
| table_scope
| tables_scope
column_scope: "column" column_name
row_scope: "row" INTEGER
cell_scope: "cell" cell_ref
table_scope: "table" QUOTED_STRING
tables_scope: "tables"
column_name: NAME -> name
| QUOTED_STRING -> quoted_name

View File

@@ -64,12 +64,18 @@ class DSLParser:
lines = text.split("\n")
lines = ["" if line.strip().startswith("#") else line for line in lines]
text = "\n".join(lines)
# Strip leading whitespace/newlines and ensure text ends with newline
text = text.strip()
if text and not text.endswith("\n"):
# Handle empty text (return empty tree that will transform to empty list)
if not text:
from lark import Tree
return Tree('start', [])
if not text.endswith("\n"):
text += "\n"
try:
return self._parser.parse(text)
except UnexpectedInput as e:

View File

@@ -32,6 +32,18 @@ class CellScope:
cell_id: str = None
@dataclass
class TableScope:
"""Scope targeting a specific table by name."""
table: str
@dataclass
class TablesScope:
"""Scope targeting all tables (global)."""
pass
@dataclass
class ScopedRule:
"""
@@ -40,8 +52,8 @@ class ScopedRule:
The DSL parser returns a list of ScopedRule objects.
Attributes:
scope: Where the rule applies (ColumnScope, RowScope, or CellScope)
scope: Where the rule applies (ColumnScope, RowScope, CellScope, TableScope, or TablesScope)
rule: The FormatRule (condition + style + formatter)
"""
scope: ColumnScope | RowScope | CellScope
scope: ColumnScope | RowScope | CellScope | TableScope | TablesScope
rule: FormatRule

View File

@@ -6,7 +6,7 @@ Converts lark AST into FormatRule and ScopedRule objects.
from lark import Transformer
from .exceptions import DSLValidationError
from .scopes import ColumnScope, RowScope, CellScope, ScopedRule
from .scopes import ColumnScope, RowScope, CellScope, TableScope, TablesScope, ScopedRule
from ..dataclasses import (
Condition,
Style,
@@ -67,7 +67,14 @@ class DSLTransformer(Transformer):
def cell_id(self, items):
cell_id = str(items[0])
return CellScope(cell_id=cell_id)
def table_scope(self, items):
table_name = self._unquote(items[0])
return TableScope(table=table_name)
def tables_scope(self, items):
return TablesScope()
def name(self, items):
return str(items[0])

View File

@@ -202,12 +202,17 @@ class InstancesManager:
return default
@staticmethod
def get_by_type(session: dict, cls: type):
def get_by_type(session: dict, cls: type, default=NO_DEFAULT_VALUE):
session_id = InstancesManager.get_session_id(session)
res = [i for s, i in InstancesManager.instances.items() if s[0] == session_id and isinstance(i, cls)]
assert len(res) <= 1, f"Multiple instances of type {cls.__name__} found"
assert len(res) > 0, f"No instance of type {cls.__name__} found"
return res[0]
try:
assert len(res) > 0, f"No instance of type {cls.__name__} found"
return res[0]
except AssertionError:
if default is NO_DEFAULT_VALUE:
raise
return default
@staticmethod
def dynamic_get(session, component_parent: tuple, component: tuple):