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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
|
# This makes all keyboards into editors automatically, so a keyboard
# doesn't need to point at a real editor. May choose to change later, or
# exclude keyboards that opt out.
When /k/ is a keyboard with /...opts/ &\
/nobody/ wishes /k/ does not create an editor {
Wish tag $k is stabilized
# Create a synthetic editor above the keyboard page.
set editor [list $k editor]
Claim $editor is an editor
Wish $editor has a canvas
When $k has resolved geometry /geom/ {
Claim $editor has resolved geometry $geom
}
When the quad library is /quadLib/ & $k has quad /q/ {
Claim $editor has quad [$quadLib move $q up 105%]
}
Claim $k has created editor $editor
Claim $k is typing into $editor
}
When /k/ is a keyboard with /...opts/ &\
/nobody/ claims /k/ has created editor /any/ &\
/k/ points up at /editor/ & /editor/ is an editor with /...opts/ {
Claim $k is typing into $editor
}
When the program save directory is /programDir/ &\
the editor utils library is /utils/ {
# TODO: also don't hardcode this?
set margin [list 0.01 0.005 0.005 0.01] ;# CSS order (top, right, bottom, left)
set defaults { textScale 0.01 }
set editorLib [library create editorLib {margin defaults} {
proc getAdvance {em} {
# From NeomatrixCode.csv
return $(0.5859375 * $em)
}
proc widthAndHeight {resolvedGeom} {
set tagSize [dict get $resolvedGeom tagSize]
set left [dict get $resolvedGeom left]
set right [dict get $resolvedGeom right]
set top [dict get $resolvedGeom top]
set bottom [dict get $resolvedGeom bottom]
set width $($left + $tagSize + $right)
set height $($top + $tagSize + $bottom)
return [list $width $height]
}
# given program and the editor options, figure out how many characters can
# fit in this editor
proc editorSizeInCharacters {resolvedGeom options} {
variable margin
set textScale [dict get $options scale]
set advance [getAdvance $textScale]
lassign [widthAndHeight $resolvedGeom] width height
set width $($width - [lindex $margin 3] - $advance*2.5 - [lindex $margin 1])
set height $($height - [lindex $margin 0] - [lindex $margin 2])
set widthInCharacters $(int($width / $advance))
set heightInCharacters $(int($height / $textScale))
return [list $widthInCharacters $heightInCharacters]
}
}]
When /someone/ claims /editor/ is an editor {
Claim $editor is an editor with {*}$defaults
}
When /editor/ is an editor with /...options/ {
# HACK: because we partial match
if {![info exists options]} { return }
set options [dict merge $defaults $options]
Wish tag $editor is stabilized
# Select which program the editor is viewing
When $editor points up with length 0.3 at /program/ {
Claim editor $editor has selected program $program
}
# Initial setup
# Load initial text settings if not set
When /nobody/ claims editor $editor has font options /...anything/ &\
the saved holds are loaded {
# Wait until all the saved holds are loaded, so we don't accidentally
# overwrite a saved setting.
set textScale [dict get $options textScale]
Hold! -save -key font-options-of:$editor \
Claim editor $editor has font options with scale $textScale
}
# We need the editor's resolved geometry to get its size.
# We then use its size to figure out how many characters we can fit in it,
# width-wise and height-wise.
When $editor has resolved geometry /geom/ &\
editor $editor has font options with /...options/ {
Claim editor $editor has viewport size [$editorLib editorSizeInCharacters $geom $options]
}
# Load in defaults for the editor if it hasn't been initialized
set results [Query! /somebody/ claims editor $editor has cursor /anything/]
if {[llength $results] == 0} {
Hold! -key editor-state-of:$editor {
Claim editor $editor has cursor 0
Claim editor $editor has max cursor x 0
Claim editor $editor has viewport position [list 0 0]
Claim editor $editor has selection anchor ""
Claim editor $editor has undo stack {}
Claim editor $editor has redo stack {}
Claim editor $editor has last edit type ""
}
}
set clipResults [Query! /somebody/ claims editor $editor has clipboard /anything/]
if {[llength $clipResults] == 0} {
Hold! -key clipboard-of:$editor Claim editor $editor has clipboard ""
}
# Load in initial program code (note, this buffer is shared between
# all editor instances)
When editor $editor has selected program /program/ &\
/program/ has program code /programCode/ &\
the saved holds are loaded {
# Check if the code already exists.
set results [Query! editor buffer for $program is /anything/]
if {[llength $results] == 0} {
Hold! -save -key buffer-for:$program \
Claim editor buffer for $program is $programCode
}
}
# Feedback: show that the editor is not active
When /nobody/ wishes $editor is outlined green {
Wish $editor is outlined blue
}
}
Subscribe: keyboard /keyboard/ claims key Control_b is down with /...options/ {
Notify: print code "# blank"
}
When /keyboard/ is a keyboard with path /kbPath/ /...anything/ &\
/keyboard/ is typing into /editor/ &\
/editor/ is an editor with /...anything/ &\
editor /editor/ has selected program /program/ {
Wish $editor is outlined green
Subscribe: keyboard $kbPath claims key /key/ is /keyState/ with /...options/ {
ForEach! editor $editor has viewport position /vpPos/ &\
editor $editor has viewport size /vpSize/ &\
editor buffer for $program is /code/ &\
editor $editor has max cursor x /maxCursorX/ &\
editor $editor has cursor /cursor/ &\
editor $editor has selection anchor /selAnchor/ &\
editor $editor has clipboard /clipboard/ &\
editor $editor has undo stack /undoStack/ &\
editor $editor has redo stack /redoStack/ &\
editor $editor has last edit type /lastEditType/ &\
editor $editor has font options with /...textOptions/ {
lassign $vpPos vpX vpY
lassign $vpSize vpWidth vpHeight
if {$keyState == "up"} { return }
# if this is true, the buffer will remove the hold for program code
# (triggering a reinitialization)
set resetBuffer false
set isUndo false
set isRedo false
set origCode $code
set origCursor $cursor
set origMaxCursorX $maxCursorX
set hasShift [dict exists $options shift]
set isNavKey [expr {$key eq "Left" || $key eq "Right" || $key eq "Up" || $key eq "Down"}]
# Handle selection replacement for text-modifying keys
set selectionHandled false
if {$selAnchor ne ""} {
set selStart [min $selAnchor $cursor]
set selEnd [max $selAnchor $cursor]
if {[dict exists $options printable]} {
lassign [$utils replaceRange $code $selStart $selEnd [dict get $options printable]] code cursor maxCursorX
set selAnchor ""
set selectionHandled true
} elseif {$key eq "Delete" || $key eq "Remove"} {
lassign [$utils replaceRange $code $selStart $selEnd ""] code cursor maxCursorX
set selAnchor ""
set selectionHandled true
} elseif {$key eq "Return"} {
# Delete selection, then fall through to Return handler
lassign [$utils replaceRange $code $selStart $selEnd ""] code cursor maxCursorX
set selAnchor ""
}
}
if {!$selectionHandled} {
if {$hasShift && $isNavKey} {
# Shift+Arrow: extend selection
if {$selAnchor eq ""} { set selAnchor $cursor }
lassign [$utils handleNavigation $key $code $cursor $maxCursorX] cursor maxCursorX
} elseif {[dict exists $options printable]} {
lassign [$utils insertText $code $cursor [dict get $options printable]] code cursor maxCursorX
} else {
# Regular navigation clears selection
if {$isNavKey} { set selAnchor "" }
# general editor functionality
lassign [$utils handleNavigation $key $code $cursor $maxCursorX] cursor maxCursorX
lassign [$utils handleRemovalAndReturn $key $code $cursor $maxCursorX] code cursor maxCursorX
# specific editor functionality
switch $key {
Control_c {
if {$selAnchor ne ""} {
set clipText [$utils getSelectedText $code $selAnchor $cursor]
Hold! -key clipboard-of:$editor Claim editor $editor has clipboard $clipText
}
}
Control_x {
if {$selAnchor ne ""} {
set clipText [$utils getSelectedText $code $selAnchor $cursor]
Hold! -key clipboard-of:$editor Claim editor $editor has clipboard $clipText
set selStart [min $selAnchor $cursor]
set selEnd [max $selAnchor $cursor]
lassign [$utils replaceRange $code $selStart $selEnd ""] code cursor maxCursorX
set selAnchor ""
}
}
Control_v {
if {$clipboard ne ""} {
if {$selAnchor ne ""} {
set selStart [min $selAnchor $cursor]
set selEnd [max $selAnchor $cursor]
lassign [$utils replaceRange $code $selStart $selEnd $clipboard] code cursor maxCursorX
set selAnchor ""
} else {
lassign [$utils replaceRange $code $cursor $cursor $clipboard] code cursor maxCursorX
}
}
}
Control_z {
if {[llength $undoStack] > 0} {
lappend redoStack [list $code $cursor $maxCursorX]
lassign [lindex $undoStack end] code cursor maxCursorX
set undoStack [lrange $undoStack 0 end-1]
set selAnchor ""
set isUndo true
set lastEditType ""
}
}
Control_y {
if {[llength $redoStack] > 0} {
lappend undoStack [list $code $cursor $maxCursorX]
lassign [lindex $redoStack end] code cursor maxCursorX
set redoStack [lrange $redoStack 0 end-1]
set selAnchor ""
set isRedo true
set lastEditType ""
}
}
Control_r {
set resetBuffer true
}
Control_s {
Notify: save code on editor $editor
}
Control_p {
Notify: print code $code
# Give the user some feedback
Hold! -key printing-alert:$editor \
Wish $editor is labelled "Printing!"
sleep 0.25
Hold! -key printing-alert:$editor {}
}
Control_underscore {
# ctrl and - (zoom out)
set textScale [dict get $textOptions scale]
set textScale [/ $textScale 1.1]
Hold! -save -key font-options-of:$editor \
Claim editor $editor has font options with scale $textScale
}
equal {
# Dunno why it registers as equal instead of Control_equal? It works regardless, lol
# ctrl and + (zoom in)
set textScale [dict get $textOptions scale]
set textScale [* $textScale 1.1]
Hold! -save -key font-options-of:$editor \
Claim editor $editor has font options with scale $textScale
}
}
}
}
# Undo stack management with coalescing
if {$code ne $origCode && !$isUndo && !$isRedo} {
# Determine edit type for coalescing consecutive same-type edits
if {[dict exists $options printable]} {
set editType "insert"
} elseif {$key eq "Delete" || $key eq "Remove"} {
set editType "delete"
} else {
set editType "other"
}
# Only push when edit type changes or for non-coalescable edits
if {$editType ne $lastEditType || $editType eq "other"} {
lappend undoStack [list $origCode $origCursor $origMaxCursorX]
if {[llength $undoStack] > 50} {
set undoStack [lrange $undoStack end-49 end]
}
}
set lastEditType $editType
# Any real edit clears the redo stack
set redoStack {}
} elseif {!$isUndo && !$isRedo} {
# Navigation or no-op resets edit type (breaks coalescing)
set lastEditType ""
}
if {$resetBuffer} {
set cursor 0
set maxCursorX 0
set selAnchor ""
set undoStack {}
set redoStack {}
set lastEditType ""
# remove the edited code to restore the program to its original code
Hold! -on builtin-programs/programs.folk -key new-code-for:$program {}
file delete "$programDir/[regsub {\.folk$} $program {}].folk.edited"
Hold! -save -key buffer-for:$program {
When $program has program code /originalCode/ {
Claim editor buffer for $program is $originalCode
}
}
} else {
Hold! -save -key buffer-for:$program \
Claim editor buffer for $program is $code
}
lassign [$utils cursorToXy $code $cursor] cursorX cursorY
# Be sure to have at least two characters on the left, so we can
# see what we're removing.
if {[- $cursorX 2] < $vpX} {
set vpX [max 0 [- $cursorX 2]]
}
if {$cursorX >= $vpX + $vpWidth} {
set vpX $($cursorX - $vpWidth)
}
if {$cursorY < $vpY} { set vpY $cursorY }
if {$cursorY >= $vpY + $vpHeight - 1} {
set vpY $($cursorY - $vpHeight + 1)
}
Hold! -keep 12ms -key editor-state-of:$editor {
Claim editor $editor has viewport position [list $vpX $vpY]
Claim editor $editor has cursor $cursor
Claim editor $editor has max cursor x $maxCursorX
Claim editor $editor has selection anchor $selAnchor
Claim editor $editor has undo stack $undoStack
Claim editor $editor has redo stack $redoStack
Claim editor $editor has last edit type $lastEditType
}
}
}
}
Subscribe: save code on editor /editor/ {
ForEach! editor $editor has selected program /program/ &\
editor buffer for /program/ is /programCode/ {
Hold! -on builtin-programs/programs.folk -key new-code-for:$program \
Wish program $program is replaced with \
code $programCode editedTime [clock seconds]
set programBase [regsub {\.folk$} $program ""]
set editedPath "$programDir/[set programBase].folk.edited"
file mkdir [file dirname $editedPath]
set fp [open $editedPath w]
puts -nonewline $fp $programCode
close $fp
# Give the user some feedback
Hold! -key saved-alert:$editor \
Wish $editor is labelled "Saved!"
sleep 0.25
Hold! -key saved-alert:$editor {}
}
}
# calculate cursor position
When /editor/ is an editor with /...anything/ &\
editor /editor/ has cursor /cursor/ &\
editor /editor/ has selected program /program/ &\
editor buffer for /program/ is /code/ &\
editor /editor/ has viewport position /vpPos/ &\
editor /editor/ has font options with /...textOptions/ {
lassign $vpPos vpX vpY
lassign [$utils cursorToXy $code $cursor] cursorX cursorY
set textScale [dict get $textOptions scale]
set advance [$editorLib getAdvance $textScale]
set offsetX $(($cursorX - $vpX) * $advance)
set offsetY $(($cursorY - $vpY) * $textScale)
Claim editor $editor has cursor position [list $offsetX $offsetY]
}
# Draw text and cursor
When /editor/ is an editor with /...anything/ &\
editor /editor/ has viewport position /vpPos/ &\
editor /editor/ has viewport size /vpSize/ &\
editor /editor/ has font options with /...fontOptions/ {
lassign $vpPos vpX vpY
lassign $vpSize vpWidth vpHeight
set textScale [dict get $fontOptions scale]
set advance [$editorLib getAdvance $textScale]
When editor $editor has selected program /program/ &\
editor buffer for /program/ is /code/ &\
editor $editor has cursor /cursor/ &\
editor $editor has cursor position /cursorPos/ &\
editor $editor has selection anchor /selAnchor/ {
set lineCount [min [- [llength [split $code "\n"]] $vpY] $vpHeight]
set lineNumbers [$utils lineNumberView $vpY $lineCount]
set marginLeft [lindex $margin 3]
set lineNumbersRight $($marginLeft + $advance*1.5)
Wish to draw text onto $editor with \
position [list $lineNumbersRight [lindex $margin 0]] \
text $lineNumbers \
scale $textScale anchor topright font NeomatrixCode
set text [$utils applyTextViewport $code $vpX $vpY $vpWidth $vpHeight]
set pos [list [+ $lineNumbersRight $advance] [lindex $margin 0]]
Wish to draw text onto $editor with \
position $pos text $text \
scale $textScale anchor topleft font NeomatrixCode
# Draw selection highlight
if {$selAnchor ne ""} {
set rawStart [min $selAnchor $cursor]
set rawEnd [max $selAnchor $cursor]
lassign [$utils cursorToXy $code $rawStart] selStartX selStartY
lassign [$utils cursorToXy $code $rawEnd] selEndX selEndY
set lines [split $code "\n"]
for {set ly $selStartY} {$ly <= $selEndY} {incr ly} {
if {$ly < $vpY || $ly >= $vpY + $vpHeight} continue
set lineLen [string length [lindex $lines $ly]]
# Absolute column range for selection on this line
if {$ly == $selStartY} {
set absStart $selStartX
} else {
set absStart 0
}
if {$ly == $selEndY} {
set absEnd $selEndX
} else {
set absEnd $lineLen
}
# Clip to viewport
set absStart [max $absStart $vpX]
set absEnd [min $absEnd [+ $vpX $vpWidth]]
if {$absStart >= $absEnd} continue
# Convert to display coordinates
set dispStart [- $absStart $vpX]
set dispEnd [- $absEnd $vpX]
set dispRow [- $ly $vpY]
set x0 $([lindex $pos 0] + $dispStart * $advance)
set x1 $([lindex $pos 0] + $dispEnd * $advance)
set y0 $([lindex $pos 1] + $dispRow * $textScale)
set y1 $($y0 + $textScale)
Wish to draw a quad onto $editor with \
p0 [list $x0 $y0] p1 [list $x1 $y0] \
p2 [list $x1 $y1] p3 [list $x0 $y1] \
color {0.2 0.4 0.8 0.7} layer -1
}
}
set p1 [vec2 add $cursorPos $pos]
set p2 [vec2 add $p1 [list 0 [* $textScale 1.2]]]
set s [/ $textScale 6]
Wish to draw a circle onto $editor with center $p1 radius $s thickness 0 color green filled true
Wish to draw a line onto $editor with points [list $p1 $p2] width $s color green
}
}
# end of library code
}
|