mirror of
https://git.srhtnin.garden/sarah/SaraView.git
synced 2026-10-15 02:43:17 +02:00
Client: variables resolved in the live editor and reading view
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
eec8fd4b79
commit
ee904540c6
7 files changed
+169
-16
No files matched your search
+24
-9
@@ -9,7 +9,9 @@ list, table or quote). The text between blocks (blank lines) is kept
|
||||
byte for byte, so editing one block never reformats the rest of the file.
|
||||
A ```mermaid block shows as its diagram when MakeDiagram gives one (Diagrams.py),
|
||||
and the properties block at the top as its panel (Properties.py). Checkboxes
|
||||
(tasks and true/false properties) tick without opening their block."""
|
||||
(tasks and true/false properties) tick without opening their block. With
|
||||
GetSpans, text and headings show their variables' results (Variables.py);
|
||||
opening a block shows what's written."""
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
@@ -20,6 +22,7 @@ import flet as ft
|
||||
import Core
|
||||
from Core import CodeStyleSheet, MarkdownCodeTheme, MarkdownSheet, Palette
|
||||
from Properties import ParseProperties, PropertiesView, ToggleLine, WithCheckboxes
|
||||
from Variables import Substitute
|
||||
|
||||
FenceStart = re.compile(r"^ {0,3}(`{3,}|~{3,})")
|
||||
HeadingLine = re.compile(r"^ {0,3}(#{1,6})(\s|$)")
|
||||
@@ -113,7 +116,8 @@ def SplitBlocks(Document: str) -> list[Block]:
|
||||
class LiveMarkdown(ft.Column):
|
||||
def __init__(self, Document: str, FontSize: int, OnChange: Callable[[], None],
|
||||
OnFocusChange: Callable[[bool], None], OnTapLink: Callable,
|
||||
MakeDiagram: Optional[Callable[[str, int], Optional[ft.Control]]] = None):
|
||||
MakeDiagram: Optional[Callable[[str, int], Optional[ft.Control]]] = None,
|
||||
GetSpans: Optional[Callable[[str], list[dict]]] = None):
|
||||
super().__init__(scroll=ft.ScrollMode.AUTO, expand=True, spacing=0,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.STRETCH)
|
||||
self.Document = Document
|
||||
@@ -122,11 +126,13 @@ class LiveMarkdown(ft.Column):
|
||||
self.OnFocusChange = OnFocusChange
|
||||
self.OnTapLink = OnTapLink
|
||||
self.MakeDiagram = MakeDiagram
|
||||
self.GetSpans = GetSpans
|
||||
self.SpansFor: tuple[str, list[dict]] = ("", []) # (document, its spans)
|
||||
self.Blocks: list[Block] = []
|
||||
self.BlockControls: list[ft.Control] = []
|
||||
# Rendered blocks by their text, reused when the layout is rebuilt so
|
||||
# only the edited part is rendered again.
|
||||
self.RenderCache: dict[tuple[str, str], list[ft.Control]] = {}
|
||||
self.RenderCache: dict[tuple[str, str], list[ft.Control]] = {} # by (kind, text as shown)
|
||||
self.EditingIndex: Optional[int] = None # len(Blocks) is the new block at the end
|
||||
self.Field: Optional[ft.TextField] = None
|
||||
self._Layout(Update=False)
|
||||
@@ -239,7 +245,14 @@ class LiveMarkdown(ft.Column):
|
||||
on_tap_link=self.OnTapLink,
|
||||
)
|
||||
|
||||
def _Render(self, Text: str, Kind: str) -> ft.Control:
|
||||
def _Spans(self) -> list[dict]:
|
||||
if self.GetSpans is None:
|
||||
return []
|
||||
if self.SpansFor[0] != self.Document:
|
||||
self.SpansFor = (self.Document, self.GetSpans(self.Document))
|
||||
return self.SpansFor[1]
|
||||
|
||||
def _Render(self, Text: str, Kind: str, Shown: Optional[str] = None, LineMap: Optional[list] = None) -> ft.Control:
|
||||
Rendered = ft.Container(padding=ft.Padding.symmetric(horizontal=8, vertical=6), border_radius=4)
|
||||
# Lines are counted from the block's start, found again when ticked:
|
||||
# a rendered block is reused wherever its text ends up (RenderCache).
|
||||
@@ -250,10 +263,10 @@ class LiveMarkdown(ft.Column):
|
||||
elif Kind == "Properties" and (Found := ParseProperties(Text)) is not None:
|
||||
Rendered.content = PropertiesView(Found, self.FontSize, self._Markdown, Toggle)
|
||||
elif Kind == "Text":
|
||||
Parts = WithCheckboxes(Text, 0, self._Markdown, Toggle, self.FontSize)
|
||||
Parts = WithCheckboxes(Shown if Shown is not None else Text, 0, self._Markdown, Toggle, self.FontSize, LineMap)
|
||||
Rendered.content = Parts[0] if len(Parts) == 1 else ft.Column(Parts, spacing=0, tight=True)
|
||||
else:
|
||||
Rendered.content = self._Markdown(Text)
|
||||
Rendered.content = self._Markdown(Shown if Shown is not None and Kind == "Heading" else Text)
|
||||
Rendered.on_click = lambda e, C=Rendered: self._OnBlockClicked(C)
|
||||
Rendered.on_hover = lambda e, C=Rendered: self._OnHover(C, e)
|
||||
return Rendered
|
||||
@@ -276,6 +289,7 @@ class LiveMarkdown(ft.Column):
|
||||
|
||||
def _Layout(self, Update: bool = True) -> None:
|
||||
self.Blocks = SplitBlocks(self.Document)
|
||||
Spans = self._Spans()
|
||||
Cache = self.RenderCache
|
||||
self.RenderCache = {}
|
||||
self.BlockControls = []
|
||||
@@ -284,11 +298,12 @@ class LiveMarkdown(ft.Column):
|
||||
self.BlockControls.append(self.Field)
|
||||
continue
|
||||
Text = self.Document[Item.Start:Item.End]
|
||||
Reused = Cache.get((Item.Kind, Text))
|
||||
Control = Reused.pop() if Reused else self._Render(Text, Item.Kind)
|
||||
Shown, LineMap = Substitute(Text, Item.Start, Spans) if Item.Kind in ("Text", "Heading") else (Text, None)
|
||||
Reused = Cache.get((Item.Kind, Shown))
|
||||
Control = Reused.pop() if Reused else self._Render(Text, Item.Kind, Shown, LineMap)
|
||||
Control.bgcolor = None
|
||||
self.BlockControls.append(Control)
|
||||
self.RenderCache.setdefault((Item.Kind, Text), []).append(Control)
|
||||
self.RenderCache.setdefault((Item.Kind, Shown), []).append(Control)
|
||||
Controls = list(self.BlockControls)
|
||||
if self.EditingIndex == len(self.Blocks):
|
||||
Controls.append(self.Field)
|
||||
|
||||
Reference in new issue
Block a user