You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cg/src/cg/06-textEditor.cg

294 lines
9.8 KiB
Plaintext

textEditorBuffers = [];
textEditor = name \
# defaults = {};
# c = { ...defaults, ...config };
scale = 2;
charH = 12;
charW = 5;
lineGap = 1;
charGap = 2;
lineH = charH * scale + lineGap;
buffersKey = "textEditorBuffers";
# load from staging buffers if it exists there. if not, load from source
source = getAt [buffersKey, name]
| None \ getSource name
| Some v \ v;
lines = split "\n" source;
write = state \
content = join "\n" state.lines;
{ state = state, emit = [rebindAt [buffersKey, name] content] };
apply = state \
content = name & " = " & (join "\n" state.lines) & ";";
result = eval! content;
_ = debug! "apply" [content, result];
result
| Defined _ \ { state = state, emit = [] }
| Err msg \ (
_ = debug! "error applying" [];
{ state = state, emit = [] }
)
| _ \ { state = state, emit = [] };
insertChar = char state \
newLines = updateAt state.cursorRow (line \
insertCharAt line state.cursorCol char
) state.lines;
{ state = state.{ lines = newLines, cursorCol = state.cursorCol + 1 }, emit = [] };
insertMode = state \
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
newUndoStack = [stateSnapshot, ...state.undoStack];
{
state = state.{
undoStack = newUndoStack,
mode = Insert
},
emit = []
};
escape = state \ { state = state.{ mode = Normal }, emit = [] };
undo = state \ state.undoStack
| [] \ { state = state, emit = [] }
| [prev, ...rest] \ (
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
{ state = state.{ lines = prev.lines, cursorCol = prev.cursorCol, cursorRow = prev.cursorRow, undoStack = rest, redoStack = [stateSnapshot, ...state.redoStack] }, emit = [] });
redo = state \ state.redoStack
| [] \ { state = state, emit = [] }
| [prev, ...rest] \ (
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
{ state = state.{ lines = prev.lines, cursorCol = prev.cursorCol, cursorRow = prev.cursorRow, undoStack = [stateSnapshot, ...state.undoStack], redoStack = rest }, emit = [] });
backspace = state \
state.cursorCol == 0
| True \ (
state.cursorRow == 0
| True \ { state = state, emit = [] }
| False \ (
{ state = state, emit = [] } # todo, join with previous line
)
)
| False \ (
newLines = updateAt state.cursorRow (line \
before = slice line 0 (state.cursorCol - 1);
after = slice line state.cursorCol (len line);
before & after) state.lines;
{ state = state.{ lines = newLines, cursorCol = state.cursorCol - 1 }, emit = [] });
enter = state \
line = nth state.cursorRow state.lines ~ unwrapOr "";
before = slice line 0 state.cursorCol;
after = slice line state.cursorCol (len line);
newLines = updateAt state.cursorRow (_ \ before) state.lines;
newLines2 = insertAt (state.cursorRow + 1) after newLines;
{ state = state.{ lines = newLines2, cursorCol = 0, cursorRow = state.cursorRow + 1 }, emit = [] };
clampCursor = state \
line = nth state.cursorRow state.lines ~ unwrapOr "";
newRow = max 0 state.cursorRow;
newRow2 = min (len state.lines - 1) newRow;
maxCol = state.mode
| Insert \ len line
| Normal \ max 0 (len line - 1);
newCol2 = min maxCol (max 0 state.cursorCol);
state.{ cursorRow = newRow2, cursorCol = newCol2 };
upArrow = state \
newState = clampCursor state.{ cursorRow = state.cursorRow - 1 };
{ state = newState, emit = [] };
downArrow = state \
newState = clampCursor state.{ cursorRow = state.cursorRow + 1 };
{ state = newState, emit = [] };
leftArrow = state \
newState = clampCursor state.{ cursorCol = state.cursorCol - 1 };
{ state = newState, emit = [] };
rightArrow = state \
newState = clampCursor state.{ cursorCol = state.cursorCol + 1 };
{ state = newState, emit = [] };
{
view = ctx \
autoScroll = state \
cursorY = state.cursorRow * lineH;
newScrollY = (cursorY < state.scrollY
| True \ cursorY
| False \ (cursorY + lineH > state.scrollY + ctx.h
| True \ cursorY + lineH - ctx.h
| False \ state.scrollY));
cursorX = state.cursorCol * charW * scale + charGap * state.cursorCol;
newScrollX = (cursorX < state.scrollX
| True \ cursorX
| False \ (cursorX + charW * scale > state.scrollX + ctx.w
| True \ cursorX + charW * scale - ctx.w
| False \ state.scrollX));
state.{ scrollY = newScrollY, scrollX = newScrollX };
withScroll = result \
{ state = autoScroll result.state, emit = result.emit };
scrollHalfUp = state ctx \ (
diff = floor ((ctx.h / 2) / lineH);
newRow = state.cursorRow - diff;
withScroll { state = clampCursor state.{ cursorRow = newRow }, emit = [] });
scrollHalfDown = state ctx \ (
diff = floor ((ctx.h / 2) / lineH);
newRow = state.cursorRow + diff;
withScroll { state = clampCursor state.{ cursorRow = newRow }, emit = [] });
ui.stateful {
focusable = True,
autoFocus = True,
key = "textEditor-" & name,
init = {
lines = lines,
cursorRow = 0,
cursorCol = 0,
scrollX = 0,
scrollY = 0,
undoStack = [],
redoStack = [],
mode = Normal # Normal | Insert | Visual
},
update = state event \ event
| Scrolled delta \ (
maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines;
totalW = maxLineLen * charW * scale + maxLineLen * charGap;
totalH = len state.lines * lineH;
newX = max 0 (min (totalW - ctx.w) (state.scrollX + delta.deltaX));
newY = max 0 (min (totalH - ctx.h) (state.scrollY + delta.deltaY));
{ state = state.{ scrollY = newY, scrollX = newX }, emit = [] })
| Key { key = "ArrowDown" } \ withScroll (downArrow state)
| Key { key = "j" } \ (state.mode
| Insert \ insertChar "j" state
| Normal \ withScroll (downArrow state))
| Key { key = "ArrowUp" } \ withScroll (upArrow state)
| Key { key = "k" } \ (state.mode
| Insert \ insertChar "k" state
| Normal \ withScroll(upArrow state))
| Key { key = "ArrowLeft" } \ withScroll (leftArrow state)
| Key { key = "h" } \ (state.mode
| Insert \ insertChar "h" state
| Normal \ withScroll(leftArrow state))
| Key { key = "ArrowRight" } \ withScroll (rightArrow state)
| Key { key = "l" } \ (state.mode
| Insert \ insertChar "l" state
| Normal \ withScroll(rightArrow state))
| Key { key = "i" } \ (state.mode
| Insert \ insertChar "i" state
| Normal \ insertMode state)
| Key { key = "d", ctrl = True } \ scrollHalfDown state ctx
| Key { key = "u", ctrl = True } \ scrollHalfUp state ctx
| Key { key = "u" } \ (state.mode
| Insert \ insertChar "u" state
| Normal \ undo state)
| Key { key = "r", ctrl = True } \ (state.mode
| Insert \ { state = state, emit = [] }
| Normal \ redo state)
| Key { key = "W", shift = True } \ (state.mode
| Insert \ insertChar "W" state
| Normal \ write state)
| Key { key = "W", ctrl = True, shift = True } \ (state.mode
| Insert \ insertChar "W" state
| Normal \ write state)
| Key { key = "A", ctrl = True, shift = True } \ (state.mode
| Insert \ insertChar "A" state
| Normal \ apply state)
| Key { key = "Escape" } \ escape state
| Key { key = "Backspace" } \ (state.mode
| Insert \ backspace state
| _ \ { state = state, emit = [] })
| Key { key = "Enter" } \ (state.mode
| Insert \ enter state
| _ \ { state = state, emit = [] })
# any other key
| Key { key = key, printable = True } \ (state.mode
| Insert \ insertChar key state
| _ \ { state = state, emit = [] })
| _ \ { state = state, emit = [] },
view = state emit \
cursorX = state.cursorCol * charW * scale + charGap * state.cursorCol;
cursorY = state.cursorRow * lineH;
cursor = ui.positioned {
x = cursorX,
y = cursorY,
child = ui.rect { w = charW * scale, h = charH * scale, color = "rgba(255,255,255,0.5)" }
};
maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines;
totalWidth = maxLineLen * charW * scale + maxLineLen * charGap;
totalHeight = len state.lines * lineH;
# buffer = map (l \ text l) state.lines;
# only render visible lines
startLine = max 0 (floor (state.scrollY / lineH));
visibleCount = floor (ctx.h / lineH) + 2;
endLine = min (len state.lines) (startLine + visibleCount);
visibleLines = slice state.lines startLine endLine;
buffer = mapWithIndex (l i \
ui.positioned {
x = 0,
y = (startLine + i) * lineH,
child = text l
}
) visibleLines;
scrollable {
w = ctx.w,
h = ctx.h,
totalWidth = totalWidth,
totalHeight = totalHeight,
scrollX = state.scrollX,
scrollY = state.scrollY,
onScroll = delta \ emit (Scrolled delta),
child = ui.stack {
children = [
...buffer,
cursor
]
}
}
}
};