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 colors.constants combinators.short-circuit compiler.units
5 concurrency.flags concurrency.mailboxes continuations destructors
6 documents documents.elements fry hashtables help help.markup io
7 io.styles kernel lexer listener math models models.delay models.arrow
8 namespaces parser prettyprint quotations sequences strings threads
9 tools.vocabs vocabs vocabs.loader vocabs.parser words debugger ui ui.commands
10 ui.pens.solid ui.gadgets ui.gadgets.glass ui.gadgets.buttons ui.gadgets.editors
11 ui.gadgets.labeled ui.gadgets.panes ui.gadgets.scrollers
12 ui.gadgets.status-bar ui.gadgets.tracks ui.gadgets.borders ui.gestures
13 ui.operations ui.tools.browser ui.tools.common ui.tools.debugger
14 ui.tools.listener.completion ui.tools.listener.popups
15 ui.tools.listener.history ;
18 ! If waiting is t, we're waiting for user input, and invoking
19 ! evaluate-input resumes the thread.
20 TUPLE: interactor < source-editor
21 output history flag mailbox thread waiting token-model word-model popup ;
23 : register-self ( interactor -- )
28 : interactor-continuation ( interactor -- continuation )
29 thread>> continuation>> value>> ;
31 : interactor-busy? ( interactor -- ? )
32 #! We're busy if there's no thread to resume.
34 [ thread>> dup [ thread-registered? ] when ]
39 M: interactor vocabs>>
40 dup interactor-busy? [ drop f ] [
42 interactor-continuation name>>
46 : vocab-exists? ( name -- ? )
47 '[ _ { [ vocab ] [ find-vocab-root ] } 1|| ] [ drop f ] recover ;
49 GENERIC: (word-at-caret) ( token completion-mode -- obj )
51 M: vocab-completion (word-at-caret)
52 drop dup vocab-exists? [ >vocab-link ] [ drop f ] if ;
54 M: word-completion (word-at-caret)
55 vocabs>> assoc-stack ;
57 M: char-completion (word-at-caret)
60 : word-at-caret ( token interactor -- obj )
61 completion-mode (word-at-caret) ;
63 : <word-model> ( interactor -- model )
64 [ token-model>> 1/3 seconds <delay> ]
65 [ '[ _ word-at-caret ] ] bi
68 : <interactor> ( -- gadget )
71 dup one-word-elt <element-model> >>token-model
72 dup <word-model> >>word-model
73 dup model>> <history> >>history ;
76 [ call-next-method ] [ dup word-model>> add-connection ] bi ;
78 M: interactor ungraft*
79 [ dup word-model>> remove-connection ] [ call-next-method ] bi ;
81 M: interactor model-changed
82 2dup word-model>> eq? [
84 [ 2drop ] [ [ value>> ] dip show-summary ] if
85 ] [ call-next-method ] if ;
87 GENERIC: (print-input) ( object -- )
89 M: input (print-input)
90 dup presented associate
91 [ string>> H{ { font-style bold } } format ] with-nesting nl ;
96 "sans-serif" font-name set
98 ] H{ } make-assoc format . ;
100 : print-input ( object interactor -- )
101 output>> [ (print-input) ] with-output-stream* ;
103 : interactor-continue ( obj interactor -- )
104 mailbox>> mailbox-put ;
106 : interactor-finish ( interactor -- )
107 [ history>> history-add ] keep
109 [ clear-editor drop ]
110 [ model>> clear-undo drop ] 2tri ;
112 : interactor-eof ( interactor -- )
113 dup interactor-busy? [
114 f over interactor-continue
117 : evaluate-input ( interactor -- )
118 dup interactor-busy? [ drop ] [
119 [ control-value ] keep interactor-continue
122 : interactor-yield ( interactor -- obj )
123 dup thread>> self eq? [
126 [ flag>> raise-flag ]
127 [ mailbox>> mailbox-get ]
132 : interactor-read ( interactor -- lines )
133 [ interactor-yield ] [ interactor-finish ] bi ;
135 M: interactor stream-readln
136 interactor-read dup [ first ] when ;
138 : (call-listener) ( quot command listener -- )
139 input>> dup interactor-busy? [ 3drop ] [
141 [ nip interactor-continue ]
145 M: interactor stream-read
149 [ interactor-read dup [ "\n" join ] when ] dip short head
152 M: interactor stream-read-partial
155 M: interactor stream-read1
156 dup interactor-read {
157 { [ dup not ] [ 2drop f ] }
158 { [ dup empty? ] [ drop stream-read1 ] }
159 { [ dup first empty? ] [ 2drop CHAR: \n ] }
163 M: interactor dispose drop ;
165 : go-to-error ( interactor error -- )
166 [ line>> 1- ] [ column>> ] bi 2array
170 TUPLE: listener-gadget < tool input output scroller ;
172 { 600 700 } listener-gadget set-tool-dim
174 : find-listener ( gadget -- listener )
175 [ listener-gadget? ] find-parent ;
177 : listener-streams ( listener -- input output )
178 [ input>> ] [ output>> ] bi <pane-stream> ;
180 : init-listener ( listener -- listener )
182 [ >>input ] [ pane new-pane t >>scrolls? >>output ] bi
183 dup listener-streams >>output drop ;
185 : <listener-gadget> ( -- gadget )
186 vertical listener-gadget new-track
189 dup output>> <scroller> >>scroller
190 dup scroller>> 1 track-add ;
192 M: listener-gadget focusable-child*
193 input>> dup popup>> or ;
195 : wait-for-listener ( listener -- )
196 #! Wait for the listener to start.
197 input>> flag>> wait-for-flag ;
199 : listener-busy? ( listener -- ? )
200 input>> interactor-busy? ;
202 : listener-window* ( -- listener )
204 dup "Listener" open-status-window ;
206 : listener-window ( -- )
207 [ listener-window* drop ] with-ui ;
209 \ listener-window H{ { +nullary+ t } } define-command
211 : (get-listener) ( quot -- listener )
217 [ input>> scroll>caret ]
218 [ input>> request-focus ] tri
220 ] [ listener-window* ] if* ; inline
222 : get-listener ( -- listener )
223 [ listener-gadget? ] (get-listener) ;
225 : show-listener ( -- )
228 \ show-listener H{ { +nullary+ t } } define-command
230 : get-ready-listener ( -- listener )
234 [ listener-busy? not ]
238 GENERIC: listener-input ( obj -- )
240 M: input listener-input string>> listener-input ;
242 M: string listener-input
244 [ set-editor-string ] [ request-focus ] bi ;
246 : call-listener ( quot command -- )
248 '[ _ _ _ dup wait-for-listener (call-listener) ]
249 "Listener call" spawn drop ;
251 M: listener-command invoke-command ( target command -- )
252 [ command-quot ] [ nip ] 2bi call-listener ;
254 M: listener-operation invoke-command ( target command -- )
255 [ operation-quot ] [ nip command>> ] 2bi call-listener ;
257 : eval-listener ( string -- )
258 get-listener input>> [ set-editor-string ] keep
261 : listener-run-files ( seq -- )
263 [ \ listener-run-files ] dip
264 '[ _ [ run-file ] each ] call-listener
267 : com-end ( listener -- )
268 input>> interactor-eof ;
270 : clear-output ( listener -- )
271 output>> pane-clear ;
273 \ clear-output H{ { +listener+ t } } define-command
275 : clear-stack ( listener -- )
276 [ [ clear ] \ clear ] dip (call-listener) ;
278 : use-if-necessary ( word seq -- )
279 2dup [ vocabulary>> ] dip and [
280 2dup [ assoc-stack ] keep = [ 2drop ] [
281 [ vocabulary>> vocab-words ] dip push
285 M: word accept-completion-hook
286 interactor>> vocabs>> use-if-necessary ;
288 M: object accept-completion-hook 2drop ;
290 : quot-action ( interactor -- lines )
291 [ history>> history-add drop ] [ control-value ] [ select-all ] tri
292 [ parse-lines ] with-compilation-unit ;
294 : <debugger-popup> ( error continuation -- popup )
295 over compute-restarts [ hide-glass ] <debugger> "Error" <labeled-gadget> ;
297 : debugger-popup ( interactor error continuation -- )
298 [ one-line-elt ] 2dip <debugger-popup> show-listener-popup ;
300 : handle-parse-error ( interactor error -- )
301 dup lexer-error? [ 2dup go-to-error error>> ] when
302 error-continuation get
305 : try-parse ( lines interactor -- quot/error/f )
306 [ drop parse-lines-interactive ] [
309 dup error>> unexpected-eof? [ drop f ] when
313 : handle-interactive ( lines interactor -- quot/f ? )
314 [ nip ] [ try-parse ] 2bi {
315 { [ dup quotation? ] [ nip t ] }
316 { [ dup not ] [ drop "\n" swap user-input* drop f f ] }
317 [ handle-parse-error f f ]
320 M: interactor stream-read-quot
321 [ interactor-yield ] keep {
322 { [ over not ] [ drop ] }
323 { [ over callable? ] [ drop ] }
325 [ handle-interactive ] keep swap
326 [ interactor-finish ] [ nip stream-read-quot ] if
330 : interactor-operation ( gesture interactor -- ? )
331 [ token-model>> value>> ] keep word-at-caret
332 [ nip ] [ gesture>operation ] 2bi
333 dup [ invoke-command f ] [ 2drop t ] if ;
335 M: interactor handle-gesture
337 { [ over key-gesture? not ] [ call-next-method ] }
338 { [ dup popup>> ] [ { [ pass-to-popup ] [ call-next-method ] } 2&& ] }
339 { [ dup token-model>> value>> ] [ { [ interactor-operation ] [ call-next-method ] } 2&& ] }
343 interactor "interactor" f {
344 { T{ key-down f f "RET" } evaluate-input }
345 { T{ key-down f { C+ } "k" } clear-editor }
348 interactor "completion" f {
349 { T{ key-down f f "TAB" } code-completion-popup }
350 { T{ key-down f { C+ } "p" } recall-previous }
351 { T{ key-down f { C+ } "n" } recall-next }
352 { T{ key-down f { C+ } "r" } history-completion-popup }
356 "If this is your first time with Factor, please read the " print
357 "handbook" ($link) ". To see a list of keyboard shortcuts," print
358 "press F1." print nl ;
360 : listener-thread ( listener -- )
361 dup listener-streams [
362 [ com-browse ] help-hook set
363 '[ [ _ input>> ] 2dip debugger-popup ] error-hook set
368 : start-listener-thread ( listener -- )
371 [ input>> register-self ]
374 ] "Listener" spawn drop ;
376 : restart-listener ( listener -- )
377 #! Returns when listener is ready to receive input.
381 [ input>> clear-editor ]
382 [ start-listener-thread ]
383 [ wait-for-listener ]
386 : listener-help ( -- ) "ui-listener" com-browse ;
388 \ listener-help H{ { +nullary+ t } } define-command
390 : com-auto-use ( -- )
391 auto-use? [ not ] change ;
393 \ com-auto-use H{ { +nullary+ t } { +listener+ t } } define-command
395 listener-gadget "misc" "Miscellaneous commands" {
396 { T{ key-down f f "F1" } listener-help }
399 listener-gadget "toolbar" f {
400 { f restart-listener }
401 { T{ key-down f { A+ } "u" } com-auto-use }
402 { T{ key-down f { A+ } "k" } clear-output }
403 { T{ key-down f { A+ } "K" } clear-stack }
404 { T{ key-down f { C+ } "d" } com-end }
407 listener-gadget "scrolling"
408 "The listener's scroller can be scrolled from the keyboard."
410 { T{ key-down f { A+ } "UP" } com-scroll-up }
411 { T{ key-down f { A+ } "DOWN" } com-scroll-down }
412 { T{ key-down f { A+ } "PAGE_UP" } com-page-up }
413 { T{ key-down f { A+ } "PAGE_DOWN" } com-page-down }
416 listener-gadget "multi-touch" f {
417 { up-action refresh-all }
420 M: listener-gadget graft*
421 [ call-next-method ] [ restart-listener ] bi ;
423 M: listener-gadget ungraft*
424 [ com-end ] [ call-next-method ] bi ;