IntelliSense in Visual FoxPro 7.0 

 

July 2002

Daryl Moore, Hazen Hills Software
Andy Kramek, Tightline Computers

Applies to:
     Visual FoxPro 7.0

Summary

Learn how IntelliSense is implemented in Visual FoxPro 7.0 and how to take advantage of its potential. IntelliSense™ was first introduced in Microsoft® Visual Basic 5.0™ and has been widely implemented as an essential developer productivity enhancement in many languages. The implementation in Visual FoxPro 7.0 is unique among them in that it features an open architecture that allows developers to both customize and extend the built-in features.

Contents

Introduction
Basic Features of IntelliSense
Quick Info in Editing Windows
Quick Info in the Command Window
Member Lists
Configuring Intellisense
The FoxCode Table
Creating and Using Scripts

Introduction

The term "IntelliSense" refers to the functionality that, when a developer is working in a code editing window, provides auto-completion of commands and context sensitive, as-you-type, help with syntax. This functionality has long been available in other Microsoft developer tools, but has been conspicuously absent from Visual FoxPro. IntelliSense was finally introduced in to Visual FoxPro with the release of Version 7.0

The implementation of IntelliSense in Visual FoxPro 7.0 provides all of the standard features, and some new ones. However, because the implementation is data driven it is possible to both alter the default behavior and to create entirely new behaviors. This paper explains how IntelliSense has been implemented in Visual FoxPro 7.0 and illustrates how developers can take advantage of its open architecture to make their lives easier, and increase their productivity.

Basic Features of IntelliSense

The basic functionality offered in Visual FoxPro 7.0 is illustrated in the following series of figures. There are two main types of functionality that are provided as standard in Visual FoxPro 7.0. First there is information that is linked to the commands and functions that are typed. This includes the Auto-Completion of key words, commands and functions, and the various formats of context sensitive help that is associated with them (see "Quick Info" entries). Second, there is the dynamic and interactive presentation of information for classes and objects (see "Member Lists").

Quick Info in Editing Windows

When working in either a code editing window (a program file or method of a class or form) or the command window all of the following features are available by default.

Figure 1: Syntax highlighting of partial commands…

Figure 1 shows how, when writing code, Visual FoxPro 7.0 recognizes partial commands and highlights them using the settings defined for syntax coloring in the 'Editor' tab of the Options dialog. There is nothing new in this but if, as soon as the code is highlighted, the space key is pressed, Visual FoxPro 7.0 completes the command and displays any associated "Quick Info" in a tooltip (Figure 2) .

Figure 2: Auto-Complete and Quick Info tooltip

Quick Info comes in several forms, depending on the context. In Figure 2 we can see that it is telling us that the next thing expected is a 'ParameterList'. However, this is not the only format for Quick Info which is also used to pop up context sensitive extracts from the help file (Figure 3).

Figure 3: Context sensitive help as code is written

The next line of code is a declaration and, after entering the key word and pressing the space key, the associated help text for the command appears. Notice that in this particular case we could not use auto-complete for the command because the first four characters ("LOCA") are actually assigned to the LOCATE command.

The next type of Quick Info that is available is a "Values" list (Figure 4) which is generated automatically for commands which have multiple options.

Figure 4: Quick Info Value List and Value Tip

Notice the additional "Value Tip" that is displayed as the list contents are scrolled. A variant of the basic value list is shown in figure 5 where the available options for a SET command are displayed, and the value tip is used to indicate which option is currently set. Similar lists are available for settings that accept True/False values.

Figure 5:Quick Info List of current and available settings

All of the examples so far are triggered when the space key is pressed, but this is not the only way to trigger IntelliSense. When entering a function, typing the left parenthesis key triggers a Quick Info "smart" tip (Figure 6).

Figure 6: Smart tips track position as code is entered

Notice that although all of the parameters for the StrTran() function are displayed in the Quick Info tip, only the second one (cSearchFor) is highlighted in bold. It is no coincidence that this is the parameter that is about to be entered in the code window. The IntelliSense engine tracks the commas that separate the parameters to synchronize the highlight in the Tip Window with the current position in the code window.

Quick Info in the Command Window

There are two more types of Quick Info which are available, but unlike all the preceding examples, they are only available when working interactively in the Command Window. The first is the "Most Recently Used" (MRU) list (Figure 7).

Figure 7: Most Recently Used (MRU) List – available in command window only

As the name implies, MRU lists contain the names, and fully qualified path, of the items of the appropriate type that were most recently opened. Separate MRU lists are maintained for the file types specified by the USE, MODIFY, OPEN, REPORT, LABEL and DO commands. Notice, by the way, that the MRU list always includes the fully qualified path and file name. If the next command entered into the command window were simply "USE fixlist", the entry that gets added to the MRU would still be "D:\VFP70\Run\fixlist.dbf".

The number of entries kept in these lists is static, which means that once the defined number of entries has been reached, the oldest entry is dropped each time a new items has to be added. The limit is set on the "View" tab of the Options Dialog and also controls the number of projects that are retained on the File pad of the main system menu.

A variant of the standard MRU list which lists fields in the currently open table is available when using the REPLACE command from the command window (Figure 8)

Figure 8: MRU for the REPLACE command (Command Window only)

Instead of displaying a list of files, the IntelliSense engine builds a list of the fields in the currently selected table, and includes the data type and size information in the value tip for each field.

Note: The same field list is triggered by typing the period when referencing any currently open table in the command window using the <alias>.<field> syntax.

The second type of special list which is available only in the command window displays all variables that are currently defined, and are in scope, when the shortcut "m." is entered, followed by a space (Figure 9).

Figure 9: Defined Variable List (Command Window only)

In this list, the Value Tip is used to display the contents of the variable. Object references are shown as either "(Object)" or ".NULL." depending on whether the object is actually instantiated or not.

Member Lists

Member lists are available in both editing and command windows for objects which have been instantiated and are currently in scope (Figure 10).

The list includes all the properties, events and methods (PEM) of an object, and each type of entry has its own distinctive icon. The Value Tip of a member list displays the comment that is associated with PEM (and even display the comments for PEMs created in user defined classes).

Figure 10: Object Member List

Member lists are evaluated dynamically so that selecting a contained object will display the members of that object and so on. Figure 11 shows the members list for the child of child of a contained object in the oDM object.

Figure 11: Member lists are built dynamically

This would, in itself be useful, but IntelliSense can do much more thanks to another new feature introduced in Visual FoxPro 7.0, "Design Time Strong Typing". Visual FoxPro has always been a "weakly typed" language. This means that there is no requirement to declare variables explicitly, and that variables, once defined can change their type as needed. This is an enormously powerful, and flexible approach which has significant benefits for a data centric development tool. However, as always, there are drawbacks. One is that the system can never 'know' what a variable is being used for, and therefore can neither assumptions about it, nor use it as a source of information.

Visual FoxPro 7.0 now allows the use of strong typing in certain places, at design time only, in order to support IntelliSense. It is implemented by using the new AS clause which can be applied to classes, parameters or variables to indicate to the IntelliSense engine how a reference is to be handled. There are a number of possible ways of using it.

Figure 12: Defining a local variable as a VFP Base Class

First it can be used to define objects and variables as instances of other Visual FoxPro classes. These can be native base classes as shown in Figure 12 where the local variable loCombo is defined as a combobox and then, in the code the reference is used inside a WITH…ENDWITH construct to access the members list directly. When coding is finished, a global search and replace removes "loCombo" leaving just the ".". Since Strong Typing is only enforced at Design time the declaration can be left in place in the code without causing any problems at run time.

The same technique can be used with custom classes (Figure 13). Note that in order to build member lists for classes they must either have been registered with IntelliSense (see below) or be specified using the OF <class library> clause as illustrated.

Figure 13: Using a custom class with IntelliSense

However, IntelliSense is not limited to Visual FoxPro classes. Any class, or application, that exposes a Type Library can be accessed by the IntelliSense engine to build a Member list. (Figure 14).

Figure 14: Exploring the Word Object model with Intellisense

This provides an alternative to using the Object Browser to discover how to access and control OLE Server applications, COM components and ActiveX controls.

Strong typing also allows for defining the return value and parameter data types for Functions and Procedures in class definitions. In this situation the members list display all of the available data types and objects that have been defined to IntelliSense (Figure 15)

Figure 15: Declaring Data Types

This information is automatically displayed by IntelliSense as Quick Info whenever the resulting method is accessed (Figure 16). Notice that is doesn't matter to IntelliSense whether the class library is defined visually, or in a program file.

Figure 16: User Defined Parameter Data Types in Quick Info

Configuring Intellisense

There are two ways of configuring IntelliSense in Visual FoxPro 7.0.

The first is by setting the contents of a new property on the VFP application object named "EditorOptions". The default setting is "LQKT" although there actually are five features that can be controlled through this property, as follows:

  • Hyperlinks ("K or k"). Determines how links are activated. Setting to "k" requires only a single mouse click, while "K" requires that CTRL+Click is used - which is the default. If neither is specified, hyperlinks are treated as normal text in editing or command windows.
  • Word Drag 'n' Drop ("W"). When enabled, text can only be dragged to a position immediately following a space. Prevents text being (inadvertently) inserted into existing text when using drag and drop in an editor or the command window. This behavior is disabled by default.
  • Designer Value Tips ("T"). This controls whether the items displayed in lists show their associated 'tool tips'. By default, they are shown as you scroll through member lists.
  • List Members ("L or l"). This controls whether, and when, the object member lists are displayed. By default, lists are automatic ("L") though you may prefer to use the lower case "l" option to suppress the automatic display, but still have the list pop up when you press CTRL+J (or select 'List Members' from the "Edit" pad on the main FoxPro menu).
  • Quick Info ("Q or q"). This controls whether, and when, the various types of Quick Info is displayed. By default, the display is automatic ("Q") though you may prefer to use the lower case "q" option to suppress the automatic display, but still have the Quick Info available when you press CTRL+I (or select 'Quick Info' from the "Edit" pad on the main FoxPro menu).

In order to enable all features of IntelliSense (including the controlling text drag and drop) simply enter the following at the command window:

_VFP.EditorOptions = "LQKTW"

or whatever combination of settings you wish to apply.

The second way is to use the new 'IntelliSense Manager' form which is accessible from the Tools pad of the main system menu. This form has four pages and not only gives access to the same configuration details described above, but also to other, IntelliSense related functionality through the FoxCode table that drives IntelliSense.

The first page (Figure 17) provides an interactive way to set the _VFP.EditorOptions property values for Member Lists and Quick Info. It also defines how IntelliSense should handle the case used for Commands, Functions and defines a default value to be used whenever there is no specific instruction. The checkbox determines whether the settings defined are applied to native Visual FoxPro commands and functions only, or whether they should be applied to user-defined commands and functions also. Buttons activate the Tips window and browse the FoxCode table that stores the information used by IntelliSense.

Figure 17: IntelliSense Configuration Page

The second "Types" page (Figure 18) controls the entries that are to generate the list of types that are displayed when the AS clause is used. Visual FoxPro 7.0 ships with a 'core' set that includes all of the Visual FoxPro base classes, and all standard data

Figure 18: IntelliSense Type List

types, whether they are actually available in Visual FoxPro or not. Clearing the check box prevents an item from being displayed in type lists.

The list of available types can be extended using the buttons on this page.

  • Edit opens a browse window that displays the entry for the currently selected item from the FoxCode table. Mainly used to amend the display name for an item
  • Type Libraries opens a dialog that lists registered COM servers, ActiveX controls or both and allows them to be added to the Type List (Figure 19)

Figure 19: Adding a Type Library to the IntelliSense Type List

Classes opens the standard Visual FoxPro visual class library dialog and allows custom classes to be added to the IntelliSense Type List (Figure 20).

Click here to see larger image

Figure 20: Adding a custom class to the IntelliSense Type List
Note that classes that have defined in program files can be included in the Type List, but the appropriate record must be added to the FoxCode table manually
  • Web Services invokes the Web Service Registration wizard and adds the specified web service to the Type List (Figure 21)

Figure 21: Adding a Web Service to the IntelliSense Type List

The third "Custom" page (Figure 22), provides a filterable view of that can be used to add, edit or delete custom entries in the FoxCode table.

Figure 22: FoxCode Table Editor

The next section of this paper deals with the FoxCode table in detail.

The final "Advanced" page (Figure 23) gives access to two additional items, Additional Properties and Cleanup. The additional properties are covered in detail in the next section of this paper.

Figure 23: IntelliSense Manager Advanced Options

The cleanup button brings forward a maintenance dialog for the FoxCode table and Most Recently Used (MRU) lists (Figure 24).

Figure 24: FoxCode Table and MRU List maintenance dialog

For more details see the Visual FoxPro Reference.

The FoxCode Table

This table, which is installed, by default, in the personal 'user application folder' on your local drive, lies at the heart of the IntelliSense implementation in Visual FoxPro 7.0. The IntelliSense functionality can be customized and enhanced by manipulating the contents of this table. Therefore understanding how this table is structured and used is crucial to working with IntelliSense in Visual FoxPro.

Table 1: Structure and Usage for FoxCode.dbf

Field Defined Description
Type C (1) Identifier which defines how the record should be processed:
  • C (Command)Auto-complete items. Triggered by " "
  • F (Function)Quick Info items. Triggered by "("
  • O (COM)Type Library, used for Type List
  • P (Property)Action when a property is accessed
  • S (Script)Execute script from the Data Field
  • T (Type)Declarations, used for Type List
  • U (User)User-defined
  • V (Version)Reserved for default/version data
  • Z (Special)Defines custom behaviors
Abbrev C (24) Shortcut that triggers an action
Expanded C (26) Expansion or Replacement text for shortcut where needed
Cmd C (15) The name of a script to execute. Enclosed in "{}"
Tip M ( 4) Quick Tip display information
Data M ( 4) The content for this record may include List values, code, script text etc
Case C ( 1) Specifies how Expanded text is formatted to replace abbreviated text

U = use Upper() function to format

L = use Lower() function to format

P = use Proper() function to format

M = No formatting applied (Mixed case)

X = No replacement applied

Note: The value specified in the 'Version' record defines the default to be used for any record that does not have its own setting.

Save L (1) Flag to indicate whether record is preserved during updates to the FoxCode table (False for VFP items)
TimeStamp T (8) Timestamp (VFP items Only)
Source M (4) Source for record content. (VFP items use "Reserved")
UniqueID C (10) Unique ID (VFP items only)
User M ( 4) Available for user-defined information

The Advanced page (Figure 24) of the IntelliSense Manager form includes options to restore and clean up the FoxCode table. If the table gets damaged, or an entry is inadvertently deleted or changed, the table can be restored to its original state. The TimeStamp, UniqueID and Save fields are checked when the FoxCode table is packed updated or restored to determine the origin of the data and whether it may be overwritten. By default the native Visual FoxPro entries have both a time stamp and a unique ID, but their Save field is set to False so that they can be overwritten. User defined entries, on the other hand, do not have (or need) either a unique ID or a time stamp. Providing that their Save field is set to True, they will not be overwritten or deleted by changes to the table.

The various record types, together with an example of using each, are detailed below:

Version Record (Type = "V")

The FoxCode table includes a single record of this type. It is intended for internal use by the IntelliSense engine. The Expanded field contains the version number for the current FoxCode table, and the Case field defines the default formatting to be applied to any item that requires one, but does not have one set explicitly.

Command Record (Type = "C")

This Command Type defines an auto-complete entry that is triggered by the space character and that is, by default, executed by calling the "Default Script" (this is defined in the Data field of the record with Type = S and an empty Abbrev field). All of the basic VFP commands use this methodology to concatenate the content of the Expanded field to the abbreviated form of the command.

The other, more complex, commands invoke a generic command handler script ({cmdhandler}) by specifying it in their Cmd field. This script replaces the content of the Abbrev field with the content of the Expanded field rather than concatenating them. This methodology can easily be used to create "custom commands" that explicitly associate Quick Info (from the Tip field) or a Members List (from the Data Field) by defining an abbreviation and including a call to the command handler script.

Example: The default behavior of the ON command auto-completion is to display a list of all the options for the "ON" command, that includes all the print and menu options. However, in general coding, the only ON command that is used often is ON KEY LABEL. To create an auto-complete command for the string "OKL" that expands to "ON KEY LABEL " when followed by a space, add a record to the FoxCode table as follows:

Type Abbrev Expanded Cmd Tip Case Save
C OKL on key label {cmdhandler} ON KEY [LABEL KeyLabelName] [Command] U .T.

Function Record (Type = "F")

The Function defines an auto-complete entry that is triggered by the left parenthesis character "(". The contents of the Tip field are used to display the 'smart' Quick Info tips that track parameter entry by matching the pattern of the text you type with that defined in the record.

Example: To create an auto-complete entry for a user-defined function named OpenFile() triggered by typing "OPF(" add a record to the FoxCode table as follows:

Type Abbrev Expanded Tip Case Save
F OPF OpenFile cFileName[, cFileDir[, lReadOnly]] M .T.

This defines the function as having three parameters. As commas are added to the typed text, the highlighted tip is synchronized automatically.

Property Record (Type = "P")

A Property record is used to assign a pop-up dialog (or value list) that is displayed whenever a value is assigned to any property on any object whose name matches the entry in the Abbrev field. The Cmd Field is used to indicate whether a script defined elsewhere in the FoxCode table, or the contents of the Data field in the current record, are to be used to generate pop-up. The FoxCode table ships with two generic scripts that are used with this record type. The first (named {color}) displays the color picker dialog and is associated with a number of color definition properties (e.g. "BackColor", "BorderColor" and "FillColor"). The second (named {picture}) displays the open picture dialog whenever either an "Icon" or "Picture" property is assigned.

Example: To assign the default color picker to a custom property name simply add a record to the FoxCode table, substituting your own property name (note that the leading "." is required in the Abbrev field) as appropriate:

Type Abbrev Cmd Case Save
P .MyColor {color} M .T.

The use of scripts is covered later in this paper in more detail. However, for now suffice it to say that a script may be written directly into the Data field of a record and executed by setting the Cmd field to contain empty braces ("{}"). To add a script that displays the GetFile() dialog and returns the selected file name for a custom property named ".cSourceFile", add a record to the foxcode table as follows:

Type Abbrev Cmd Data Case Save
P .cSourceFile {}
LPARAMETER oFoxCode
LOCAL lcTxt
oFoxcode.valuetype = "V"
lcTxt = ['] +  GETFILE() + [']
RETURN lcTxt
M .T.

Whenever a property named cSourceFile is assigned a value, the GetFile() dialog will be automatically displayed. (Remember, IntelliSense only operates in the development environment, nothing happens at run time).

COM Component Record (Type = "O")

A COM record is used to define, to the IntelliSense engine, where to find the Type Library for a COM Component (or ActiveX control). The Data field is used to store the GUID and Version information, and the Tip field holds the full name of the control. Whatever is contained in the Abbrev field is used as the entry for the Type List associated with an AS clause (DEFINE CLASS…AS… , LOCAL…AS… ).

Example: To add the Microsoft TreeView control as an option in the Type List, add a record to the FoxCode table as follows(

Type Abbrev Tip Data Save
O TreeView Microsoft TreeView Control 6.0 (SP4) {831FDD16-0C5C-11D2-A9FC-0000F8754DA1}#2.0 .T.

Note that the Data field must contain the correct GUID which is obtainable from the registry. However, the easiest way to create a record of this type is to use the 'Type Libraries' option on the second page of the IntelliSense Manager and then edit the default name (if necessary) using the editor on the third page.

Typing Record (Type = "T")

A Typing record is sued to define to IntelliSense an entry that does not have a type library but is still to be included in the Type List. Since no type library is required, this can be used to add custom classes to the list. The content of the Data field is displayed directly in the drop down list and this is the only field that needs to be completed. However, adding a description to the Abbrev field makes maintaining the FoxCode table easier.

For visual classes, the 'Classes' option on the second page of the IntelliSense Manager can be used. However for programmatically defined classes the necessary record must be added manually.

Type Abbrev Data Save
T Container basecnt OF X:\Projects\Libs\base.vcx" .T.
T Header basehdr OF X:\Projects\Libs\NonVisClass.prg .T.

User Record (Type = "U")

The User type record identifies the contents as a "user-defined" auto text entry that, like a Command entry, is triggered by typing the space character. The difference is that the default behavior for a User record is to replace the content of the Abbrev field with the content of the Expanded field. There is no need to call the command handler script and this is, therefore the preferred method for creating user defined shortcuts.

Example: To create an expansion of the 'cright' shortcut, add a record to the Foxcode table as follows:

Type Abbrev Expanded Case Save
U cright © 2002 Microsoft Corporation X .T.

User Type records can also call scripts. The Cmd field is used to indicate where the script is located. A pair of empty braces means that the script is embedded in the current record, while specifying a name indicates that the script is defined in a Script Record elsewhere in the table. The following variation on the shortcut uses an embedded script to achieve exactly the same result:

Type Abbrev Cmd Data Case Save
U cright {}
LPARAMETER oFoxCode
LOCAL lcTxt
oFoxcode.valuetype = "V"
lcTxt = "© 2002 Microsoft Corporation"
RETURN lcTxt
X .T.

Script Record (Type = "S")

Script records store code that is to be compiled and executed 'on the fly'. These records are used to define "generic" scripts that can be used by other records in the FoxCode table. Other records trigger the execution of these scripts by including the name that is specified in their Abbrev field (enclosed in braces "{}") as the content of their own Cmd field. (Note: Any record type, with the exception of "T" and "O" records, can include a script in its own Data field. However, in order to execute such embedded scripts, a pair of empty braces "{}" must be inserted into the Cmd field).

Example: The following script displays the Getfile() dialog when invoked and returns, to the calling source, whatever was returned from that dialog. All that is required is to add a record to the FoxCode table with the name of the script in the Abbrev field and the necessary code in the Data field, as follows:

Type Abbrev Data Save
S ChooseFile
LPARAMETER oFoxCode
LOCAL lcTxt
oFoxcode.valuetype = "V"
lcTxt = ['] +  GETFILE() + [']
RETURN lcTxt
.T.

Custom Extension Record (Type = "Z")

The custom extension type is used to identify records that IntelliSense does not process automatically. There are only two such records that are shipped with Visual FoxPro 7.0.

The first, whose Abbrev field contains "CustomPEMs" stores the settings for the advanced configuration properties in its Data field. These properties are accessible through the 'Properties' button on the Advanced page of the IntelliSense Manager.

The second, whose Abbrev field contains "CustomDefaultScripts" stores the names of any user defined scripts that are invoked by the hooks coded into the default script when the advanced property named "lAllowCustomDefScripts" is set to True.

Creating and Using Scripts

All scripts consist, essentially, of two parts. The first is the IntelliSense-specific preamble and the second is the actual FoxPro code that generates the desired result. The IntelliSense specific component usually consists of three elements.

  • A parameter statement. Scripts need to be able to accept a single parameter, which is a reference to the FoxCode object. The properties of the FoxCode object are documented fully documented in the help file and are also available on MSDN.
  • Define the type of return that the script generates. This is done by setting the ValueType property of the FoxCode object. This property is used to determine how the result of running the code in the script is to be interpreted and there are three possible values as shown in Table 2:

Table 2: Script Return Types

Value Result Interpreted as
V Value: Action depends upon the script – may be used to replace the triggering text or add to it
L List: Displays the contents of the FoxCode.Items array as a list
T Tip: Displays the contents of the FoxCode.ValueTip property as a Quick Info Tip
  • Check the Location from which the script has been invoked. This is done by checking the Location property of the FoxCode object. Clearly not all actions are appropriate to all situations and this allows us to bypass the script unless we are in the correct editing window. The values generated for the different editing window types are listed in Table 3:

Table 3: Values for FoxCode.Location

Value Type of editor
0 Command Window
1 Program
8 Menu Snippet
10 Code Snippet
12 Stored Procedure
Note: These values, together with much other information about the currently active window, are obtained by calling the FoxTools _EdGetEnv() function. This is not functionality that is specific to IntelliSense.

The remainder of the script is basically standard FoxPro code, that manipulates the properties of the FoxCode Object and carries out whatever tasks are necessary. The following examples illustrate the construction and use of Scripts.

Example 1: Auto-inserting Text

Perhaps one of the simplest, and yet most useful, customizations we can add to IntelliSense is to create scripts to handle routine programming chores like creating headers for programs or methods, or inserting standard blocks of code. Figure EG01 shows a standard program header that is created using a script triggered by typing 'hdr' and pressing the space bar in a program editing window:

Figure EG01: Script-generated program header

In order, to create the script to generate this block of text we need to add a record to the FoxCode table and set it up as illustrated in the following table:

Field Contents
Type U
Abbrev hdr
Cmd { }
Data
LPARAMETERS toFoxCode
IF toFoxcode.Location <1
   RETURN toFoxCode.UserTyped
ENDIF
toFoxcode.valuetype = "V"
LOCAL lcTxt, lcName, lcComment
STORE "" TO lcTxt, lcName, lcComment
#DEFINE CRLF CHR(13)+CHR(10)
lcName = WONTOP()
lcVersion = "Visual FoxPro" + VERSION(4)
lcComment = INPUTBOX( 'Comment for the header:' )
TEXT TO lcTxt NOSHOW
***********************************************************************
* Program....: <<UPPER(lcName)>>
* Author.....: Andy Kramek
* Date.......: <<DMY(DATE())>>
* Notice.....: Copyright (c) <<TRANSFORM( YEAR(DATE()))>> Tightline Computers Inc
* Compiler...: <<lcVersion>>
* Purpose....: <<lcComment>>
***********************************************************************
~
ENDTEXT
RETURN TEXTMERGE(lcTxt)

Although very simple, this script illustrates how an IntelliSense script is really constructed in two parts. First we have the IntelliSense specific setup in which we establish a parameter to receive a reference to the FoxCode object:

LPARAMETERS toFoxCode
*** This script returns a Value
toFoxcode.valuetype = "V"
*** Do nothing unless we are in a program editing window
IF toFoxcode.Location # 1
   RETURN toFoxCode.UserTyped
ENDIF

Then we define how the IntelliSense engine is to treat the return value; there are three possibilities:

  • V (Value)Treat whatever is returned from this script as a value. By default values simply replace the shortcut that triggered the script
  • L (List)Use the contents of the Items collection as the source for a list. The script must ensure that the collection is properly dimensioned and populated
  • T (QuickTip)Display the contents of the ValueTip property as a 'Quick Info' tip. The script must manage the content of the property

Finally we check the Location property of the FoxCode object. The location is stored as a numeric value derived from a FoxTools function (EdGetEnv()) that returns an array containing various items of information about the current editor session. The window type (element 25) is specified as being:

0 = Command Window
1 = Program
8 = Menu Snippet
10 = Code Snippet
12 = Stored Procedure

Since this particular script is really only applicable to a Program file, we can limit its scope to location = 1. Note that when called from any other locations we simply return the contents of another FoxCode object property, "UserTyped", which, as the name implies contains whatever it was that the user actually typed in to trigger the script. The net result of typing 'hdr ' in any location except a program, is therefore, the text string 'hdr '.

The second part of the script is standard Visual FoxPro code that actually builds the header text as a string. It is worth noting that this simple script contains a mixture of old and new Visual FoxPro functions.

*** Standard FoxPro Code from here on
*** Define and initialize local variables
LOCAL lcTxt, lcName, lcComment
STORE "" TO lcTxt, lcName, lcComment
*** Get the window name (if one has been defined)
lcName = WONTOP()
*** Get the VFP Version Number 
lcVersion = "Visual FoxPro " + VERSION( 4 )
*** Get the description
lcComment = INPUTBOX( 'Comment for the header:' )
TEXT TO lcTxt NOSHOW
***********************************************************************
* Program....: <<UPPER(lcName)>>
* Author.....: Andy Kramek
* Date.......: <<DMY(DATE())>>
* Notice.....: Copyright (c) <<TRANSFORM( YEAR(DATE()))>> Tightline Computers Inc
* Compiler...: <<lcVersion>>
* Purpose....: <<lcComment>>
***********************************************************************
~
ENDTEXT
RETURN TEXTMERGE(lcTxt)

After defining, and initializing, the necessary local variables, we begin populating them by getting the name of the current window using the old WONTOP() function. Next we retrieve the VFP Version number, using option 4 of the VERSION() function, and finally we use the brand new INPUTBOX() function to get a comment for the header from the developer.

The block of text that we want to insert is then built as a string using the newly extended TEXT…ENDTEXT construct (which now allows us to store the text directly as a variable). Notice the inclusion of the tilde character on the last line of the header to ensure that the cursor is positioned correctly. Finally the RETURN statement uses yet another new function, TEXTMERGE() to evaluate the variable and return the resulting text as the replacement for the defined abbreviation.

Example 2: Creating Static Lists

One of the most obvious features of IntelliSense is the wide and varied use it makes of lists. Many of the native Visual FoxPro commands and functions implement lists and you can easily create your own lists too. The simplest version is a static list in which you define the items that you want to display directly in the data field of a "User" type record and then invoke the native "CmdHandler" script to generate the list.

The following example shows how we can define a simple list of this sort that allows us to create customized shortcuts for native VFP commands that we use often.

Figure EG02: Simple static list

Here a user-defined shortcut ("CSPB") displays, as a list, two pre-defined options which can be used to complete the CursorSetProp buffering command (we do not normally use any of the other modes). To create this (and similar) shortcuts all that is needed is to add a record to the FoxCode table with the following characteristics:

Field Content
Type U
Abbrev CSPB
Expanded CURSORSETPROP(
Cmd {CmdHandler}
Data "Buffering", 5, ALIAS() ) && Table Buffered

"Buffering", 3, ALIAS() ) && Row Buffered

The CmdHandler script's default behavior is that it reads the entries from the 'data' field and displays them as a list. The text in the 'abbrev' field is replaced with the contents of the 'expanded' field to which the selected item from the list is appended.

However, this is not the only way in which we can create a static list. As we indicated in the discussion of the header script above, the FoxCode object exposes an Items collection that is used to generate lists when the ValueType property is set to "L". We can, therefore, generate a list by creating a script which directly populates the Items collection. This has a distinct advantage over the CmdHandler approach because the Items collection actually has two columns. The first is used as the prompt displayed in the list and the second is used to create the "Value Tips" that are seen in most of the native lists.

The next example populates the Items collection with a list of three possible references to be used when creating a local variable to refer to an object. Figure EG03 shows the list, which is triggered by typing 'obj' followed by a space.

Figure EG03: Static list using FoxCode.Items collection

In this example we are again using the 'expanded' field to replace the text used to trigger the script and the list to complete the assignment statement. The necessary FoxCode record looks like this:

Field Content
Type U
Abbrev Obj
Expanded loRef =
Cmd {}
Data
LPARAMETER toFoxCode
WITH toFoxCode
  .ValueType = "L"
  DIMENSION .Items[3,2]
  .Items[1,1] = "This"
  .Items[1,2] = "Current Object"
  .Items[2,1] = "ThisForm"
  .Items[2,2] = "Current Form"
  .Items[3,1] = "This.Parent"
  .Items[3,2] = "Immediate Parent Container"
  RETURN ALLTRIM( .Expanded )   
ENDWITH

Example 3: Creating Dynamic Lists

Static lists can certainly be useful, but perhaps the greatest single productivity aid that the Visual FoxPro implementation of IntelliSense brings is the ability to create lists which are generated dynamically. The process is a little more complex than any of the examples we have looked at so far but, as you will see, the potential returns certainly justify the investment of a little time.

The following example uses a generic script to build a list of files of the specified type in the current directory and any immediate sub-directories. The idea is that it is then called by other shortcuts that define the specific file type required at the time. Why bother when we already have MRU lists? Well in order for a file to appear in an MRU you must have used it at least once, and it must have been used within whatever number of files you are using as the MRU file limit. Figure EG04 shows the result of typing the 'mop' (for "Modify Program") shortcut at the command line.

Figure EG04: Dynamic list of PRG files in current and first level sub directory

The first thing that we need to do is to create a shortcut entry in the FoxCode table. This is a simple User record of the type we have already met. The only difference here is that instead of calling the script embedded in the Data field, we are now naming a generic script ("ShoFile") in the Cmd field. The record to add looks like this:

Field Content
Type U
Abbrev Mop
Expanded MODIFY COMMAND
Cmd {shofile}

The actual script is a little more complex than we have seen so far because we need to manipulate the way in which the FoxCode object is handled. In order to do this we have to instantiate a custom sub-class of the FoxCodeScript class (which is defined in FoxCode.prg). The first part of the script deals with this as follows:

LPARAMETER toFoxcode
IF FILE(_CODESENSE)
   LOCAL luRetVal, loFoxCodeLoader
  *** Ensure we have the root class definition
   SET PROCEDURE TO (_CODESENSE) ADDITIVE
  *** Create an instance of our custom sub class
   loFoxCodeLoader = CreateObject("FoxCodeLoader")
  *** Call it's Start() method and pass the FoxCode object
   luRetVal = loFoxCodeLoader.Start( toFoxCode )
  *** Clean up nicely here
   loFoxCodeLoader = NULL
   IF ATC(_CODESENSE,SET("PROC"))#0
      RELEASE PROCEDURE (_CODESENSE)
   ENDIF
  *** Return whatever we got back
   RETURN luRetVal
ENDIF

Notice that this code assumes that the new system variable, _CodeSense, is pointing to a program that includes the class definition for the "FoxCodeScript" object. The second part of this script (below) defines the custom subclass of the which is instantiated here and passed the reference to the FoxCode object. The rest of this part of the script merely ensures that the object is released cleanly and no references are left dangling. This the standard preamble for creating a custom script.

The second part of the script is where we define the sub class to carry out the specific tasks we need. The FoxCodeScript class defines a custom property named "oFoxCode" which holds the passed in reference to the FoxCode object and a template method named "Main()" which is called from the Start() method when the object is instantiated. It is here that we place our specific handling code:

DEFINE CLASS FoxCodeLoader as FoxCodeScript
  PROCEDURE Main()
    LOCAL lcMenu, lcKey
    lcMenu = THIS.oFoxcode.MenuItem
    IF EMPTY( lcMenu )
      *** Nothing selected, so display list
      lcKey  = UPPER( THIS.oFoxcode.UserTyped )
      *** What sort of files do we want
      DO CASE
        CASE INLIST( lcKey, "MOP","DOP" )
          lcFiles = '*.prg'
        CASE INLIST( lcKey, "MOF", "DOF" )
          lcFiles = '*.scx'
        CASE INLIST( lcKey, "MOR", "DOR" )
          lcFiles = '*.frx'
        OTHERWISE
          lcFiles = ""
      ENDCASE
      *** Populate the Items Array for display
      This.GetItemList( lcFiles )
      *** Return the Expanded item
      RETURN This.AdjustCase()

The important thing to realize here is that we will actually call this script TWICE. The first time it is called is when the IntelliSense engine reacts to our defined shortcut (in this case 'mop'). The script is called explicitly from the record in the FoxCode table and passed a reference to the FoxCode object as usual. Of course, at this point all we have done is to type 'mop' so the MenuItem property on the FoxCode object will be empty and the code defined at the top of the Main() method executes.

As you can see this checks the UserTyped property on the FoxCode object to determine what shortcut was typed, sets the appropriate file type and calls the custom GetItemList() method. This method is responsible for running an ADIR() to retrieve the file names and copying the list to the Items collection on the FoxCode Object. It then sets the ValueType property to "L" (to display the list) and, crucially, sets the ItemScript property to point back to this self-same ShoFile script.

The ItemScript property is used by the IntelliSense engine to determine what to do when the user selects an item from a list. The return value from this pass through the script will be the usual result of calling a "User" type record which is to replace the shortcut with the content of the Expanded field (in this case "Modify Command"). In addition, the list generated by GetItemList() will be displayed. Since the Itemscript property now points to the ShoFile script, when a selection is made from the list we get the second pass through the script but, now the FoxCode Object's MenuItem property will have a value and so it is the second part of the Main() method which gets executed:

    ELSE
      *** Return the Selected item
      This.oFoxCode.ValueType = "V"
      RETURN lcMenu
    ENDIF
  ENDPROC

All this does is to re-set the ValueType property to "V" and pass whatever was selected from the list (now in the lcMenu variable) back as the return value to complete the operation.

The code in the GetItemList() method is, with the exception of the very end which deals with the setting up the FoxCode object for the second pass, standard Visual FoxPro code to retrieve a list of files from the current, and first level subdirectories:

  PROCEDURE GetItemList( tcKey )
  LOCAL ARRAY laFiles[1,2], laDirs[1], laJunk[1]
  LOCAL lcRootDir, lnDirs, lnDCnt, lnFiles, lcNewDir, lnFound, lcCurDir, lnFCnt, lcFile
    *** Save Root Directory
    lcRootDir = FULLPATH( CURDIR())
    *** Get sub-directories
    lnDirs = ADIR( laDirs, '*.' , 'D' )
    lnFiles = 0
    *** And Process them
    FOR lnDCnt = 1 TO lnDirs
      *** Return to root and re-set
      CD ( lcRootDir )
      DIMENSION laJunk[1]
      lcNewDir = UPPER( ALLTRIM( laDirs[ lnDCnt, 1 ] ))
      IF lcNewDir = ".."
        *** Don't go up the tree
        LOOP
      ENDIF
      CD ( lcNewDir )
      lnFound = ADIR( laJunk, lcFiles )
      lcCurDir = FULLPATH( CURDIR())
      *** If we didn't get any just go on
      IF lnFound < 1
        LOOP
      ENDIF
      *** Now process the files
      FOR lnFCnt = 1 TO lnFound
        lcFile = lcCurDir + laJunk[ lnFCnt, 1 ]
        lnFiles = ALEN( laFiles, 1 ) + 1
        IF NOT EMPTY( laFiles[1] )
          DIMENSION laFiles[ lnFiles, 2 ]
        ELSE
          lnFiles = 1
        ENDIF
        laFiles[ lnFiles, 1 ] = lcFile
      NEXT 
    NEXT
    *** Change back to original directory
    CD (lcRootDir)
    *** If we got something, display the list
    IF lnFiles > 0
At this point we have a list of any files of the required type in the array "laJunk". Now we need to set up the  FoxCode object to display the list and re-call this script on selection:

        THIS.oFoxcode.ValueType = "L"
        THIS.oFoxcode.ItemScript = "ShoFile"
        *** Copy items to temporary array
        DIMENSION THIS.oFoxcode.Items[lnFiles ,2]
        ACOPY(laFiles,THIS.oFoxcode.Items)
     ENDIF
  ENDPROC
ENDDEFINE

This is not really as complex as it looks. The entire ShoFile script record is given here:

Field Content
Type S
Abbrev Shofile
Cmd {}
Data
LPARAMETER toFoxcode
IF FILE(_CODESENSE)
   LOCAL luRetVal, loFoxCodeLoader
  *** Ensure we have the root class definition
   SET PROCEDURE TO (_CODESENSE) ADDITIVE
  *** Create an instance of our custom sub class
   loFoxCodeLoader = CreateObject("FoxCodeLoader")
  *** Call it's Start() method and pass the FoxCode object
   luRetVal = loFoxCodeLoader.Start( toFoxCode )
  *** Clean up nicely here
   loFoxCodeLoader = NULL
   IF ATC(_CODESENSE,SET("PROC"))#0
      RELEASE PROCEDURE (_CODESENSE)
   ENDIF
  *** Return whatever we got back
   RETURN luRetVal
ENDIF
DEFINE CLASS FoxCodeLoader as FoxCodeScript
  PROCEDURE Main()
    LOCAL lcMenu, lcKey
    lcMenu = THIS.oFoxcode.MenuItem
    IF EMPTY( lcMenu )
      *** Nothing selected, so display list
      lcKey  = UPPER( THIS.oFoxcode.UserTyped )
      *** What sort of files do we want
      DO CASE
        CASE INLIST( lcKey, "MOP","DOP" )
          lcFiles = '*.prg'
        CASE INLIST( lcKey, "MOF", "DOF" )
          lcFiles = '*.scx'
        CASE INLIST( lcKey, "MOR", "DOR" )
          lcFiles = '*.frx'
        OTHERWISE
          lcFiles = ""
      ENDCASE
      *** Populate the Items Array for display
      This.GetItemList( lcFiles )
      *** Return the Expanded item
      RETURN This.AdjustCase()
    ELSE
      *** Return the Selected item
      This.oFoxCode.ValueType = "V"
      RETURN lcMenu
    ENDIF
  ENDPROC

  PROCEDURE GetItemList( tcKey )
  LOCAL ARRAY laFiles[1,2], laDirs[1], laJunk[1]
  LOCAL lcRootDir, lnDirs, lnDCnt, lnFiles, lcNewDir
  LOCAL lnFound, lcCurDir, lnFCnt, lcFile
    *** Save Root Directory
    lcRootDir = FULLPATH( CURDIR())
    *** Get sub-directories
    lnDirs = ADIR( laDirs, '*.' , 'D' )
    lnFiles = 0
    *** And Process them
    FOR lnDCnt = 1 TO lnDirs
      *** Return to root and re-set
      CD ( lcRootDir )
      DIMENSION laJunk[1]
      lcNewDir = UPPER( ALLTRIM( laDirs[ lnDCnt, 1 ] ))
      IF lcNewDir = ".."
        *** Don't go up the tree
        LOOP
      ENDIF
      CD ( lcNewDir )
      lnFound = ADIR( laJunk, lcFiles )
      lcCurDir = FULLPATH( CURDIR())
      *** If we didn't get any just go on
      IF lnFound < 1
        LOOP
      ENDIF
      *** Now process the files
      FOR lnFCnt = 1 TO lnFound
        lcFile = lcCurDir + laJunk[ lnFCnt, 1 ]
        lnFiles = ALEN( laFiles, 1 ) + 1
        IF NOT EMPTY( laFiles[1] )
          DIMENSION laFiles[ lnFiles, 2 ]
        ELSE
          lnFiles = 1
        ENDIF
        laFiles[ lnFiles, 1 ] = lcFile
      NEXT 
    NEXT
    *** Change back to original directory
    CD (lcRootDir)
    *** If we got something, display the list
    IF lnFiles > 0
        THIS.oFoxcode.ValueType = "L"
        THIS.oFoxcode.ItemScript = "ShoFile"
        *** Copy items to temporary array
        DIMENSION THIS.oFoxcode.Items[lnFiles ,2]
        ACOPY(laFiles,THIS.oFoxcode.Items)
     ENDIF
  ENDPROC
ENDDEFINE

The script as given includes handling for Programs (mop & dop), Forms (mof and dof) and Reports (mor and dor). It is, of course, a trivial task to add additional types to this script and that is left as an exercise for the reader.

Show: