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