]> gitweb.factorcode.org Git - factor.git/blob - basis/ui/gadgets/editors/editors.factor
f846d6cbdb881cefa69dbfb7104f58cc124b6edb
[factor.git] / basis / ui / gadgets / editors / editors.factor
1 ! Copyright (C) 2006, 2011 Slava Pestov
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays assocs calendar colors combinators
4 combinators.short-circuit documents documents.elements fonts fry
5 grouping kernel literals locals make math math.functions
6 math.order ranges math.rectangles math.vectors models
7 models.arrow namespaces opengl opengl.gl sequences sorting
8 splitting system timers ui.baseline-alignment ui.clipboards
9 ui.commands ui.gadgets ui.gadgets.borders
10 ui.gadgets.line-support ui.gadgets.menus ui.gadgets.scrollers
11 prettyprint math.parser
12 ui.gestures ui.pens.solid ui.render ui.text ui.theme unicode variables ;
13 IN: ui.gadgets.editors
14
15 TUPLE: editor < line-gadget
16     caret mark
17     caret-shape
18     focused? blink blink-timer
19     default-text
20     preedit-start
21     preedit-end
22     preedit-selected-start
23     preedit-selected-end
24     preedit-selection-mode?
25     preedit-underlines ;
26
27 M: editor preedit? preedit-start>> ;
28
29 SYMBOLS: +line+ +box+ +filled+ ;
30 GLOBAL: caret-is-shape 
31 +line+ caret-is-shape set-global
32
33 : <caret-shape> ( -- shape )  caret-is-shape get-global <model> ;
34
35 <PRIVATE
36
37 : <loc> ( -- loc ) { 0 0 } <model> ;
38
39 : init-editor-locs ( editor -- editor )
40     <loc> >>caret
41     <caret-shape> >>caret-shape
42     <loc> >>mark ; inline
43
44 : editor-theme ( editor -- editor )
45     monospace-font >>font ; inline
46
47 PRIVATE>
48
49 : new-editor ( class -- editor )
50     new-line-gadget
51         <document> >>model
52         init-editor-locs
53         editor-theme ; inline
54
55 : <editor> ( -- editor )
56     editor new-editor ;
57
58 <PRIVATE
59
60 : activate-editor-model ( editor model -- )
61     [ add-connection ]
62     [ nip activate-model ]
63     [ swap model>> add-loc ] 2tri ;
64
65 : deactivate-editor-model ( editor model -- )
66     [ remove-connection ]
67     [ nip deactivate-model ]
68     [ swap model>> remove-loc ] 2tri ;
69
70 : blink-caret ( editor -- )
71     [ not ] change-blink relayout-1 ;
72
73 SYMBOL: blink-interval
74
75 750 milliseconds blink-interval set-global
76
77 : stop-blinking ( editor -- )
78     blink-timer>> [ stop-timer ] when* ;
79
80 : start-blinking ( editor -- )
81     t >>blink
82     blink-timer>> [ restart-timer ] when* ;
83
84 : restart-blinking ( editor -- )
85     dup focused?>> [
86         [ start-blinking ]
87         [ relayout-1 ]
88         bi
89     ] [ drop ] if ;
90
91 PRIVATE>
92
93 M: editor graft*
94     [ dup caret>> activate-editor-model ]
95     [ dup mark>> activate-editor-model ]
96     [
97         [
98             '[ _ blink-caret ] blink-interval get dup <timer>
99         ] keep blink-timer<<
100     ] tri ;
101
102 M: editor ungraft*
103     [ [ stop-blinking ] [ f >>blink-timer drop ] bi ]
104     [ dup caret>> deactivate-editor-model ]
105     [ dup mark>> deactivate-editor-model ] tri ;
106
107 : editor-caret ( editor -- loc ) caret>> value>> ;
108
109 : editor-mark ( editor -- loc ) mark>> value>> ;
110
111 : set-caret ( loc editor -- )
112     [ model>> validate-loc ] [ caret>> ] bi set-model ;
113
114 : set-mark ( loc editor -- )
115     [ model>> validate-loc ] [ mark>> ] bi set-model ;
116
117 : change-caret ( editor quot: ( loc document -- newloc ) -- )
118     [ [ [ editor-caret ] [ model>> ] bi ] dip call ] [ drop ] 2bi
119     set-caret ; inline
120
121 : mark>caret ( editor -- )
122     [ editor-caret ] [ mark>> ] bi set-model ;
123
124 : change-caret&mark ( editor quot: ( loc document -- newloc ) -- )
125     [ change-caret ] [ drop mark>caret ] 2bi ; inline
126
127 : editor-line ( n editor -- str ) control-value nth ;
128
129 :: point>loc ( point editor -- loc )
130     point second editor y>line {
131         { [ dup 0 < ] [ drop { 0 0 } ] }
132         { [ dup editor model>> last-line# > ] [ drop editor model>> doc-end ] }
133         [| n |
134             n
135             point first
136             editor font>>
137             n editor editor-line
138             x>offset 2array
139         ]
140     } cond ;
141
142 : clicked-loc ( editor -- loc )
143     [ hand-rel ] keep point>loc ;
144
145 : click-loc ( editor model -- )
146     [ clicked-loc ] dip set-model ;
147
148 : focus-editor ( editor -- )
149     [ start-blinking ] [ t >>focused? relayout-1 ] bi ;
150
151 : unfocus-editor ( editor -- )
152     [ stop-blinking ] [ f >>focused? relayout-1 ] bi ;
153
154 : loc>x ( loc editor -- x )
155     [ first2 swap ] dip [ editor-line ] [ font>> ] bi swap offset>x gl-round ;
156
157 : loc>point ( loc editor -- loc )
158     [ loc>x ] [ [ first ] dip line>y gl-ceiling ] 2bi 2array ;
159
160 : caret-loc ( editor -- loc )
161     [ editor-caret ] keep loc>point ;
162
163 : caret-dim ( editor -- dim )
164     [ 0 ] dip line-height 2array ;
165
166 : scroll>caret ( editor -- )
167     dup graft-state>> second [
168         [
169             [ caret-loc ] [ caret-dim { 2 1 } v+ ] bi <rect>
170         ] keep scroll>rect
171     ] [ drop ] if ;
172
173 <PRIVATE
174
175 : draw-caret? ( editor -- ? )
176     { [ focused?>> ] [ blink>> ]
177       [ [ preedit? not ] [ preedit-selection-mode?>> not ] bi or ] } 1&& ;
178
179 : (caret-location) ( editor -- loc dim )
180     [ caret-loc ] [ caret-dim ] bi ;
181
182 : (caret-rect) ( dim -- newdim )
183     second [ 2 / ] keep 2array ;
184
185 : draw-caret-line ( editor -- )
186     (caret-location) over v+ gl-line ;
187
188 : draw-caret-rect ( editor -- )
189     (caret-location) (caret-rect) gl-rect ;
190
191 : draw-caret-rect-filled ( editor -- )
192     (caret-location) (caret-rect) gl-fill-rect ;
193
194 : draw-caret-shape ( editor -- )
195     dup caret-shape>> value>>
196     {
197         { +box+ [ draw-caret-rect ] }
198         { +filled+ [ draw-caret-rect-filled ] }
199         [ drop  draw-caret-line ]
200     } case ;
201     
202
203 : draw-caret ( editor -- )
204     dup draw-caret? [
205         [ editor-caret-color gl-color ] dip
206         draw-caret-shape
207     ] [ drop ] if ;
208
209 :: draw-preedit-underlines ( editor -- )
210     editor [ preedit? ] [ preedit-underlines>> ] bi and [
211         editor [ caret-loc second ] [ caret-dim second ] bi + 2.0 - :> y
212         editor editor-caret first :> row
213         editor font>> foreground>> gl-color
214         editor preedit-underlines>> [
215             GL_LINE_BIT [
216                 dup second glLineWidth
217                 first editor preedit-start>> second dup 2array v+ first2
218                 [ row swap 2array editor loc>x 1.0 + y 2array ]
219                 [ row swap 2array editor loc>x 1.0 - y 2array ]
220                 bi*
221                 gl-line
222             ] do-attribs
223         ] each
224     ] when ;
225
226 : selection-start/end ( editor -- start end )
227     [ editor-mark ] [ editor-caret ] bi sort-pair ;
228
229 SYMBOL: selected-lines
230
231 TUPLE: selected-line start end first? last? ;
232
233 : compute-selection ( editor -- assoc )
234     dup gadget-selection? [
235         [ selection-start/end [ [ first ] bi@ [a..b] ] [ ] 2bi ]
236         [ model>> ] bi
237         '[ [ _ _ ] [ _ start/end-on-line ] bi 2array ] H{ } map>assoc
238     ] [ drop f ] if ;
239
240 :: draw-selection ( line pair editor -- )
241     pair [ editor font>> line offset>x gl-round ] map :> pair
242     editor selection-color>> gl-color
243     pair first 0 2array
244     pair second pair first - 1 max editor line-height 2array
245     gl-fill-rect ;
246
247 : draw-unselected-line ( line editor -- )
248     font>> swap draw-text ;
249
250 : draw-selected-line ( line pair editor -- )
251     over all-equal? [
252         [ nip draw-unselected-line ] [ draw-selection ] 3bi
253     ] [
254         [ draw-selection ]
255         [
256             [ [ first2 ] [ selection-color>> ] bi* <selection> ]
257             [ draw-unselected-line ] bi
258         ] 3bi
259     ] if ;
260
261 : draw-default-text? ( editor -- ? )
262     { [ default-text>> ] [ model>> doc-string empty? ] } 1&& ;
263
264 : draw-default-text ( editor -- )
265     [ font>> clone line-color >>foreground ]
266     [ default-text>> ] bi draw-text ;
267
268 PRIVATE>
269
270 M: editor draw-line
271     [ selected-lines get at ] dip over
272     [ draw-selected-line ] [ nip draw-unselected-line ] if ;
273
274 M: editor draw-gadget*
275     dup draw-default-text? [
276         [ draw-default-text ] [ draw-caret ] [ draw-preedit-underlines ] tri
277     ] [
278         dup compute-selection selected-lines [
279             [ draw-lines ] [ draw-caret ] [ draw-preedit-underlines ] tri
280         ] with-variable
281     ] if ;
282
283 M: editor pref-dim*
284     [ call-next-method ] keep ! at least as big as our min-rows/min-cols
285     ! Add some space for the caret.
286     [ font>> ] keep dup draw-default-text?
287     [ default-text>> ] [ control-value ] if
288     text-dim { 1 0 } v+ vmax ;
289
290 M: editor baseline font>> font-metrics ascent>> ;
291
292 M: editor cap-height font>> font-metrics cap-height>> ;
293
294 <PRIVATE
295
296 : contents-changed ( model editor -- )
297     [ [ nip caret>> ] [ drop ] 2bi '[ _ validate-loc ] (change-model) ]
298     [ [ nip mark>> ] [ drop ] 2bi '[ _ validate-loc ] (change-model) ]
299     [ nip relayout ] 2tri ;
300
301 : caret/mark-changed ( editor -- )
302     [ restart-blinking ] keep scroll>caret ;
303
304 PRIVATE>
305
306 M: editor model-changed
307     {
308         { [ 2dup model>> eq? ] [ contents-changed ] }
309         { [ 2dup caret>> eq? ] [ nip caret/mark-changed ] }
310         { [ 2dup mark>> eq? ] [ nip caret/mark-changed ] }
311     } cond ;
312
313 M: editor gadget-selection?
314     selection-start/end = not ;
315
316 M: editor gadget-selection
317     [ selection-start/end ] [ model>> ] bi doc-range ;
318
319 : remove-selection ( editor -- )
320     [ selection-start/end ] [ model>> ] bi remove-doc-range ;
321
322 M: editor user-input*
323     [ selection-start/end ] [ model>> ] bi set-doc-range t ;
324
325 M: editor temp-im-input
326     [ selection-start/end ] [ model>> ] bi set-doc-range* t ;
327
328 : editor-string ( editor -- string )
329     model>> doc-string ;
330
331 : set-editor-string ( string editor -- )
332     model>> set-doc-string ;
333
334 M: editor gadget-text* editor-string % ;
335
336 : extend-selection ( editor -- )
337     [ request-focus ]
338     [ restart-blinking ]
339     [ dup caret>> click-loc ] tri ;
340
341 : remove-preedit-text ( editor -- )
342     { [ preedit-start>> ] [ set-caret ]
343       [ preedit-end>> ] [ set-mark ]
344       [ remove-selection ]
345     } cleave ;
346
347 : remove-preedit-info ( editor -- )
348     f >>preedit-start
349     f >>preedit-end
350     f >>preedit-selected-start
351     f >>preedit-selected-end
352     f >>preedit-selection-mode?
353     f >>preedit-underlines
354     drop ;
355
356 : mouse-elt ( -- element )
357     hand-click# get {
358         { 1 one-char-elt }
359         { 2 one-word-elt }
360     } at one-line-elt or ;
361
362 : drag-direction? ( loc editor -- ? )
363     editor-mark before? ;
364
365 : drag-selection-caret ( loc editor element -- loc )
366     [
367         [ drag-direction? ] [ model>> ] 2bi
368     ] dip prev/next-elt ? ;
369
370 : drag-selection-mark ( loc editor element -- loc )
371     [
372         [ drag-direction? not ]
373         [ editor-mark ]
374         [ model>> ] tri
375     ] dip prev/next-elt ? ;
376
377 : drag-caret&mark ( editor -- caret mark )
378     [ clicked-loc ] [ mouse-elt ] bi
379     [ drag-selection-caret ]
380     [ drag-selection-mark ] 3bi ;
381
382 : drag-selection ( editor -- )
383     [ drag-caret&mark ]
384     [ mark>> set-model ]
385     [ caret>> set-model ] tri ;
386
387 : editor-cut ( editor clipboard -- )
388     [ gadget-copy ] [ drop remove-selection ] 2bi ;
389
390 : delete/backspace ( editor quot -- )
391     over gadget-selection? [
392         drop remove-selection
393     ] [
394         [ [ [ editor-caret ] [ model>> ] bi ] dip call ]
395         [ drop model>> ]
396         2bi remove-doc-range
397     ] if ; inline
398
399 : editor-delete ( editor elt -- )
400     '[ dupd _ next-elt ] delete/backspace ;
401
402 : editor-backspace ( editor elt -- )
403     '[ over [ _ prev-elt ] dip ] delete/backspace ;
404
405 : editor-select-prev ( editor elt -- )
406     '[ _ prev-elt ] change-caret ;
407
408 : editor-prev ( editor elt -- )
409     [ editor-select-prev ] [ drop mark>caret ] 2bi ;
410
411 : editor-select-next ( editor elt -- )
412     '[ _ next-elt ] change-caret ;
413
414 : editor-next ( editor elt -- )
415     dupd editor-select-next mark>caret ;
416
417 : editor-select ( from to editor -- )
418     [ mark>> set-model ] [ caret>> set-model ] bi-curry bi* ;
419
420 : select-elt ( editor elt -- )
421     [ [ [ editor-caret ] [ model>> ] bi ] dip prev/next-elt ] [ drop ] 2bi
422     editor-select ;
423
424 : start-of-document ( editor -- ) doc-elt editor-prev ;
425
426 : end-of-document ( editor -- ) doc-elt editor-next ;
427
428 : position-caret ( editor -- )
429     mouse-elt dup one-char-elt =
430     [ drop dup extend-selection dup mark>> click-loc ]
431     [ select-elt ] if ;
432
433 : delete-previous-character ( editor -- )
434     char-elt editor-backspace ;
435
436 : delete-next-character ( editor -- )
437     char-elt editor-delete ;
438
439 : delete-previous-word ( editor -- )
440     word-elt editor-backspace ;
441
442 : delete-next-word ( editor -- )
443     word-elt editor-delete ;
444
445 : delete-to-start-of-line ( editor -- )
446     one-line-elt editor-backspace ;
447
448 : delete-to-end-of-line ( editor -- )
449     one-line-elt editor-delete ;
450
451 : delete-to-start-of-document ( editor -- )
452     doc-elt editor-delete ;
453
454 : delete-to-end-of-document ( editor -- )
455     doc-elt editor-delete ;
456
457 : com-undo ( editor -- ) model>> undo ;
458
459 : com-redo ( editor -- ) model>> redo ;
460
461 editor "editing" f {
462     { undo-action com-undo }
463     { redo-action com-redo }
464     { T{ key-down f f "DELETE" } delete-next-character }
465     { T{ key-down f f "BACKSPACE" } delete-previous-character }
466     { T{ key-down f { S+ } "DELETE" } delete-next-character }
467     { T{ key-down f { S+ } "BACKSPACE" } delete-previous-character }
468     { T{ key-down f ${ os macosx? A+ C+ ? } "DELETE" } delete-next-word }
469     { T{ key-down f ${ os macosx? A+ C+ ? } "BACKSPACE" } delete-previous-word }
470     { T{ key-down f ${ os macosx? M+ A+ ? } "DELETE" } delete-to-end-of-line }
471     { T{ key-down f ${ os macosx? M+ A+ ? } "BACKSPACE" } delete-to-start-of-line }
472 } os macosx? [ {
473     { T{ key-down f { C+ } "DELETE" } delete-next-character }
474     { T{ key-down f { C+ } "BACKSPACE" } delete-previous-character }
475 } append ] when define-command-map
476
477 : com-paste ( editor -- ) clipboard get paste-clipboard ;
478
479 : paste-selection ( editor -- ) ui.clipboards:selection get paste-clipboard ;
480
481 : com-cut ( editor -- ) clipboard get editor-cut ;
482
483 editor "clipboard" f {
484     { cut-action com-cut }
485     { copy-action com-copy }
486     { paste-action com-paste }
487     { T{ button-up } com-copy-selection }
488     { T{ button-up f f 2 } paste-selection }
489 } define-command-map
490
491 : previous-character ( editor -- )
492     dup gadget-selection? [
493         dup selection-start/end drop
494         over set-caret mark>caret
495     ] [
496         char-elt editor-prev
497     ] if ;
498
499 : next-character ( editor -- )
500     dup gadget-selection? [
501         dup selection-start/end nip
502         over set-caret mark>caret
503     ] [
504         char-elt editor-next
505     ] if ;
506
507 : previous-word ( editor -- ) word-elt editor-prev ;
508
509 : next-word ( editor -- ) word-elt editor-next ;
510
511 : start-of-line ( editor -- ) one-line-elt editor-prev ;
512
513 : end-of-line ( editor -- ) one-line-elt editor-next ;
514
515 : start-of-paragraph ( editor -- ) paragraph-elt editor-prev ;
516
517 : end-of-paragraph ( editor -- ) paragraph-elt editor-next ;
518
519 editor "caret-motion" f {
520     { T{ button-down } position-caret }
521     { T{ key-down f f "LEFT" } previous-character }
522     { T{ key-down f f "RIGHT" } next-character }
523     { T{ key-down f ${ os macosx? A+ C+ ? } "LEFT" } previous-word }
524     { T{ key-down f ${ os macosx? A+ C+ ? } "RIGHT" } next-word }
525     { T{ key-down f f "HOME" } start-of-line }
526     { T{ key-down f f "END" } end-of-line }
527     { T{ key-down f ${ os macosx? A+ C+ ? } "UP" } start-of-paragraph }
528     { T{ key-down f ${ os macosx? A+ C+ ? } "DOWN" } end-of-paragraph }
529     { T{ key-down f ${ os macosx? A+ C+ ? } "HOME" } start-of-document }
530     { T{ key-down f ${ os macosx? A+ C+ ? } "END" } end-of-document }
531 } os macosx? [ {
532     { T{ key-down f { M+ } "LEFT" } start-of-line }
533     { T{ key-down f { M+ } "RIGHT" } end-of-line }
534     { T{ key-down f { M+ } "UP" } start-of-paragraph }
535     { T{ key-down f { M+ } "DOWN" } end-of-paragraph }
536     { T{ key-down f { M+ } "HOME" } start-of-document }
537     { T{ key-down f { M+ } "END" } end-of-document }
538 } append ] when define-command-map
539
540 : clear-editor ( editor -- )
541     model>> clear-doc ;
542
543 : select-all ( editor -- ) doc-elt select-elt ;
544
545 : select-line ( editor -- ) one-line-elt select-elt ;
546
547 : select-word ( editor -- ) one-word-elt select-elt ;
548
549 : selected-token ( editor -- string )
550     dup gadget-selection?
551     [ dup select-word ] unless
552     gadget-selection ;
553
554 : select-previous-character ( editor -- )
555     char-elt editor-select-prev ;
556
557 : select-next-character ( editor -- )
558     char-elt editor-select-next ;
559
560 : select-previous-word ( editor -- )
561     word-elt editor-select-prev ;
562
563 : select-next-word ( editor -- )
564     word-elt editor-select-next ;
565
566 : select-start-of-line ( editor -- )
567     one-line-elt editor-select-prev ;
568
569 : select-end-of-line ( editor -- )
570     one-line-elt editor-select-next ;
571
572 : select-start-of-paragraph ( editor -- )
573     paragraph-elt editor-select-prev ;
574
575 : select-end-of-paragraph ( editor -- )
576     paragraph-elt editor-select-next ;
577
578 : select-start-of-document ( editor -- )
579     doc-elt editor-select-prev ;
580
581 : select-end-of-document ( editor -- )
582     doc-elt editor-select-next ;
583
584 editor "selection" f {
585     { T{ button-down f { S+ } 1 } extend-selection }
586     { T{ button-up f { S+ } 1 } com-copy-selection }
587     { T{ drag { # 1 } } drag-selection }
588     { gain-focus focus-editor }
589     { lose-focus unfocus-editor }
590     { delete-action remove-selection }
591     { select-all-action select-all }
592     { T{ key-down f { C+ } "l" } select-line }
593     { T{ key-down f { S+ } "LEFT" } select-previous-character }
594     { T{ key-down f { S+ } "RIGHT" } select-next-character }
595     { T{ key-down f ${ S+ os macosx? A+ C+ ? } "LEFT" } select-previous-word }
596     { T{ key-down f ${ S+ os macosx? A+ C+ ? } "RIGHT" } select-next-word }
597     { T{ key-down f { S+ } "HOME" } select-start-of-line }
598     { T{ key-down f { S+ } "END" } select-end-of-line }
599     { T{ key-down f ${ S+ os macosx? A+ C+ ? } "UP" } select-start-of-paragraph }
600     { T{ key-down f ${ S+ os macosx? A+ C+ ? } "DOWN" } select-end-of-paragraph }
601     { T{ key-down f ${ S+ os macosx? A+ C+ ? } "HOME" } select-start-of-document }
602     { T{ key-down f ${ S+ os macosx? A+ C+ ? } "END" } select-end-of-document }
603 } os macosx? [ {
604     { T{ key-down f { S+ M+ } "LEFT" } select-start-of-line }
605     { T{ key-down f { S+ M+ } "RIGHT" } select-end-of-line }
606     { T{ key-down f { S+ M+ } "UP" } select-start-of-paragraph }
607     { T{ key-down f { S+ M+ } "DOWN" } select-end-of-paragraph }
608     { T{ key-down f { S+ M+ } "HOME" } select-start-of-document }
609     { T{ key-down f { S+ M+ } "END" } select-end-of-document }
610 } append ] when define-command-map
611
612 : editor-menu ( editor -- )
613     {
614         com-undo
615         com-redo
616         ----
617         com-cut
618         com-copy
619         com-paste
620     } show-commands-menu ;
621
622 editor "misc" f {
623     ! { T{ button-down f f 2 } paste-selection }
624     { T{ button-down f f 3 } editor-menu }
625 } define-command-map
626
627 ! Multi-line editors
628 TUPLE: multiline-editor < editor ;
629
630 : <multiline-editor> ( -- editor )
631     multiline-editor new-editor ;
632
633 : previous-line ( editor -- ) line-elt editor-prev ;
634
635 : next-line ( editor -- ) line-elt editor-next ;
636
637 <PRIVATE
638
639 : page-elt ( editor n -- editor element )
640     over visible-lines 1 - min 1 max <page-elt> ;
641
642 : prev-page-elt ( editor -- editor element )
643     dup editor-caret first page-elt ;
644
645 : next-page-elt ( editor -- editor element )
646     dup [ control-value length 1 - ] [ editor-caret first ] bi - page-elt ;
647
648 PRIVATE>
649
650 : previous-page ( editor -- ) prev-page-elt editor-prev ;
651
652 : next-page ( editor -- ) next-page-elt editor-next ;
653
654 : select-previous-line ( editor -- ) line-elt editor-select-prev ;
655
656 : select-next-line ( editor -- ) line-elt editor-select-next ;
657
658 : select-previous-page ( editor -- ) prev-page-elt editor-select-prev ;
659
660 : select-next-page ( editor -- ) next-page-elt editor-select-next ;
661
662 : insert-newline ( editor -- )
663     "\n" swap user-input* drop ;
664
665 : change-selection ( editor quot -- )
666     '[ gadget-selection @ ] [ user-input* drop ] bi ; inline
667
668 <PRIVATE
669
670 : join-lines ( string -- string' )
671     split-lines
672     [ rest-slice [ [ blank? ] trim-head-slice ] map! drop ]
673     [ but-last-slice [ [ blank? ] trim-tail-slice ] map! drop ]
674     [ join-words ]
675     tri ;
676
677 : last-line? ( document line -- ? )
678     [ last-line# ] dip = ;
679
680 : prev-line-and-this ( document line -- start end )
681     swap [ drop 1 - 0 2array ] [ line-end ] 2bi ;
682
683 : join-with-prev ( document line -- )
684     [ prev-line-and-this ] [ drop ] 2bi
685     [ join-lines ] change-doc-range ;
686
687 : this-line-and-next ( document line -- start end )
688     swap [ drop 0 2array ] [ [ 1 + ] dip line-end ] 2bi ;
689
690 : join-with-next ( document line -- )
691     [ this-line-and-next ] [ drop ] 2bi
692     [ join-lines ] change-doc-range ;
693
694 PRIVATE>
695
696 : com-join-lines ( editor -- )
697     dup gadget-selection?
698     [ [ join-lines ] change-selection ] [
699         [ model>> ] [ editor-caret first ] bi {
700             { [ over last-line# 0 = ] [ 2drop ] }
701             { [ 2dup last-line? ] [ join-with-prev ] }
702             [ join-with-next ]
703         } cond
704     ] if ;
705
706 multiline-editor "multiline" f {
707     { T{ key-down f f "UP" } previous-line }
708     { T{ key-down f f "DOWN" } next-line }
709     { T{ key-down f { S+ } "UP" } select-previous-line }
710     { T{ key-down f { S+ } "DOWN" } select-next-line }
711     { T{ key-down f f "PAGE_UP" } previous-page }
712     { T{ key-down f f "PAGE_DOWN" } next-page }
713     { T{ key-down f { S+ } "PAGE_UP" } select-previous-page }
714     { T{ key-down f { S+ } "PAGE_DOWN" } select-next-page }
715     { T{ key-down f f "RET" } insert-newline }
716     { T{ key-down f { S+ } "RET" } insert-newline }
717     { T{ key-down f f "ENTER" } insert-newline }
718     { T{ key-down f { S+ } "ENTER" } insert-newline }
719     { T{ key-down f { C+ } "j" } com-join-lines }
720 } define-command-map
721
722 TUPLE: source-editor < multiline-editor ;
723
724 : <source-editor> ( -- editor )
725     source-editor new-editor ;
726
727 ! A useful model
728 : <element-model> ( editor element -- model )
729     [ [ caret>> ] [ model>> ] bi ] dip
730     '[ _ _ elt-string ] <arrow> ;
731
732 ! Fields wrap an editor
733 TUPLE: field < border editor min-cols max-cols ;
734
735 <PRIVATE
736
737 : field-theme ( gadget -- gadget )
738     { 2 2 } >>size
739     { 1 0 } >>fill
740     field-border-color <solid> >>boundary ; inline
741
742 : <field-border> ( gadget -- border )
743     border new-border field-theme ;
744
745 PRIVATE>
746
747 : new-field ( class -- gadget )
748     [ <editor> ] dip new-border
749         dup gadget-child >>editor
750         field-theme ; inline
751
752 ! For line-gadget-width
753 M: field font>> editor>> font>> ;
754
755 M: field pref-dim*
756     [ ]
757     [ editor>> pref-dim ]
758     [ [ line-gadget-width ] [ drop second ] 2bi 2array ]
759     tri border-pref-dim ;
760
761 M: field default-text>> editor>> default-text>> ;
762
763 M: field default-text<< editor>> default-text<< ;
764
765 TUPLE: model-field < field field-model ;
766
767 : <model-field> ( model -- gadget )
768     model-field new-field
769         swap >>field-model ;
770
771 M: model-field graft*
772     [ [ field-model>> value>> ] [ editor>> ] bi set-editor-string ]
773     [ dup editor>> model>> add-connection ]
774     bi ;
775
776 M: model-field ungraft*
777     dup editor>> model>> remove-connection ;
778
779 M: model-field model-changed
780     nip [ editor>> editor-string ] [ field-model>> ] bi set-model ;
781
782 TUPLE: action-field < field quot ;
783
784 : <action-field> ( quot: ( string -- ) -- gadget )
785     action-field [ <editor> ] dip new-border
786         dup gadget-child >>editor
787         field-theme
788         swap >>quot ;
789
790 : invoke-action-field ( field -- )
791     [ editor>> editor-string ]
792     [ editor>> clear-editor ]
793     [ quot>> ]
794     tri call( string -- ) ;
795
796 action-field H{
797     { T{ key-down f f "RET" } [ invoke-action-field ] }
798 } set-gestures
799
800 : readline-bindings ( editor-class -- )
801     "readline" f {
802         { T{ key-down f { C+ } "p" } previous-line }
803         { T{ key-down f { C+ } "n" } next-line }
804         { T{ key-down f { C+ } "b" } previous-character }
805         { T{ key-down f { C+ } "f" } next-character }
806         { T{ key-down f { C+ } "a" } start-of-line }
807         { T{ key-down f { C+ } "e" } end-of-line }
808         ! { T{ key-down f { C+ } "t" } transpose-character }
809         { T{ key-down f { C+ } "d" } delete-next-character }
810         { T{ key-down f { C+ } "h" } delete-previous-character }
811         { T{ key-down f { C+ } "u" } delete-to-start-of-line }
812         { T{ key-down f { C+ } "k" } delete-to-end-of-line }
813         { T{ key-down f { C+ } "w" } delete-previous-word }
814     } define-command-map ;