#lang racket/base
(require drracket/tool
racket/class
racket/gui/base
racket/unit
racket/file
racket/pretty racket/path racket/dict
racket/list
racket/runtime-path (for-syntax racket/base) net/sendurl framework planet/version )
(provide tool@)
(define-runtime-path help-path
(build-path "planet-docs" "manual" "index.html"))
(define-runtime-path examples-path
(build-path "examples"))
(define base-default-user-script-dir (find-system-path 'pref-dir))
(preferences:set-default 'user-script-dir
(path->string (build-path base-default-user-script-dir
"user-scripts"))
path-string?)
(define (script-dir)
(preferences:get 'user-script-dir))
(unless (directory-exists? (script-dir))
(make-directory* base-default-user-script-dir)
(copy-directory/files examples-path (script-dir)))
(define (set-script-dir dir)
(preferences:set 'user-script-dir (if (path? dir) (path->string dir) dir)))
(define (choose-script-dir)
(let ([d (get-directory "Choose a directory to store scripts" #f
(script-dir))])
(when d (set-script-dir d))))
(define (error-message-box filename e)
(message-box filename
(format "Error in script file ~s: ~a" filename (exn-message e))
#f '(stop ok)))
(define-namespace-anchor a)
(preferences:add-panel
"Scripts"
(λ(parent)
(define pref-panel (new vertical-panel% [parent parent]
[alignment '(center center)]
[spacing 10]
[horiz-margin 10]
[vert-margin 10]
))
(define dir-panel (new horizontal-panel% [parent pref-panel]))
(define text-dir (new text-field% [parent dir-panel]
[label "Script directory:"]
[init-value (script-dir)]
[enabled #f]))
(new button% [parent dir-panel]
[label "Change script directory"]
[callback (λ _ (choose-script-dir))])
(preferences:add-callback 'user-script-dir
(λ(p v)(send text-dir set-value v)))
pref-panel))
(define tool@
(unit
(import drracket:tool^)
(export drracket:tool-exports^)
(define script-menu-mixin
(mixin (drracket:unit:frame<%>) ()
(super-new)
(inherit get-button-panel
get-definitions-text
get-interactions-text
create-new-tab
)
(define (get-the-text-editor)
(define defed (get-definitions-text))
(if (send defed has-focus?)
defed
(get-interactions-text))
)
(define frame this)
(define props-default
`((functions . item-callback)
(shortcut . #f)
(shortcut-prefix . #f)
(help-string . "Help String")
(output-to . selection) (persistent . #f)
(active . #t)
))
(define (prop-dict-ref props key)
(dict-ref props key (dict-ref props-default key)))
(define (new-script)
(define name (get-text-from-user "Script name" "Enter the name of the script:"))
(when name
(define script-name (string-append name ".rkt"))
(define f-script (build-path (script-dir) script-name))
(define f-prop (build-path (script-dir) (string-append script-name "d")))
(with-output-to-file f-prop
(λ _ (pretty-write (cons `(label . ,name) props-default))))
(with-output-to-file f-script
(λ _
(displayln "#lang racket/base\n\n;; Sample identity function:\n;; string? -> (or/c string? #f)")
(for-each pretty-write
'((provide item-callback)
(define (item-callback str)
str
)))
(displayln "\n;; See the manual in the Script/Help menu for more information.")
))
(edit-script f-prop)
(edit-script f-script)
))
(define (edit-script file)
(when file
(send this open-in-new-tab file)
))
(define (open-script)
(define file (get-file "Open a script" frame (script-dir) #f #f '()
'(("Racket" "*.rkt"))))
(edit-script file)
)
(define (open-script-properties)
(define file (get-file "Open a script properties" frame (script-dir) #f #f '()
'(("Property file" "*.rktd"))))
(edit-script file)
)
(define (import-bundled-script)
(define src-file (get-file "Open a script" frame examples-path #f #f '()
'(("Racket" "*.rkt"))))
(when src-file
(define src-dir (path-only src-file))
(define filename (path->string (file-name-from-path src-file)))
(define filenamed (string-append filename "d"))
(define src-filed (build-path src-dir filenamed))
(define dest-file (build-path (script-dir) filename))
(define dest-filed (build-path (script-dir) filenamed))
(define overwrite? (or (not (file-exists? dest-file))
(eq? 'ok
(message-box "Overwrite?" (string-append "The script " filename " already exists in your script directory.\n"
"Do you want to overwrite it?")
frame
'(caution ok-cancel)))))
(when overwrite?
(if (file-exists? src-filed)
(begin (copy-file src-file dest-file #t)
(copy-file src-filed dest-filed #t))
(message-box "Not a script" "This is not a script file (no associated .rktd file found)"
frame '(caution ok))))))
(define namespace-dict (make-hash))
(define (unload-persistent-scripts)
(set! namespace-dict (make-hash)))
(define (run-script fun file output-to persistent?)
(define text (get-the-text-editor))
(define str (send text get-text
(send text get-start-position)
(send text get-end-position)))
(define (make-script-namespace)
(define ns (make-base-empty-namespace))
(for ([mod '(racket/class racket/gui/base)])
(namespace-attach-module (namespace-anchor->empty-namespace a)
mod ns))
ns)
(define ns
(if persistent?
(dict-ref! namespace-dict file make-script-namespace)
(make-script-namespace)))
(define file-str (path->string file))
(define ed-file (send (get-definitions-text) get-filename))
(define str-out
(with-handlers ([exn:fail? (λ(e)(error-message-box (path->string (file-name-from-path file)) e)
#f)])
(parameterize ([current-namespace ns])
(let ([f (dynamic-require file fun)]
[kw-dict `((#:definitions . ,(get-definitions-text))
(#:interactions . ,(get-interactions-text))
(#:editor . ,text)
(#:file . ,ed-file)
(#:frame . ,this))])
(let-values ([(_ kws) (procedure-keywords f)])
(let ([k-v (sort (map (λ(k)(assoc k kw-dict)) kws)
keyword<? #:key car)])
(keyword-apply f (map car k-v) (map cdr k-v) str '())
)))
)))
(define (insert-to-text text)
(send text begin-edit-sequence)
(send text insert str-out)
(send text end-edit-sequence))
(when (or (string? str-out) (is-a? str-out snip%)) (case output-to
[(new-tab)
(create-new-tab)
(insert-to-text (get-the-text-editor))] [(selection)
(insert-to-text text)]
[(message-box)
(message-box "Ouput" str-out this)]
))
)
(define (open-help)
(send-url/file help-path))
(define (bug-report)
(send-url
(string-append
"http://planet.racket-lang.org/trac/newticket?component="
(this-package-version-owner) "%2F" (this-package-version-name)
"&planetversion=%28" (number->string (this-package-version-maj))
"+" (number->string (this-package-version-min)) "%29"
"&author=" (preferences:get 'drracket:email)
"&pltversion=" (version)
"&keywords=" (symbol->string (system-type 'os)) )))
(define menu-bar (send this get-menu-bar))
(define scripts-menu
(new menu% [parent menu-bar] [label "&Scripts"]
[demand-callback
(λ(m)
(for ([item (list-tail (send scripts-menu get-items) 2)])
(send item delete))
(define menu-hash (make-hash))
(for ([f (directory-list (script-dir))])
(let ([f-prop (build-path (script-dir) (string-append (path->string f) "d"))])
(with-handlers ([exn:fail? (λ(e)(error-message-box (path->string (file-name-from-path f-prop)) e))])
(when (and (member (filename-extension f) '(#"rkt"))
(file-exists? f-prop))
(with-input-from-file f-prop
(λ _
(let loop ([props (read)])
(when (and (dict? props) (prop-dict-ref props 'active))
(let*([label (dict-ref props 'label (path->string f))]
[functions (prop-dict-ref props 'functions)]
[shortcut (prop-dict-ref props 'shortcut)]
[shortcut-prefix (or (prop-dict-ref props 'shortcut-prefix)
(get-default-shortcut-prefix))]
[help-string (prop-dict-ref props 'help-string)]
[output-to (prop-dict-ref props 'output-to)]
[persistent (prop-dict-ref props 'persistent)]
[parent-menu (if (list? functions)
(hash-ref! menu-hash label
(λ _ (new menu% [parent scripts-menu]
[label label])))
scripts-menu)]
[label-functions (if (list? functions)
functions
(list (list functions label)))]
)
(for ([fun (map first label-functions)]
[label (map second label-functions)])
(if (eq? label 'separator)
(new separator-menu-item% [parent parent-menu])
(new menu-item% [parent parent-menu]
[label label]
[shortcut shortcut]
[shortcut-prefix shortcut-prefix]
[help-string help-string]
[callback (λ(it ev)
(run-script fun
(build-path (script-dir) f)
output-to
persistent))]))
))
(loop (read))
)))))))))]))
(define manage-menu (new menu% [parent scripts-menu] [label "Manage scripts"]))
(for ([(lbl cbk) (in-dict `(("New Script..." . ,new-script)
("Open Script..." . ,open-script)
("Open Script Properties..." . ,open-script-properties)
("Import Bundled Script..." . ,import-bundled-script)
(separator . #f)
("Unload persistent scripts" . ,unload-persistent-scripts)
(separator . #f)
("Help" . ,open-help)
("Feedback/Bug report..." . ,bug-report)
))])
(if (eq? lbl 'separator)
(new separator-menu-item% [parent manage-menu])
(new menu-item% [parent manage-menu] [label lbl]
[callback (λ _ (cbk))])))
(new separator-menu-item% [parent scripts-menu])
))
(define (phase1) (void))
(define (phase2) (void))
(drracket:get/extend:extend-unit-frame script-menu-mixin)
))