How-to: Provide Statement Completion

Statement completion is ubiquitous in Visual Studio. Traditional statement completion is invoked by pressing CTRL+<LEFT ARROW> or CTRL+SPACEBAR. The Visual Studio environment completes the current word if possible or shows a list of possible candidates if there is ambiguity. The key combination CTRL+J shows list of all identifiers that are in scope. Providing statement completion is done in the following steps (see the sections that follow for details):

  • Setting Statement Completion Properties

  • Providing Scope Information

注意

You must provide your own lex/yacc-compatible tools; these tools are not included in the VS SDK and the language service project will not build without them. To build the language service using the sample grammar and parser for the sample My C Package, you must use version 1.24 or later of Bison and version 2.5.4a or later of Flex. Place the Bison and Flex executables in the Babel Tools folder, for example, <InstallPath>\VisualStudioIntegration\Babel\Tools. These version numbers are specific to the My C sample.

Setting Statement Completion Properties

Regarding statement completion:

  • The ShowCompletion property determines if the options Show Member List and Show Parameter List are visible in the Visual Studio environment. This property is enabled by default.

  • The SortMemberList property determines if the identifier list is alphabetized. This property is also enabled by default.

Providing Scope Information

In order to implement statement completion, information about the declarations and scopes in the source file must be provided. Ultimately, you should implement an IScope Interface to provide scope information for a specific language. This interface is a simple abstraction around the symbol tree that is typically generated during parsing.

注意

This documentation only covers the default implementation of an IScope object that is provided by the Babel package and can deal with most languages. A significant drawback of this approach, however, is that although it provides a good starting point, this IScope object may implement specific scoping rules of your language incorrectly. A correct implementation can be plugged in afterwards by overloading the addScope and addScopeText methods in the standard service class.

The standard service, g_service, has two methods to give scope and declaration information, as shown in the following example:

Example of Adding Scope and Declaration Information

void addScope( in const Location& start, in const Location& end,
               in ScopeKind       kind, 
               in ScopeAccess     access, 
               in ScopeStorage    storage,
               in const Location& name, 
               in const Location& descStart, 
               in const Location& descEnd,
               in const Location* type = NULL, 
               in long            glyph = -1,
               in bool            merge = false 
               in bool  makeDescription = false );

void addScopeText( in const Location& start, in const Location& end,
                   in ScopeKind     kind, 
                   in ScopeAccess   access, 
                   in ScopeStorage  storage,
                   in const char*   name    = NULL, 
                   in const char*   description = NULL,
                   in const char*   type    = NULL,
                   in const char*   display = NULL,
                   in long          glyph = -1,
                   in bool          merge = false );

The first method takes raw locations for descriptions, while the second one takes arbitrary text. The following functions can be used to combine text locations or to get a pointer to the text at a location:

Location    range( in const Location& loc1, 
                   in const Location& loc2 ) const;
const char* tokenText( in const Location* = NULL, 
                       in const Location* = NULL ) const;   

注意

The pointer returned by the tokenText method is in static memory—text should be copied if it is called more than once in a method.

For each declaration and scope, the addScope or addScopeText method is called with the start and ending location. These locations give the lexical scope of this declaration. If a declaration does not have a lexical scope, such as a variable declaration, the locations might simply coincide.

The kind parameter determines if a declaration has parameters or if it can contain other scopes; it contains no other semantic information. A method information window is only displayed for scopes that have the ScopeProcedure kind (see the ScopeKind enumeration for details).

The access parameter determines the visibility of the declaration and its members, such as private and public. See the ScopeAccess enumeration for details.

The storage parameter is used to calculate an icon if one is not given and the parameter is used to find the parameters of a method. See the ScopeStorage enumeration for more details on scope storage.

The name parameter contains the name of a declaration. The descStart and descEnd parameters contain the start and end location of the declaration. The text between those locations is the description of this declaration. The type location contains the type of this declaration. Set the merge flag to TRUE if the declaration is specified in multiple places and you want the information to be merged with the previous entries.

You can extend the rules in parser.y with calls to the addScope method. The following example shows a declaration of a public procedure in the My C sample:

Example of Declaring a Public Procedure in My C

Declaration
  : Type IDENTIFIER ParenParams Block 
    { g_service->addScope( $1, $4, 
                           ScopeProcedure, AccessPublic, StorageType,
                           $2, $1, $3, &$1 ); } 
  ;

The scope of the method extends from the start of the Type production to the end of the Block production, that is, $1 to $4. The name is given by IDENTIFIER, that is, $2. The description of the scope that is shown as quick info is just the declaration without the block of code, that is, $1 to $3.

By default, the location of a production is the same as that of its first token; each rule contains the implicit assignment: $$ = $0;. To verify that the location of the Block production includes the entire block (and not just the first token brace), add the following rules:

Block
  : '{' '}'   
      { $$ = g_service->range($1,$2);
        g_service->matchPair($1,$2); 
        g_service->addScopeText( $1, $2, 
                                 ScopeBlock, AccessPublic, StorageOther); 
      }
  | '{' BlockContent1 '}'
      { $$ = g_service->range($1,$3);
        g_service->matchPair($1,$3);
        g_service->addScopeText( $1, $3, 
                                 ScopeBlock, AccessPublic, StorageOther);  
      }
  ;

The rules assign the whole range, beginning to end, to the location of the Block production. In this particular case, it would suffice to assign $$ = $2 and $$ = $3 respectively, but using the range method is a good coding practice.

A block itself imposes a lexical scope although it has no name or description. Here, the method addScopeText is called with the ScopeBlock kind and no text for the name, description, or type.

See Also

Concepts

Language Service How-to Topics