]> gitweb.factorcode.org Git - factor.git/blob - basis/ui/tools/listener/listener.factor
863e170384b08e0507cdeb949ad67dfb53ad2f42
[factor.git] / basis / ui / tools / listener / listener.factor
1 ! Copyright (C) 2005, 2010 Slava Pestov.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays assocs calendar combinators
4 combinators.short-circuit concurrency.flags
5 concurrency.mailboxes continuations destructors documents
6 documents.elements fonts fry hashtables help help.markup
7 help.tips io io.styles kernel lexer listener literals locals
8 math models models.arrow models.delay namespaces parser
9 prettyprint sequences source-files.errors strings system threads
10 tools.errors.model ui ui.commands ui.gadgets ui.gadgets.editors
11 ui.gadgets.glass ui.gadgets.labeled ui.gadgets.panes
12 ui.gadgets.scrollers ui.gadgets.status-bar ui.theme
13 ui.gadgets.toolbar ui.gadgets.tracks ui.gestures ui.operations
14 ui.pens.solid ui.tools.browser ui.tools.common ui.tools.debugger
15 ui.tools.error-list ui.tools.listener.completion
16 ui.tools.listener.history ui.tools.listener.popups vocabs
17 vocabs.loader vocabs.parser vocabs.refresh words ;
18 IN: ui.tools.listener
19
20 TUPLE: interactor < source-editor
21     output history flag mailbox thread waiting token-model word-model popup ;
22
23 INSTANCE: interactor input-stream
24
25 : register-self ( interactor -- )
26     <mailbox> >>mailbox
27     self >>thread
28     drop ;
29
30 : interactor-continuation ( interactor -- continuation )
31     thread>> thread-continuation ;
32
33 : interactor-busy? ( interactor -- ? )
34     {
35         [ waiting>> ]
36         [ thread>> dup [ thread-registered? ] when ]
37     } 1&& not ;
38
39 SLOT: manifest
40
41 M: interactor manifest>>
42     dup interactor-busy? [ drop f ] [
43         interactor-continuation name>>
44         manifest swap assoc-stack
45     ] if ;
46
47 : vocab-exists? ( name -- ? )
48     '[ _ { [ lookup-vocab ] [ find-vocab-root ] } 1|| ] [ drop f ] recover ;
49
50 GENERIC: (word-at-caret) ( token completion-mode -- obj )
51
52 M: vocab-completion (word-at-caret)
53     drop dup vocab-exists? [ >vocab-link ] [ drop f ] if ;
54
55 M: word-completion (word-at-caret)
56     manifest>> [
57         '[ _ _ search-manifest ] [ drop f ] recover
58     ] [ drop f ] if* ;
59
60 M: char-completion (word-at-caret) 2drop f ;
61
62 M: path-completion (word-at-caret) 2drop f ;
63
64 M: color-completion (word-at-caret) 2drop f ;
65
66 : word-at-caret ( token interactor -- obj )
67     completion-mode (word-at-caret) ;
68
69 : <word-model> ( interactor -- model )
70     [ token-model>> 1/3 seconds <delay> ]
71     [ '[ _ word-at-caret ] ] bi
72     <arrow> ;
73
74 : <interactor> ( -- gadget )
75     interactor new-editor
76         theme-font-colors
77         <flag> >>flag
78         dup one-word-elt <element-model> >>token-model
79         dup <word-model> >>word-model
80         dup model>> <history> >>history ;
81
82 M: interactor graft*
83     [ call-next-method ] [ dup word-model>> add-connection ] bi ;
84
85 M: interactor ungraft*
86     [ dup word-model>> remove-connection ] [ call-next-method ] bi ;
87
88 M: interactor model-changed
89     2dup word-model>> eq? [
90         dup popup>>
91         [ 2drop ] [ [ value>> ] dip show-summary ] if
92     ] [ call-next-method ] if ;
93
94 M: interactor stream-element-type drop +character+ ;
95
96 GENERIC: (print-input) ( object -- )
97
98 SYMBOL: listener-input-style
99 H{
100     { font-style bold }
101     { foreground $ text-color }
102 } listener-input-style set-global
103
104 SYMBOL: listener-word-style
105 H{
106     { font-name "sans-serif" }
107     { font-style bold }
108     { foreground $ text-color }
109 } listener-word-style set-global
110
111 M: input (print-input)
112     dup presented associate [
113         string>> listener-input-style get-global format
114     ] with-nesting nl ;
115
116 M: word (print-input)
117     "Command: " listener-word-style get-global format . ;
118
119 : print-input ( object interactor -- )
120     output>> [ (print-input) ] with-output-stream* ;
121
122 : interactor-continue ( obj interactor -- )
123     mailbox>> mailbox-put ;
124
125 : interactor-finish ( interactor -- )
126     [ history>> history-add ] keep
127     [ print-input ]
128     [ clear-editor drop ]
129     [ model>> clear-undo drop ] 2tri ;
130
131 : interactor-eof ( interactor -- )
132     dup interactor-busy? [
133         f over interactor-continue
134     ] unless drop ;
135
136 : evaluate-input ( interactor -- )
137     dup interactor-busy? [ drop ] [
138         [ control-value ] keep interactor-continue
139     ] if ;
140
141 : interactor-yield ( interactor -- obj )
142     dup thread>> self eq? [
143         {
144             [ t >>waiting drop ]
145             [ flag>> raise-flag ]
146             [ mailbox>> mailbox-get ]
147             [ f >>waiting drop ]
148         } cleave
149     ] [ drop f ] if ;
150
151 : interactor-read ( interactor -- lines )
152     [ interactor-yield ] [ interactor-finish ] bi ;
153
154 M: interactor stream-readln
155     interactor-read dup [ first ] when ;
156
157 : (call-listener) ( quot command listener -- )
158     input>> dup interactor-busy? [ 3drop ] [
159         [ print-input drop ]
160         [ nip interactor-continue ]
161         3bi
162     ] if ;
163
164 M:: interactor stream-read-unsafe ( n buf interactor -- count )
165     n [ 0 ] [
166         drop
167         interactor interactor-read dup [ "\n" join ] when
168         n short [ head-slice 0 buf copy ] keep
169     ] if-zero ;
170
171 M: interactor stream-read1
172     dup interactor-read {
173         { [ dup not ] [ 2drop f ] }
174         { [ dup empty? ] [ drop stream-read1 ] }
175         { [ dup first empty? ] [ 2drop CHAR: \n ] }
176         [ nip first first ]
177     } cond ;
178
179 M: interactor stream-read-until ( seps stream -- seq sep/f )
180     swap '[
181         _ interactor-read [
182             "\n" join CHAR: \n suffix
183             [ _ member? ] dupd find
184             [ [ head ] when* ] dip dup not
185         ] [ f f f ] if*
186     ] [ drop ] produce swap [ concat "" prepend-as ] dip ;
187
188 M: interactor dispose drop ;
189
190 : go-to-error ( interactor error -- )
191     [ line>> 1 - ] [ column>> ] bi 2array
192     over set-caret
193     mark>caret ;
194
195 TUPLE: listener-gadget < tool error-summary output scroller input ;
196
197 { 600 700 } listener-gadget set-tool-dim
198
199 : listener-streams ( listener -- input output )
200     [ input>> ] [ output>> <pane-stream> ] bi ;
201
202 : init-input/output ( listener -- listener )
203     <interactor>
204     [ >>input ] [ pane new-pane t >>scrolls? >>output ] bi
205     dup listener-streams >>output drop ;
206
207 : error-summary. ( -- )
208     error-counts keys [
209         H{ { table-gap { 3 3 } } } [
210             [ [ [ icon>> write-image ] with-cell ] each ] with-row
211         ] tabular-output
212         last-element off
213         { "Press " { $command tool "common" show-error-list } " to view errors." }
214         print-element
215     ] unless-empty ;
216
217 : <error-summary> ( -- gadget )
218     error-list-model get [ drop error-summary. ] <pane-control>
219     error-summary-background <solid> >>interior ;
220
221 : init-error-summary ( listener -- listener )
222     <error-summary> >>error-summary
223     dup error-summary>> f track-add ;
224
225 : add-listener-area ( listener -- listener )
226     dup output>> margins <scroller> >>scroller
227     dup scroller>> white-interior 1 track-add ;
228
229 : <listener-gadget> ( -- listener )
230     vertical listener-gadget new-track with-lines
231     add-toolbar
232     init-input/output
233     add-listener-area
234     init-error-summary ;
235
236 M: listener-gadget focusable-child*
237     input>> dup popup>> or ;
238
239 : wait-for-listener ( listener -- )
240     input>> flag>> 5 seconds wait-for-flag-timeout ;
241
242 : listener-busy? ( listener -- ? )
243     input>> interactor-busy? ;
244
245 : listener-window* ( -- listener )
246     <listener-gadget>
247     dup "Listener" open-status-window ;
248
249 : listener-window ( -- )
250     [ listener-window* drop ] with-ui ;
251
252 \ listener-window H{ { +nullary+ t } } define-command
253
254 : (get-listener) ( quot -- listener )
255     find-window [
256         [ raise-window ]
257         [
258             gadget-child
259             [ ]
260             [ input>> scroll>caret ]
261             [ input>> request-focus ] tri
262         ] bi
263     ] [ listener-window* ] if* ; inline
264
265 : get-listener ( -- listener )
266     [ listener-gadget? ] (get-listener) ;
267
268 : show-listener ( -- )
269     get-listener drop ;
270
271 \ show-listener H{ { +nullary+ t } } define-command
272
273 : get-ready-listener ( -- listener )
274     [
275         {
276             [ listener-gadget? ]
277             [ listener-busy? not ]
278         } 1&&
279     ] (get-listener) ;
280
281 GENERIC: listener-input ( obj -- )
282
283 M: input listener-input string>> listener-input ;
284
285 M: string listener-input
286     get-listener input>>
287     [ set-editor-string ] [ request-focus ] bi ;
288
289 : call-listener ( quot command -- )
290     get-ready-listener
291     '[ _ _ _ dup wait-for-listener (call-listener) ]
292     "Listener call" spawn drop ;
293
294 M: listener-command invoke-command ( target command -- )
295     [ command-quot ] [ nip ] 2bi call-listener ;
296
297 M: listener-operation invoke-command ( target command -- )
298     [ operation-quot ] [ nip command>> ] 2bi call-listener ;
299
300 : eval-listener ( string -- )
301     get-listener input>> [ set-editor-string ] keep
302     evaluate-input ;
303
304 : listener-run-files ( seq -- )
305     [
306         '[ _ [ run-file ] each ]
307         \ listener-run-files
308         call-listener
309     ] unless-empty ;
310
311 : com-end ( listener -- )
312     input>> interactor-eof ;
313
314 : clear-output ( listener -- )
315     output>> pane-clear ;
316
317 \ clear-output H{ { +listener+ t } } define-command
318
319 : clear-stack ( listener -- )
320     [ [ clear ] \ clear ] dip (call-listener) ;
321
322 : use-if-necessary ( word manifest -- )
323     2dup [ vocabulary>> ] dip and [
324         manifest [
325             [ vocabulary>> use-vocab ]
326             [ dup name>> associate use-words ] bi
327         ] with-variable
328     ] [ 2drop ] if ;
329
330 M: word accept-completion-hook
331     interactor>> manifest>> use-if-necessary ;
332
333 M: object accept-completion-hook 2drop ;
334
335 : quot-action ( interactor -- lines )
336     [ history>> history-add drop ] [ control-value ] [ select-all ] tri
337     parse-lines-interactive ;
338
339 : do-recall? ( table error -- ? )
340     [ selection>> value>> not ] [ lexer-error? ] bi* and ;
341
342 : recall-lexer-error ( interactor error -- )
343     over recall-previous go-to-error ;
344
345 : make-restart-hook-quot ( error interactor -- quot )
346     over '[
347         dup hide-glass
348         _ do-recall? [ _ _ recall-lexer-error ] when
349     ] ;
350
351 : frame-debugger ( debugger -- labeled )
352     "Error" debugger-color <framed-labeled> ;
353
354 :: <debugger-popup> ( error continuation interactor -- popup )
355     error
356     continuation
357     error compute-restarts
358     error interactor make-restart-hook-quot
359     <debugger> frame-debugger ;
360
361 : debugger-popup ( interactor error continuation -- )
362     pick <debugger-popup> one-line-elt swap show-listener-popup ;
363
364 : try-parse ( lines -- quot/f )
365     [ parse-lines-interactive ] [ nip '[ _ rethrow ] ] recover ;
366
367 M: interactor stream-read-quot ( stream -- quot/f )
368     dup interactor-yield dup array? [
369         over interactor-finish try-parse
370         [ nip ] [ stream-read-quot ] if*
371     ] [ nip ] if ;
372
373 : interactor-operation ( gesture interactor -- ? )
374     [ token-model>> value>> ] keep word-at-caret
375     [ nip ] [ gesture>operation ] 2bi
376     [ invoke-command f ] [ drop t ] if* ;
377
378 M: interactor handle-gesture
379     {
380         { [ over key-gesture? not ] [ call-next-method ] }
381         { [ dup popup>> ] [ { [ pass-to-popup ] [ call-next-method ] } 2&& ] }
382         {
383             [ dup token-model>> value>> ]
384             [ { [ interactor-operation ] [ call-next-method ] } 2&& ]
385         }
386         [ call-next-method ]
387     } cond ;
388
389 interactor "interactor" f {
390     { T{ key-down f f "RET" } evaluate-input }
391     { T{ key-down f { C+ } "k" } clear-editor }
392 } define-command-map
393
394 interactor "completion" f {
395     { T{ key-down f f "TAB" } code-completion-popup }
396     { T{ key-down f { C+ } "p" } recall-previous }
397     { T{ key-down f { C+ } "n" } recall-next }
398     { T{ key-down f { C+ } "r" } history-completion-popup }
399 } define-command-map
400
401 : introduction. ( -- )
402     [
403         H{ { font-size $ default-font-size } } [
404             { $tip-of-the-day } print-element nl
405             { $strong "Press " { $snippet "F1" } " at any time for help." } print-element nl
406             version-info print-element
407         ] with-style
408     ] with-default-style nl nl ;
409
410 : listener-thread ( listener -- )
411     dup listener-streams [
412         [ com-browse ] help-hook set
413         '[ [ _ input>> ] 2dip debugger-popup ] error-hook set
414         error-summary? off
415         introduction.
416         listener
417         nl
418         "The listener has exited. To start it again, click “Restart Listener”." print
419     ] with-input-output+error-streams* ;
420
421 : start-listener-thread ( listener -- )
422     '[
423         _
424         [ input>> register-self ]
425         [ listener-thread ]
426         bi
427     ] "Listener" spawn drop ;
428
429 : restart-listener ( listener -- )
430     ! Returns when listener is ready to receive input.
431     {
432         [ com-end ]
433         [ clear-output ]
434         [ input>> clear-editor ]
435         [ start-listener-thread ]
436         [ wait-for-listener ]
437     } cleave ;
438
439 : com-help ( -- ) "help.home" com-browse ;
440
441 \ com-help H{ { +nullary+ t } } define-command
442
443 : com-auto-use ( -- )
444     auto-use? toggle ;
445
446 \ com-auto-use H{ { +nullary+ t } { +listener+ t } } define-command
447
448 listener-gadget "toolbar" f {
449     { f restart-listener }
450     { T{ key-down f { A+ } "u" } com-auto-use }
451     { T{ key-down f { A+ } "k" } clear-output }
452     { T{ key-down f { A+ } "K" } clear-stack }
453     { T{ key-down f { C+ } "d" } com-end }
454     { T{ key-down f f "F1" } com-help }
455 } define-command-map
456
457 listener-gadget "scrolling"
458 "The listener's scroller can be scrolled from the keyboard."
459 {
460     { T{ key-down f { A+ } "UP" } com-scroll-up }
461     { T{ key-down f { A+ } "DOWN" } com-scroll-down }
462     { T{ key-down f { A+ } "PAGE_UP" } com-page-up }
463     { T{ key-down f { A+ } "PAGE_DOWN" } com-page-down }
464 } define-command-map
465
466 listener-gadget "multi-touch" f {
467     { up-action refresh-all }
468 } define-command-map
469
470 M: listener-gadget graft*
471     [ call-next-method ] [ restart-listener ] bi ;
472
473 M: listener-gadget ungraft*
474     [ com-end ] [ call-next-method ] bi ;
475
476 <PRIVATE
477
478 :: make-font-style ( family size -- assoc )
479     H{ } clone
480         family font-name pick set-at
481         size font-size pick set-at ;
482
483 PRIVATE>
484
485 :: set-listener-font ( family size -- )
486     get-listener input>> :> inter
487     family size make-font-style
488     inter output>> make-span-stream :> ostream
489     ostream inter output<<
490     inter font>> clone
491         family >>name
492         size >>size
493     inter font<<
494     ostream output-stream set ;