diff --git a/pkgs/drracket-pkgs/drracket/drracket/private/get-module-path.rkt b/pkgs/drracket-pkgs/drracket/drracket/private/get-module-path.rkt new file mode 100644 index 0000000000..1e8d279ed5 --- /dev/null +++ b/pkgs/drracket-pkgs/drracket/drracket/private/get-module-path.rkt @@ -0,0 +1,290 @@ +#lang racket/base +(require racket/class + racket/gui/base + string-constants) + +(module find-completions racket/base + (require racket/contract/base) + (provide + (contract-out + [find-completions (-> string? (listof (list/c string? path?)))])) + + (define (ignore? x) (member x '("compiled"))) + + (define (find-completions string) + (find-completions/internal string + (find-all-collection-dirs) + directory-list + directory-exists?)) + + (define (find-completions/internal string collection-dirs dir->content is-dir?) + (define segments (regexp-split #rx"/" string)) + (define first-candidates + (cond + [(null? segments) '()] + [else + (define reg (regexp + (string-append "^" + (regexp-quote (car segments)) + (if (null? (cdr segments)) + "" + "$")))) + (filter (λ (line) (regexp-match reg (list-ref line 0))) + collection-dirs)])) + (define unsorted + (let loop ([segments (cdr segments)] + [candidates first-candidates]) + (cond + [(null? segments) candidates] + [else + (define reg (regexp (string-append + "^" + (regexp-quote (car segments)) + (if (null? (cdr segments)) + "" + "$")))) + (define nexts + (for*/list ([key+candidate (in-list candidates)] + [candidate (in-value (list-ref key+candidate 1))] + #:when (is-dir? candidate) + [ent (in-list (dir->content candidate))] + [ent-str (in-value (path->string ent))] + #:unless (ignore? ent-str) + #:when (regexp-match reg ent-str)) + (list ent-str (build-path candidate ent)))) + (loop (cdr segments) nexts)]))) + (sort unsorted string<=? #:key (λ (x) (path->string (list-ref x 1))))) + + ;; -> (listof (list string? path?)) + ;; returns a list of all of the directories that are being treated as collections, + ;; (together with the names of the collections) + (define (find-all-collection-dirs) + ;; link-content : (listof (list (or/c 'root 'static-root string?) path?)) + (define link-content + (apply + append + (for/list ([link (in-list (current-library-collection-links))]) + (cond + [link + (define-values (base name dir?) (split-path link)) + (if (file-exists? link) + (for/list ([link-ent (call-with-input-file link read)] + #:when (if (= 3 (length link-ent)) + (regexp-match (list-ref link-ent 2) (version)) + #t)) + `(,(list-ref link-ent 0) + ,(simplify-path (build-path base (list-ref link-ent 1))))) + '())] + [else + (for/list ([clp (in-list (current-library-collection-paths))]) + `(root ,(simplify-path clp)))])))) + + (apply + append + (for/list ([just-one (in-list link-content)]) + (define-values (what pth) (apply values just-one)) + (cond + [(string? what) + (list just-one)] + [else + (for/list ([dir (in-list (directory-list pth))] + #:when (directory-exists? (build-path pth dir))) + (list (path->string dir) (build-path pth dir)))])))) + + (module+ test + (require rackunit + racket/list + racket/contract + racket/match) + + (define/contract find-completions/c + (-> string? (listof (list/c string? path?)) (-> path? (listof path?)) (-> path? boolean?) + (listof (list/c string? path?))) + find-completions/internal) + + (define coll-table + `(("racket" ,(string->path "/plt/pkgs/compatibility-pkgs/compatibility-lib/racket")) + ("racket" ,(string->path "/plt/pkgs/draw-pkgs/draw-lib/racket")) + ("racket" ,(string->path "/plt/racket/collects/racket")) + ("rackunit" ,(string->path "plt/pkgs/gui-pkgs/gui-lib/rackunit")))) + + (define (dir-list d) + (match (path->string d) + ["/plt/racket/collects/racket" + (map string->path '("list.rkt" "info.rkt" "include.rkt" "init.rkt" "gui"))] + ["/plt/racket/collects/racket/gui" + (map string->path '("dynamic.rkt"))] + ["/plt/pkgs/draw-pkgs/draw-lib/racket" + (map string->path '("gui"))] + ["/plt/pkgs/draw-pkgs/draw-lib/racket/gui" + (map string->path '("draw.rkt"))] + [_ '()])) + + (define (dir-exists? d) + (not (regexp-match #rx"rkt$" (path->string d)))) + + (check-equal? + (find-completions/c "rack/" coll-table dir-list dir-exists?) + '()) + + (check-equal? + (find-completions/c "rack" coll-table dir-list dir-exists?) + coll-table) + + (check-equal? + (find-completions/c "racku" coll-table dir-list dir-exists?) + (list (last coll-table))) + + (check-equal? + (find-completions/c "racket/i" coll-table dir-list dir-exists?) + (list (list "include.rkt" (string->path "/plt/racket/collects/racket/include.rkt")) + (list "info.rkt" (string->path "/plt/racket/collects/racket/info.rkt")) + (list "init.rkt" (string->path "/plt/racket/collects/racket/init.rkt")))) + + (check-equal? + (find-completions/c "racket/" coll-table dir-list dir-exists?) + (list (list "gui" (string->path "/plt/pkgs/draw-pkgs/draw-lib/racket/gui")) + (list "gui" (string->path "/plt/racket/collects/racket/gui")) + (list "include.rkt" (string->path "/plt/racket/collects/racket/include.rkt")) + (list "info.rkt" (string->path "/plt/racket/collects/racket/info.rkt")) + (list "init.rkt" (string->path "/plt/racket/collects/racket/init.rkt")) + (list "list.rkt" (string->path "/plt/racket/collects/racket/list.rkt")))) + + (check-equal? + (find-completions/c "racket/g" coll-table dir-list dir-exists?) + (list (list "gui" (string->path "/plt/pkgs/draw-pkgs/draw-lib/racket/gui")) + (list "gui" (string->path "/plt/racket/collects/racket/gui")))) + + (check-equal? + (find-completions/c "racket/gui/d" coll-table dir-list dir-exists?) + (list (list "draw.rkt" (string->path "/plt/pkgs/draw-pkgs/draw-lib/racket/gui/draw.rkt")) + (list "dynamic.rkt" (string->path "/plt/racket/collects/racket/gui/dynamic.rkt")))))) + +(module+ test (require (submod ".." find-completions test))) + +(require (submod "." find-completions) + racket/contract + framework) + +(provide + (contract-out + [get-module-path-from-user + (->* () (#:init string? #:pref symbol?) (or/c path? #f))])) + +(define (get-module-path-from-user #:init [init-value ""] #:pref [pref-sym #f]) + + (define dlg% + (class dialog% + (define/override (on-subwindow-char receiver event) + (cond + [(member (send event get-key-code) '(up down)) + (define old-sel (send lb get-selection)) + (define dir (if (equal? (send event get-key-code) 'up) + -1 + 1)) + (unless (= 0 (send lb get-number)) + (send lb set-selection + (cond + [old-sel + (modulo (+ old-sel + (if (equal? (send event get-key-code) 'up) + -1 + 1)) + (send lb get-number))] + [(equal? (send event get-key-code) 'up) + (- (send lb get-number) 1)] + [else + 0])))] + [else (super on-subwindow-char receiver event)])) + (super-new))) + + (define dlg (new dlg% [label ""][width 600][height 600])) + (define (tf-callback) + (adjust-lb) + (update-buttons)) + (define tf (new text-field% [parent dlg] [label #f] + [init-value init-value] + [callback (λ (tf evt) + (when pref-sym + (preferences:set pref-sym (send tf get-value))) + (tf-callback))])) + (define lb (new list-box% + [parent dlg] [choices '()] [label #f] + [callback (λ (lb evt) (update-buttons))])) + + (define (adjust-lb) + (send lb clear) + (unless (equal? (send tf get-value) "") + (for ([i (in-list (find-completions (send tf get-value)))] + [n (in-naturals)]) + (send lb append (path->string (list-ref i 1))) + ;; data holds a path => open the file + ;; data holds a string => add that past the last / in 'tf' + (cond + [(file-exists? (list-ref i 1)) + (send lb set-data n (list-ref i 1))] + [else + (send lb set-data n (list-ref i 0))])) + (when (= 1 (send lb get-number)) + (send lb set-selection 0)))) + + (define bp (new horizontal-panel% + [parent dlg] + [stretchable-height #f] + [alignment '(right center)])) + + (define cancelled? #t) + + (define (ok button evt) + (set! cancelled? #f) + (send dlg show #f)) + (define (cancel button evt) (send dlg show #f)) + (define (enter-sub button evt) + (define item-to-act-on (get-item-to-act-on)) + (define mtch (regexp-match #rx"(^.*/)[^/]*$" (send tf get-value))) + (define prefix + (if mtch + (list-ref mtch 1) + "")) + + (send tf set-value (string-append prefix + (send lb get-data item-to-act-on) + "/")) + (adjust-lb) + (update-buttons)) + + (define enter-sub-button (new button% + [parent bp] + [style '(border)] + [label (string-constant enter-subcollection)] + [callback enter-sub])) + + (define-values (ok-button cancel-button) (gui-utils:ok/cancel-buttons bp ok cancel)) + + (define (update-buttons) + (define item-to-act-on (get-item-to-act-on)) + (cond + [item-to-act-on + (define datum (send lb get-data item-to-act-on)) + (cond + [(path? datum) + (send ok-button enable #t) + (send enter-sub-button enable #f)] + [(string? datum) + (send ok-button enable #f) + (send enter-sub-button enable #t)])] + [else + (send ok-button enable #f) + (send enter-sub-button enable #f)])) + + (define (get-item-to-act-on) + (or (send lb get-selection) + (and (= 1 (send lb get-number)) + 0))) + + (adjust-lb) + (update-buttons) + (send dlg show #t) + (cond + [cancelled? #f] + [else (send lb get-data (get-item-to-act-on))])) diff --git a/pkgs/drracket-pkgs/drracket/drracket/private/main.rkt b/pkgs/drracket-pkgs/drracket/drracket/private/main.rkt index 36ee4228e1..a16540a340 100644 --- a/pkgs/drracket-pkgs/drracket/drracket/private/main.rkt +++ b/pkgs/drracket-pkgs/drracket/drracket/private/main.rkt @@ -75,6 +75,8 @@ (application:current-app-name (string-constant drscheme)) +(preferences:set-default 'drracket:open-module-path-last-used "" string?) + (preferences:set-default 'drracket:logger-receiver-string "error debug@GC debug@PLaneT" string?) (preferences:set-default 'drracket:logger-scroll-to-bottom? #t boolean?) diff --git a/pkgs/drracket-pkgs/drracket/drracket/private/unit.rkt b/pkgs/drracket-pkgs/drracket/drracket/private/unit.rkt index 6c6f230e6e..44a5cf02a3 100644 --- a/pkgs/drracket-pkgs/drracket/drracket/private/unit.rkt +++ b/pkgs/drracket-pkgs/drracket/drracket/private/unit.rkt @@ -26,6 +26,7 @@ "local-member-names.rkt" "eval-helpers.rkt" "parse-logger-args.rkt" + "get-module-path.rkt" (prefix-in drracket:arrow: "../arrow.rkt") (prefix-in icons: images/compile-time) mred @@ -3753,6 +3754,18 @@ (create-new-tab)))))) [define/override file-menu:between-open-and-revert (lambda (file-menu) + (new menu:can-restore-menu-item% + [label (string-constant open-collection-path)] + [shortcut #\o] + [shortcut-prefix (cons 'shift (get-default-shortcut-prefix))] + [parent file-menu] + [callback + (λ (x y) + (define pth + (get-module-path-from-user + #:init (preferences:get 'drracket:open-module-path-last-used) + #:pref 'drracket:open-module-path-last-used)) + (when pth (handler:edit-file pth)))]) (super file-menu:between-open-and-revert file-menu) (make-object separator-menu-item% file-menu))] (define close-tab-menu-item #f) diff --git a/pkgs/drracket-pkgs/drracket/scribblings/drracket/menus.scrbl b/pkgs/drracket-pkgs/drracket/scribblings/drracket/menus.scrbl index 520ca8468c..ce2e47843c 100644 --- a/pkgs/drracket-pkgs/drracket/scribblings/drracket/menus.scrbl +++ b/pkgs/drracket-pkgs/drracket/scribblings/drracket/menus.scrbl @@ -24,6 +24,11 @@ @item{@defmenuitem{Open Recent} Lists recently opened files. Choosing one of them opens that file for editing.} + @item{@defmenuitem{Open Collection Path...} Opens a dialog where you + can enter in a @racket[require]-like module path (e.g., + @litchar{racket/base.rkt} or @litchar{data/splay-tree.rkt}) + and edit the corresponding files in the @tech{definitions window}.} + @item{@defmenuitem{Install PLT File...} Opens a dialog asking for the location of the @filepath{.plt} file (either on the local disk or on the web) and installs the contents of the file.} diff --git a/pkgs/string-constants-pkgs/string-constants-lib/string-constants/private/english-string-constants.rkt b/pkgs/string-constants-pkgs/string-constants-lib/string-constants/private/english-string-constants.rkt index 3f8f874ca0..52fed05e00 100644 --- a/pkgs/string-constants-pkgs/string-constants-lib/string-constants/private/english-string-constants.rkt +++ b/pkgs/string-constants-pkgs/string-constants-lib/string-constants/private/english-string-constants.rkt @@ -1884,5 +1884,10 @@ please adhere to these guidelines: (install-pkg-package-catalogs "Package Catalogs") ; label for a list box (install-pkg-add-package-catalog "Add Package Catalog") + + ;; open a file via a collection path (new "Open" menu item in DrRacket) + (open-collection-path "Open Collection Path...") + (enter-subcollection "Enter subcollection") ; button in new dialog + )