Gem #88: GPS - Smart Completion (Part 1 of 2)

by Quentin Ochem —AdaCore

Let's get started...

In this Gem we're going to create a few files using the completion mechanism. All the semantic information required to provide this computation is done on the fly. In order to follow along, simply open GPS on any project.

Note that the completion mechanism relies on the ad hoc parser launched at GPS startup. You can see the progression of the parser on the bottom right corner of the screen. Completion may not be accurate before the parsing is finished.

Simple completion of components

Create a new file main.adb:

procedure Main is
begin
   null;
end Main;

First we'll declare a few types and objects. Create a new record type in the declarative part, and then a variable of that type, for example:

type Rec is record
   A, B : Integer;
end record;

A_Variable : Rec;

Then, in the sequence of statements, type "A" and hit <control-space>. <control-space> is the shortcut for manually querying the completion. A popup is opened, showing all declarations and keywords starting with "A". At this stage, there are just too many of them, so let's narrow down the list by adding the character "_". The list is now much shorter. You can select the appropriate completion. A_Variable will be entered in the text.

<control-space> can be used any time to query a completion. When writing code, the completion can be triggered in certain cases. Enter a dot ('.') character in the code. The smart completion popup is automatically triggered, and offers to complete with the components of Rec, namely A and B.

This information is completely synchronized with the current contents of the editor. Adding a component, for example C, in the completion view, and then querying the completion again will show the three components.

Completion of with and use clauses

We're now going to add some with and use clauses to the program. In particular, say we want to add a dependency on some standard unit that provides mathematical operations.

Write "with" at the top of the main subprogram. Query the completion by entering <control-space>. All packages that can be "withed" are now listed here. You can narrow down the list of possibilities by writing, for example, "Ad", and selecting Ada. Add a dot. The completion mechanism will list all children of the predefined Ada package. You can select "Numerics". The interesting thing is that we may not know at this stage what's available in Numerics. Adding another dot will list all the child packages of Numerics. Scroll down the list. "Elementary_Functions" sounds like what we need. Select it.

Completion of subprogram profiles

Create a new Float variable, such as "X : Float;". We're going to use that variable in a mathematical computation. Since we still don't really know what's in the "Elementary_Functions" package, we'll start writing a fully prefixed call. Enter "X := Ada.Numerics.Elementary_Functions.". You can now see all the functions listed in the completion popup. Select, for example, Arctan and enter a left parenthesis. The completion mechanism will offer to complete with several profiles. The red diamonds provide complete profile completion with named notation. Select the first one, and give a value to X and Y.

Completion and OOP

Consider a simple package named "Base":

package Base is

   type Root is tagged private;

   procedure Prim (V : Root; I1, I2 : Integer);

   type Child is new Root with private;

   procedure Prim2 (V : Child);

private

   type Root is tagged record
      A : Integer;
   end record;

   type Child is new Root with record
      B : Integer;
   end record;

end Base;

In Main, create two variable, V1 and V2 of type Root and Child:

   V1 : Root;
   V2 : Child;

When completing, say, "V1.", the completion mechanism offers prefixed primitives, such as "Prim". When completing "V2.", the implicitly inherited primitive "Prim" and the newly declared "Prim2" are offered.

Completion is also sensitive to visibility context. Let's create a body for Base:

package body Base is

   procedure Prim (V : Root; I1, I2 : Integer) is
   begin
      null;
   end Prim;

   procedure Prim2 (V : Child) is
   begin
      null;
   end Prim2;

end Base;

Try writing "V." in the body of Prim2. You will now have access to all the visible components of Child, including the fields which are hidden from Main because of the private part.


About the Author

Quentin Ochem has a software engineering background, specialized in software development for critical applications. He has over 10 years of experience in Ada development. He works today as a technical account manager for AdaCore, following projects related to avionics, railroad, space and defense industries. He also teaches the avionics standard DO-178B course at the EPITA University in Paris.