1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
from functools import wraps
from typing import Callable
from .selection import Selection
from .state import EditorState, Result, StateChange
class StateMonad:
def __init__(self, run_fn: Callable[[EditorState], Result]):
self._run_fn = run_fn
def run(self, state: EditorState) -> Result:
return self._run_fn(state)
def commit(self, state: EditorState) -> tuple[Selection, EditorState]:
sel, changes = self.run(state)
state.commit_changes(changes)
return (sel, state)
def bind(self, f: Callable[[Selection], "StateMonad"]) -> "StateMonad":
def run_bound(state: EditorState) -> Result:
sel1, changes1 = self.run(state)
expr2 = f(sel1)
sel2, changes2 = expr2.run(state)
return (sel2, changes1 + changes2)
return StateMonad(run_bound)
@staticmethod
def pure(selection: Selection) -> "StateMonad":
def run_pure(_: EditorState) -> Result:
return (selection, [])
return StateMonad(run_pure)
def modify(change: StateChange, selection: Selection) -> "StateMonad":
def run_modify(_: EditorState) -> Result:
return (selection, [change])
return StateMonad(run_modify)
def monadic(
func: Callable[[Selection, EditorState], Result],
) -> Callable[[Selection | StateMonad], StateMonad]:
def make_monad(sel: Selection) -> StateMonad:
def run_fn(state: EditorState) -> Result:
return func(sel, state)
return StateMonad(run_fn)
@wraps(func)
def wrapper(arg: Selection | StateMonad) -> StateMonad:
if isinstance(arg, StateMonad):
return arg.bind(make_monad)
else:
return make_monad(arg)
return wrapper
def group(*exprs: StateMonad) -> StateMonad:
def run_grouped(state: EditorState) -> Result:
all_selections = []
all_changes = []
for expr in exprs:
sel, changes = expr.run(state)
all_selections.append(sel)
all_changes.extend(changes)
# TODO: Check for any overlapping changes
# TODO: Merge selections into union
return (
all_selections[0] if all_selections else state.current_selection,
all_changes,
)
return StateMonad(run_grouped)
|