#lang scribble/doc @(begin (require scribble/manual "common.rkt" scribble/racket (for-syntax racket/base "example-src.rkt") (for-label drracket/tool-lib) (for-label racket/unit racket/contract) (for-label racket/base racket/gui) (for-label framework/framework) (for-label drracket/syncheck-drracket-button drracket/check-syntax string-constants/string-constant) scribble/eval scribble/extract) (define (File x) @tt[x]) (define (FileFirst x) @tt[x]) ;; indexing missing (define-syntax-rule (item/cap x . ys) (item (indexed-racket x) ": " . ys))) @title{DrRacket Plugins} @author["Robert Bruce Findler"] @defmodule*[(drracket/tool-lib drscheme/tool-lib)] This manual describes DrRacket's plugins interface. It assumes familiarity with Racket, as described in the @(other-manual '(lib "scribblings/guide/guide.scrbl")), and the @(other-manual '(lib "scribblings/reference/reference.scrbl")), DrRacket, as described in @(other-manual '(lib "scribblings/drracket/drracket.scrbl")), and the GUI library, as described in @(other-manual '(lib "scribblings/gui/gui.scrbl")). The Framework, as described in @(other-manual '(lib "scribblings/framework/framework.scrbl")), may also come in handy. The @racketmodname[drscheme/tool-lib] library is for backward compatibility; it exports all of the bindings of @racketmodname[drracket/tool-lib]. @table-of-contents[] @bold{Thanks} Thanks to PLT and the early adopters of the tools interface for their feedback and help. A special thanks to Eli Barzilay, John Clements, Matthias Felleisen, Cormac Flanagan, Matthew Flatt, Max Hailperin, Philippe Meunier, and Christian Queinnec for their help being early clients for DrRacket plugins. @section[#:tag "implementing-tools"]{Implementing DrRacket Plugins} Plugins are designed for major extensions in DrRacket's functionality. To extend the appearance or the functionality the DrRacket window (say, to annotate programs in certain ways or to add buttons to the DrRacket frame) use a tool. The Macro Stepper, the Syntax Checker, the Stepper, and the teaching languages are all implemented as tools. When DrRacket starts up, it looks for tools by reading fields in the @File{info.rkt} file of each collection and the newest version of each PLaneT package installed on the system. (Technically, DrRacket looks in a cache of the @filepath{info.rkt} files contents created by @tt{raco setup}. Be sure to re-run @tt{raco setup} if you change the contents of the @File{info.rkt} files). DrRacket checks for these fields: @itemize[ @item/cap[drracket-tools]{ @racket[(listof (listof string[subcollection-name]))] } @item/cap[drracket-tool-names]{@racket[(listof (or/c #f string))]} @item/cap[drracket-tool-icons]{ @racketblock[(listof (or/c #f string[relative-pathname] (cons string[filename] (listof string[collection-name]))))] } @item/cap[drracket-tool-urls]{ @racket[(listof (or/c #f string[url]))] }] The @racket[drracket-tools] field names a list of tools in this collection. Each tool is specified as a collection path, relative to the collection where the @File{info.rkt} file resides. As an example, if there is only one tool named @File{tool.rkt}, this suffices: @racketblock[ (define drracket-tools (list (list "tool.rkt"))) ] If the @racket[drracket-tool-icons] or @racket[drracket-tool-names] fields are present, they must be the same length as @racket[drracket-tools]. The @racket[drracket-tool-icons] field specifies the path to an icon for each tool and the name of each tool. If it is @racket[#f], no tool is shown. If it is a relative pathname, it must refer to a bitmap and if it is a list of strings, it is treated the same as the arguments to @racket[lib], inside @racket[require]. This bitmap and the name show up in the about box, the bug report form, and the splash screen as the tool is loaded at DrRacket's startup. @index{phase1} @index{phase2} Each of the @racket[drracket-tools] files must contain a module that @racket[provide]s @racket[tool@], which must be bound to a @racket[unit]. The unit must import the @racket[drracket:tool^] signature, which is provided by the @FileFirst{tool.rkt} library in the @racket[drscheme] collection. The @as-index{@racket[drracket:tool^]} signature contains all of the names listed in this manual. The unit must export the @racket[drracket:tool-exports^] signature. The @as-index{@racket[drracket:tool-exports^]} signature contains two names: @racket[phase1] and @racket[phase2]. These names must be bound to thunks. After all of the tools are loaded, all of the @tt{phase1} functions are called and then all of the @tt{phase2} functions are called. Certain primitives can only be called during the dynamic extent of those calls. This mechanism is designed to support DrRacket's @racket[drracket:language:language<%>] extension capabilities. That is, this mechanism enables two tools to cooperate via new capabilities of languages. The first phase is used for adding functionality that each language must support and the second is used for creating instances of languages. As an example, a tool may require certain specialized language-specific information. It uses phase1 to extend the @racket[drracket:language:language<%>] interface and supply a default implementation of the interface extension. Then, other languages that are aware of the extension can supply non-default implementations of the additional functionality. Phase 1 functions: @itemize[ @item{@racket[drracket:language:extend-language-interface]} @item{@racket[drracket:unit:add-to-program-editor-mixin]} ] Phase 2 functions: @itemize[ @item{@racket[drracket:language-configuration:add-language]} @item{@racket[drracket:language:get-default-mixin]} @item{@racket[drracket:language:get-language-extensions]} ] If the tool raises an error as it is loaded, invoked, or as the @racket[phase1] or @racket[phase2] thunks are called, DrRacket catches the error and displays a message box. Then, DrRacket continues to start up, without the tool. For example, if the @File{info.rkt} file in a collection contains: @racketmod[ setup/infotab (define drracket-name "Tool Name") (define drracket-tools (list (list "tool.rkt"))) ] then the same collection would be expected to contain a @File{tool.rkt} file. It might contain something like this: @racketmod[ racket/gui (require drracket/tool) (provide tool@) (define tool@ (unit (import drracket:tool^) (export drracket:tool-exports^) (define (phase1) (message-box "tool example" "phase1")) (define (phase2) (message-box "tool example" "phase2")) (message-box "tool example" "unit invoked"))) ] This tool just opens a few windows to indicate that it has been loaded and that the @racket[phase1] and @racket[phase2] functions have been called. Finally, here is a more involved example. This module defines a plugin that adds a button to the DrRacket frame that, when clicked, reverses the contents of the definitions window. It also adds an easter egg. Whenever the definitions text is modified, it checks to see if the definitions text contains the text ``egg''. If so, it adds ``easter '' just before. @(let () (define-syntax-rule (define-linked-method name interface) (define-syntax name (make-element-id-transformer (lambda (stx) #'(method interface name))))) (define-linked-method begin-edit-sequence editor<%>) (define-linked-method end-edit-sequence editor<%>) (define-linked-method find-first-snip editor<%>) (define-linked-method on-insert text%) (define-linked-method on-delete text%) (define-linked-method after-insert text%) (define-linked-method after-delete text%) (define-linked-method insert text%) (define-linked-method get-text text%) (define-linked-method split-snip text%) (define-linked-method next snip%) (define-linked-method release-from-owner snip%) (define-linked-method change-children area-container<%>) (define-linked-method get-button-panel drracket:unit:frame%) (define-linked-method register-toolbar-button drracket:unit:frame<%>) (define-linked-method get-definitions-text drracket:unit:frame<%>) (define-linked-method erase dc<%>) (define-linked-method set-smoothing dc<%>) (define-linked-method set-pen dc<%>) (define-linked-method set-brush dc<%>) (define-linked-method draw-ellipse dc<%>) (define-linked-method set-bitmap bdc%) (define-syntax (get-src stx) (define file (list-ref files 1)) #`(racketmod #,@(let loop ([sw (list-ref file 1)]) (cond [(src-wrap? sw) (datum->syntax #'here (loop (src-wrap-obj sw)) (src-wrap-srcloc sw))] [(pair? sw) (cons (loop (car sw)) (loop (cdr sw)))] [else sw])))) (get-src)) @section[#:tag "adding-languages"]{Adding Languages to DrRacket} @index{adding languages to DrRacket} @subsection{@tt{#lang}-based Languages in DrRacket} If a language can be implemented as a module (see @racket[module] for details), then the simplest and best way to use the language is via the ``Use the language declared the in source'' checkbox in the @onscreen{Language} dialog. In this case, DrRacket's appearance can still be customized to the language; it uses @racket[read-language] with these arguments as the @racket[_key] argument to the @racket[_get-info] function to do so: @itemize[@item{@language-info-ref[drracket:toolbar-buttons]} @item{@language-info-ref[drracket:opt-out-toolbar-buttons]} @item{@language-info-ref[color-lexer]}] If the call to @racket[read-language] raises an error, DrRacket logs the error via @racket[log-debug]. @language-info-def[color-lexer]{ When a language's @racket[_get-info] procedure responds to @racket['color-lexer], it is expected to return a procedure suitable to pass as the @racket[_get-token] argument to @method[color:text<%> start-colorer]. } The recognized token styles (specified implicitly via @method[color:text<%> start-colorer]'s @racket[_token-sym->style] argument) are: @itemize[@item{@indexed-racket['symbol]} @item{@indexed-racket['keyword]} @item{@indexed-racket['comment]} @item{@indexed-racket['string]} @item{@indexed-racket['constant]} @item{@indexed-racket['parenthesis]} @item{@indexed-racket['error]} @item{@indexed-racket['other]}] These precise colors for these identifiers are controlled by the preferences dialog in DrRacket. @subsection{Adding Module-based Languages to DrRacket} For backwards compatibility, DrRacket also supports and @File{info.rkt} file-based method for specifying such languages. Include these definitions: @itemize[ @item/cap[drscheme-language-modules]{ This must be bound to a list of collection path specifications or strings, one for each language in the collection. Each collection path specification is the quoted form of what might appear as an argument to @racket[require], using the @tt{lib} argument (but without the @tt{lib}). The strings represent relative paths starting at the directory containing the @File{info.rkt} file. They are interpreted like string arguments to @racket[require]. } @item/cap[drscheme-language-positions]{ This must be bound to a list of language positions. Each language position corresponds to the position of the language in language dialog. Each language position is a list of strings whose length must be at least two. If the first string is the same as @racket[(string-constant teaching-languages)], then it is put into the ``Teaching Languages'' section of the dialog. Otherwise, it goes into the ``Other Languages'' section of the dialog. } @item/cap[drscheme-language-numbers]{ This is optional. If present, it must be a list of a list of numbers. Each list corresponds to a single language from this collection. Each number indicates a sorting order in the language dialog for the corresponding string in @racket[drscheme-language-positions]. If absent, it defaults to a list of zeros that has the same length as @racket[drscheme-language-positions]. This will rarely be correct. } @item/cap[drscheme-language-one-line-summaries]{ This is optional. If present, it must be a list of strings. Each string is displayed at the bottom of the language dialog when the corresponding language is selected. } @item/cap[drscheme-language-urls]{ This is optional. If present, it must be a list whose elements are either strings or @racket[#f]. Clicking the corresponding language's name in the interactions window opens a web browser to the url. } @item/cap[drscheme-language-readers]{ This is optional. If present, it must be bound to a quoted list of module specifications (that is, a quoted version of the argument to @racket[require]). Each specification must be a module that exports a function named @racket[read-syntax]. Each of these @racket[read-syntax] functions must match Racket's @racket[read-syntax] primitive's contract, but may read different concrete syntax. If the module specification is a plain string, it represents a relative path starting at the directory containing the @File{info.rkt} file. It is interpreted like the string arguments to @racket[require]. }] The lists must have the same length. As an example, the @italic{Essentials of Programming Languages} language specification's @File{info.rkt} used to look like this: @racketmod[ setup/infotab (require string-constants) (define name "EoPL Support") (define drscheme-language-modules (list "eopl-lang.rkt")) (define drscheme-language-positions (list (list (string-constant teaching-languages) "Essentials of Programming Languages"))) ] This @File{info.rkt} file indicates that there is a single language in this collection. The module that implements the language is the @File{eopl-lang.rkt} file in the same directory as the @File{info.rkt} file. Additionally, the language dialog will contain @tt{Essentials of Programming Languages} as a potential language. The use of the string constant @racket[teaching-languages] ensures that EoPL's language is placed properly in foreign language versions of DrRacket. For collections that define multiple (related) languages, if the language-positions contain multiple strings, the languages whose leading strings match are grouped together. That is, if two languages have strings: @racketblock[ '("My Text" "First Language") ] and @racketblock[ '("My Text" "Second Language") ] the two languages will be grouped together in the language dialog. @subsection{Adding Arbitrary Languages to DrRacket} With some additional work, any language that can be compiled to Racket is supported by the tools interface, not just those that use standard configurations and @racket[module]. Each language is a class that implement the @racket[drracket:language:language<%>] interface. DrRacket also provides two simpler interfaces: @racket[drracket:language:module-based-language<%>] and @racket[drracket:language:simple-module-based-language<%>], and @racket[mixin]s @racket[drracket:language:simple-module-based-language->module-based-language-mixin] and @racket[drracket:language:module-based-language->language-mixin] that build implementations of @racket[drracket:language:language<%>]s from these simpler interfaces. Once you have an implementation of the @racket[drracket:language:language<%>] interface, call @racket[drracket:language-configuration:add-language] to add the language to DrRacket. Each language comes with its own type, called @tt{settings}. This can be any type the language designer chooses, but to aid documentation, we call it @tt{settings} here. The settings type is expected to contain parameters of the language, such as case sensitivity, etc. The implementor of the language provides a GUI so the user can configure the settings and all of the language's operations accept a setting. DrRacket maintains the current settings for each language. @subsection{Language Extensions} Some tools may require additional functionality from the @racket[drracket:language:language<%>] interface. The @racket[drracket:language:extend-language-interface] function and the @racket[drracket:language:get-default-mixin] mixin make this possible. For example, the MrFlow tool expands a program, analyzes it and then displays sets of values for each program point. These sets of values should be rendered in the syntax of the language that MrFlow analyzes. Since MrFlow doesn't know which languages are available, it can call @racket[drracket:language:extend-language-interface] to extend the @racket[drracket:language:language<%>] interface with a method for rendering sets of values and provide a default implementation of that method. Tools that know about MrFlow can then override the value rendering method to provide a language-specific implementation of value rendering. Additionally, since the @racket[drracket:language:get-default-mixin] adds the default implementation for the value-set rendering method, all languages at least have some form of value-set rendering. In some cases, it is important for one tool to avoid depending on another in the manner above. For example, if a tool that provides a new language provides an implementation for the MrFlow-specific method, that tool may fail to load if MrFlow is not present (Indeed, with the tool manager, this can happen to any tool that depends on another in this manner.) To avoid this problem, consider writing your tool to first check to see if the base method is available before extending it. For example, if the MrFlow tool provides the @tt{render-value<%>} interface, then a tool that overrides that method can first test to see if the superclass implements that method before overriding it: @racketblock[ (define (my-language-mixin %) (if (implementation? % mrflow:render-value<%>) (class % (define/override ...) (super-new)) %)) ] To help test your tool, use the @seclink["environment-variables" #:doc '(lib "scribblings/drracket/drracket.scrbl")]{@tt{PLTONLYTOOL}} environment variable to load it in isolation. @section{Creating New Kinds of DrRacket Frames} Each frame in DrRacket has certain menus and functionality, most of which is achieved by using the framework. Additionally, there is one mixin that DrRacket provides to augment that. It is @racket[drracket:frame:basics-mixin]. Be sure to mix it into any new frame class that you add to DrRacket. @section{Extending the Existing DrRacket Classes} Each of the names: @itemize[ @item{@racket[drracket:get/extend:extend-interactions-text]} @item{@racket[drracket:get/extend:extend-definitions-text]} @item{@racket[drracket:get/extend:extend-interactions-canvas]} @item{@racket[drracket:get/extend:extend-definitions-canvas]} @item{@racket[drracket:get/extend:extend-unit-frame]} @item{@racket[drracket:get/extend:extend-tab]}] is bound to an extender function. In order to change the behavior of DrRacket, you can derive new classes from the standard classes for the frame, texts, canvases. Each extender accepts a function as input. The function it accepts must take a class as its argument and return a classes derived from that class as its result. For example: @racketblock[ (drracket:get/extend:extend-interactions-text (lambda (super%) (class super% (define/public (method1 x) ...) (super-new)))) ] extends the interactions text class with a method named @tt{method1}. @section[#:tag "Expanding and Breaking"]{Expanding the User's Program Text and Breaking} @index{expanding user programs} @index{breaking} @index{break button} Macro-expanding a program may involve arbitrary computation and requires the setup of the correct language. To aid this, DrRacket's tool interface provides @racket[drracket:eval:expand-program] to help. Use this method to extract the fully expanded program text in a particular language. Because expanding the user's program may require DrRacket to evaluate arbitrary code that the user wrote, tools that expand the user's program should also allow the user to break the expansion. To help with this, the tools interfaces provides these methods: @method[drracket:rep:context<%> enable-evaluation] and @method[drracket:rep:context<%> disable-evaluation]. Since your tool will be expanding the program text, you should be both overriding @method[drracket:rep:context<%> enable-evaluation] and @method[drracket:rep:context<%> disable-evaluation] to disable your tool and calling them to ensure that only one expansion is happening at a time. Finally, DrRacket provides the @method[drracket:rep:context<%> set-breakables] method. This method controls what behavior the Break button has. @section{Editor Modes} @index{modes} @index{scheme mode} @index{racket mode} DrRacket provides support for multiple editor modes. Tools register modes via @racket[drracket:modes:add-mode]. Each mode is visible in the @onscreen{Modes} submenu of the @onscreen{Edit} menu. Initially, DrRacket only supports two modes: Racket mode and text mode. DrRacket automatically selects a mode for each open file based on the file's extension. If the file ends with @File{.txt}, DrRacket uses text mode. Otherwise, DrRacket uses Racket mode. @section{Language-specific capabilities} @subsection[#:tag "drracket:lang-languages-customization"]{Customizing DrRacket's behavior} When using the language declared in the source, DrRacket queries that language via @racket[module-compiled-language-info] to determine if an expression in the interactions window is ready to be submitted to the evaluator (when the user types return). The info procedure is passed @racket['drracket:submit-predicate] and should return a function with this contract: @racketblock[(-> input-port? boolean? boolean?)] This function's first argument is a port that contains the interactions window's data, starting from the prompt position to the end of the editor. The second argument is a boolean indicating if the insertion point is followed only by whitespace. The results should be a boolean indicating if the expression should be evaluated. This function is called in sandbox, but with no filesystem or networking limits. @subsection{Customizing DrRacket's GUI} DrRacket's capability interface provides a mechanism for tools to allow languages to hide their GUI interface, if the tool does not apply to the language. Tools register capabilities keyed with symbols via. @racket[drracket:language:register-capability]. Once registered, a tool can query a language, via the @method[drracket:language:language<%> capability-value] method. The result from this method controls whether or not the tool shows this part of the GUI for DrRacket. See @racket[drracket:language:register-capability] for a list of the capabilities registered by default. @section{Check Syntax} Check Syntax is a part of the DrRacket collection, but is implemented via the tools API. @subsection{Accessing Check Syntax Programmatically} @defmodule[drracket/check-syntax] @defproc[(make-traversal [namespace namespace?] [path (or/c #f path-string?)]) (values (->* (syntax?) ((-> (and/c syntax? (λ (x) (define lst (syntax->list x)) (and lst (andmap identifier? lst)))) void?)) void?) (-> void?))]{ This function creates some local state about a traversal of syntax objects and returns two functions. The first one should be called with each of the (fully expanded) syntax objects that make up a program (there will be only one if the program is a module) and then the second one should be called to indicate there are no more. The optional argument to the first function is called for each sequence of binding identifiers encountered in @racket[define-values], @racket[define-syntaxes], and @racket[define-values-for-syntax]. During the dynamic extent of the call to the two result functions, the value of the @racket[current-annotations] parameter is consulted and various methods are invoked in the corresponding object (if any), to indicate what has been found in the syntax object. These methods will only be called if the syntax objects have source locations. } @defparam[current-annotations ca (or/c #f (is-a?/c syncheck-annotations<%>))]{ The methods of the value of this parameter are invoked by the functions returned from @racket[make-traversal]. } @definterface[syncheck-annotations<%> ()]{ Classes implementing this interface are accceptors of information about a traversal of syntax objects. See @racket[make-traversal]. Do not implement this interface directly, as it is liable to change without warning. Instead, use the @racket[annotations-mixin] and override the methods you're interested in. The @racket[annotations-mixin] will keep in sync with this interface, providing methods that ignore their arguments. @defmethod[(syncheck:find-source-object [stx syntax?]) (or/c #f (not/c #f))]{ This should return @racket[#f] if the source of this syntax object is uninteresting for annotations (if, for example, the only interesting annotations are those in the original file and this is a syntax object introduced by a macro and thus has a source location from some other file). Otherwise, it should return some (non-@racket[#f]) value that will then be passed to one of the other methods below as a @racket[_source-obj] argument. } @defmethod[(syncheck:add-background-color [source-obj (not/c #f)] [start exact-nonnegative-integer?] [end exact-nonnegative-integer?] [color string?]) void?]{ Called to indicate that the color @racket[color] should be drawn on the background of the given range in the editor, when the mouse moves over it. This method is typically called in conjuction with some other method that provides some other annotation on the source. } @defmethod[(syncheck:add-require-open-menu [source-obj (not/c #f)] [start exact-nonnegative-integer?] [end exact-nonnegative-integer?] [file path-string?]) void?]{ Called to indicate that there is a @racket[require] at the location from @racket[start] to @racket[end], and that it corresponds to @racket[file]. Check Syntax adds a popup menu. } @defmethod[(syncheck:add-docs-menu [source-obj (not/c #f)] [start exact-nonnegative-integer?] [end exact-nonnegative-integer?] [id symbol?] [label any/c] [path any/c] [tag any/c]) void?]{ Called to indicate that there is something that has documentation between the range @racket[start] and @racket[end]. The documented identifier's name is given by @racket[id] and the docs are found in the html file @racket[path] at the html tag @racket[tag]. The @racket[label] argument describes the binding for use in the menu item (although it may be longer than 200 characters). } @defmethod[(syncheck:add-rename-menu [id symbol?] [all-ids (listof (list/c (not/c #f) exact-nonnegative-integer? exact-nonnegative-integer?))] [new-name-interferes? (-> symbol boolean?)]) void?]{ Called to indicate that there is a variable that can be renamed. The identifier's name is @racket[id] and all of the occurrences of the identifier are given in the list @racket[all-ids]. The @racket[new-name-interferes?] procedure determines if a potential name would interfere with the existing bindings. } @defmethod[(syncheck:add-arrow [start-source-obj (not/c #f)] [start-left exact-nonnegative-integer?] [start-right exact-nonnegative-integer?] [end-source-obj (not/c #f)] [end-left exact-nonnegative-integer?] [end-right exact-nonnegative-integer?] [actual? boolean?] [phase-level (or/c exact-nonnegative-integer? #f)]) void?]{ Called to indicate that there should be an arrow between the locations described by the first six arguments. The @racket[phase-level] argument indicates the phase of the binding and the @racket[actual?] argument indicates if the binding is a real one, or a predicted one from a syntax template (predicted bindings are drawn with question marks in Check Syntax). } @defmethod[(syncheck:add-tail-arrow [from-source-obj (not/c #f)] [from-pos exact-nonnegative-integer?] [to-source-obj (not/c #f)] [to-pos exact-nonnegative-integer?]) void?]{ Called to indicate that there are two expressions, beginning at @racket[from-pos] and @racket[to-pos] that are in tail position with respect to each other. } @defmethod[(syncheck:add-mouse-over-status [source-obj (not/c #f)] [pos-left exact-nonnegative-integer?] [pos-right exact-nonnegative-integer?] [str string?]) void?]{ Called to indicate that the message in @racket[str] should be shown when the mouse passes over the given position. } @defmethod[(syncheck:add-jump-to-definition [source-obj (not/c #f)] [start exact-nonnegative-integer?] [end exact-nonnegative-integer?] [id any/c] [filename path-string?]) void?]{ Called to indicate that there is some identifier at the given location (named @racket[id]) that is defined in the file @racket[filename]. } @defmethod[(syncheck:color-range [source-obj (not/c #f)] [start exact-nonnegative-integer?] [finish exact-nonnegative-integer?] [style-name any/c] [mode any/c]) void?]{ Called to indicate that the given location should be colored according to the style @racket[style-name] when in @racket[mode]. The mode either indicates regular check syntax or is used indicate blame for potential contract violations (and still experimental). } } @defmixin[annotations-mixin () (syncheck-annotations<%>)]{ Supplies all of the methods in @racket[syncheck-annotations<%>] with default behavior. Be sure to use this mixin to future-proof your code and then override the methods you're interested in. The @racket[syncheck:find-source-object] method ignores its arguments and returns @racket[#f]; all of the other methods ignore their arguments and return @racket[(void)]. @examples[#:eval (let ([evaluator (make-base-eval)]) (evaluator '(require drracket/check-syntax)) evaluator) (require racket/class) (define arrows-collector% (class (annotations-mixin object%) (super-new) (define/override (syncheck:find-source-object stx) stx) (define/override (syncheck:add-arrow start-source-obj start-left start-right end-source-obj end-left end-right actual? phase-level) (set! arrows (cons (list start-source-obj end-source-obj) arrows))) (define arrows '()) (define/public (collected-arrows) arrows))) (define (arrows form) (define base-namespace (make-base-namespace)) (define-values (add-syntax done) (make-traversal base-namespace #f)) (define collector (new arrows-collector%)) (parameterize ([current-annotations collector] [current-namespace base-namespace]) (add-syntax (expand form)) (done)) (send collector collected-arrows)) (define (make-id name pos orig?) (datum->syntax #f name (list #f #f #f pos (string-length (symbol->string name))) (and orig? #'is-orig))) (arrows `(λ (,(make-id 'x 1 #t)) ,(make-id 'x 2 #t))) (arrows `(λ (x) x)) (arrows `(λ (,(make-id 'x 1 #f)) ,(make-id 'x 2 #t))) (arrows `(λ (,(make-id 'x 1 #t)) x))] } @(define-syntax-rule (syncheck-method-id x ...) (begin @defidform[x]{Bound to an identifier created with @racket[define-local-member-name] that is used in @racket[syncheck-annotations<%>].} ...)) @syncheck-method-id[syncheck:find-source-object syncheck:add-background-color syncheck:add-require-open-menu syncheck:add-docs-menu syncheck:add-rename-menu syncheck:add-arrow syncheck:add-tail-arrow syncheck:add-mouse-over-status syncheck:add-jump-to-definition syncheck:color-range] @subsection{Check Syntax Button} @defmodule[drracket/syncheck-drracket-button] @defthing[syncheck-drracket-button (list/c string? (is-a?/c bitmap%) (-> (is-a?/c top-level-window<%>) any))]{ This is meant to be used with the @racket['drracket:toolbar-buttons] argument to the info proc returned from @racket[read-language]. } @defidform[syncheck:button-callback]{ This is defined with @racket[define-local-member-name] and is bound to a method of no arguments of the DrRacket frame that runs Check Syntax. } @defthing[syncheck-bitmap (is-a?/c bitmap%)]{ The bitmap in the Check Syntax button on the DrRacket frame. } @subsection{Disappeared uses and bindings} @section-index["disappeared-use" "disappeared-binding"] Check Syntax collects the values of the @racket[syntax-property]s named @racket['disappeared-use] and @racket['disappeared-binding] and uses them to add additional arrows to the program text. These properties are intended for use when a macro discards identifiers that, from the programmers perspective, should be binding each other. For example, here is a macro that discards its arguments, but adds properties to the result syntax object so the arguments are treated as a binding/bound pair by Check Syntax. @racketblock[ (define-syntax (m stx) (syntax-case stx () [(_ id1 id2) (and (identifier? #'id1) (identifier? #'id2)) (syntax-property (syntax-property #'1 'disappeared-use (list (syntax-local-introduce #'id1))) 'disappeared-binding (list (syntax-local-introduce #'id2)))]))] See also @racket[current-recorded-disappeared-uses]. @section{Teaching Languages} The teaching language are implemented via the tools interface and thus not part of DrRacket proper, but one helper library is documented here. @defmodule[lang/htdp-langs-save-file-prefix] @defthing[htdp-save-file-prefix (listof string?)]{ These strings are used as the prefix in a file saved while using the teaching languages. Each string is on a separate line in the saved file. } @defproc[(htdp-file-prefix? [ip input-port?]) boolean?]{ Determines if the contents of @racket[ip] is one of the possible prefixes that DrRacket saves at the beginning of a teaching language file. In the case that this function returns @racket[#t], it consumes the entire prefix from @racket[ip] (and discards it). In the case that this function returns @racket[#f], it does not consume anything from @racket[ip]. } @include-section["get-slash-extend.scrbl"] @include-section["unit.scrbl"] @include-section["language.scrbl"] @include-section["language-configuration.scrbl"] @include-section["debug.scrbl"] @include-section["rep.scrbl"] @include-section["frame.scrbl"] @include-section["help-desk.scrbl"] @include-section["eval.scrbl"] @include-section["modes.scrbl"] @include-section["module-language-tools.scrbl"] @include-section["module-language.scrbl"] @include-section["documentation-utils.scrbl"] @section{Backwards compatibility} This section lists the bindings that begin with @tt{drscheme:} provided by the tools library; they are here for backwards compatibility and to provide links to the @tt{drracket:} versions of the names. @(require drracket/private/drsig (for-syntax racket/base racket/unit-exptime)) @(define-syntax (drs-compat stx) (let-values ([(drs-parent drs-vars drs-var-defs-in-sig drs-stx-defs-in-sig) (signature-members #'drscheme:tool-cm^ #'here)] [(drr-parent drr-vars drr-var-defs-in-sig drr-stx-defs-in-sig) (signature-members #'drracket:tool-cm^ #'here)]) (with-syntax ([(drs-id ...) drs-vars] [(drr-id ...) drr-vars]) #'(begin (defthing drs-id any/c "This is provided for backwards compatibility; new code should use " (racket drr-id) " instead.") ...)))) @drs-compat[] @(tools-include/drs "debug") @(tools-include/drs "eval") @(tools-include/drs "frame") @(tools-include/drs "get/extend") @(tools-include/drs "help-desk") @(tools-include/drs "language-configuration") @(tools-include/drs "language") @(tools-include/drs "modes") @(tools-include/drs "module-language-tools") @(tools-include/drs "module-language") @(tools-include/drs "rep") @(tools-include/drs "unit") @index-section[]