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