Using pdScript IDE as a code editor for your own products

pdScript IDE can be executed from the command-line and then controlled by Windows messages (WM Interface). By this way, you can use it as a code editor for your own products.

This tutorial will show you how to do it.

1. Implement the execution of pdScript IDE into your application

The simplest way of using the pdScript IDE from within your application, is to call it by ShellExecute API function and wait for its termination to continue. You can specify a script file to edit by passing an appropriate parameter (see Command-line parameters). This technique has nothing to do with WM Interface and you must rely on a standard pdScript interpreter features, but it is mentioned here as a basic form of usage.

Example:


  procedure pdScriptIDE_Execute(const scriptFile, pdScriptIDE: string);
  begin
    if ShellExecute(0, 'open', PChar(pdScriptIDE), PChar('"'+scriptFile+'"'), nil, SW_SHOW) <= 32 then
      MessageDlg('', SysErrorMessage(GetLastError()), mtError, [mbOK]);
  end;
 

2. Implement the required Windows Message processing into your application

First, you have to implement the execution of pdScript IDE into your application. It is similar to the previous example, but this time you have to specify a special command-line parameters, that define your interface. Bellow is a real example of this step, taken from the Precision Helper product.


  procedure TfrmMain.pdScriptIDE_Execute(const scriptFile, pdScriptIDE: string);
  var
    prm: string;
  begin
    prm := 
      '"/I{host=' + Self.Handle + ';' +
      'comp-msg=2;' +
      'comp-exitcode=0;' +
      'run-msg=3;' +
      'post-msg=4;' +
      'postonsave=0;' +
      'postonclose=0;' +
      'alwayssave=1;' +
      'script-name=' + scriptFile + ';'+
      'cc-templ=Precision Helper}"';
    if ShellExecute(0, 'open', PChar(pdScriptIDE), PChar('"'+scriptFile+'" '+prm), nil, SW_SHOW) <= 32 then
      MessageDlg('', SysErrorMessage(GetLastError()), mtError, [mbOK]);
  end;   

Parameter /I tells the pdScript IDE to instantiate a WM Interface with a caller window specified by an option "host=HWND". Values for options (like "comp-msg=2", "run-msg=3", etc.) are constants of your choice, that are used later during the messages handling. Of course, you can define them as constant identifiers if needed. A full description of all the options can be found in the WM Interface topic.


Now you should declare a handling of WM_COPYDATA message by your application. This is usually a simple coding - see the example from Precision Helper product bellow. A timer, ProcessWM method and the FScriptIntfData variable, used in our example, allows a safe Windows message queue processing for WM_COPYDATA (recommended by Microsoft), so the execution of the requested code is delayed. Interval for the timer can be as small as possible (ie. 50ms). Of course, you can use a different technique than a timer, such as posting an internal message to the message queue (use PostMessage, not SendMessage).


  interface
  
  type
    TScriptIntfData = record
      Cmd: Integer;
      Editor: HWND;
      ScriptFile: string;
    end;
  
    TfrmMain = class(TForm)
      TimerWM: TTimer;
      procedure TimerWMOnTimer(Sender: TObject);
    private
      FScriptIntfData: TScriptIntfData;
      procedure WMCopyData(var M: TMessage); message WM_COPYDATA;
      procedure ProcessWM;
      ...
    end;

  implementation
    
  procedure TfrmMain.WMCopyData(var M: TMessage);
  var
    i, c: Integer;
    Data: TCopyDataStruct;
    sFile: string;
  begin
    Data := PCopyDataStruct(M.lParam)^;
    i := Data.cbData;
    if i > 0 then
    begin
      SetLength(sFile, (i div SizeOf(Char))+(i mod SizeOf(Char)));
      CopyMemory(@sFile[1], Data.lpData, i);
    end;
  
    case M.wParam of
      2,3,4:
        begin
          FScriptIntfData.Cmd := M.wParam;       // WM Interface command (comp-msg, run-msg, etc.)
          FScriptIntfData.ScriptFile := sFile;   // Script file 
          FScriptIntfData.Editor := Data.dwData; // Window handle of the pdScript IDE
          TimerWM.Enabled := True;
        end;
    end;
    M.Result := 1;
  end;
  
  procedure TfrmMain.TimerWMOnTimer(Sender: TObject);
  begin
    TimerWM.Enabled := False;
    ProcessWM;
  end;  
  
  procedure TfrmMain.ProcessWM;
  var
    WMCData: TCopyDataStruct;
    compileok: Boolean;
    log: string;
    h2: HWND;
  begin
    case FScriptIntfData.Cmd of
      2:begin
          // compile the script
          log := '';
          WMCData.dwData := CompileScript(FScriptIntfData.ScriptFile, log); // exitcode
          WMCData.cbData := Length(log) * SizeOf(Char);
          if Length(log) > 0 then
            WMCData.lpData := Pointer(log)
          else
            WMCData.lpData := nil;
          SendMessage(FScriptIntfData.Editor, WM_COPYDATA, 2, Longint(@WMCData));
        end;
      3:begin
          // compile and run the script
          Application.BringToFront;
          log := '';
          compileok := False;
          WMCData.dwData := CompileAndRunScript(FScriptIntfData.ScriptFile, log, compileok);
          WMCData.cbData := Length(log) * SizeOf(Char);
          if Length(log) > 0 then
            WMCData.lpData := Pointer(log)
          else
            WMCData.lpData := nil;
          if compileok then
          begin
            h2 := GetLastActivePopup(FScriptIntfData.Editor);
            if (h2<>FScriptIntfData.Editor) and (h2<>0) and IsWindowEnabled(h2) and IsWindowVisible(h2) then
              SetForegroundWindow(h2)
            else
              SetForegroundWindow(FScriptIntfData.Editor);
            SendMessage(FScriptIntfData.Editor, WM_COPYDATA, 3, Longint(@WMCData)) 
          end
          else
            SendMessage(FScriptIntfData.Editor, WM_COPYDATA, 2, Longint(@WMCData));
        end;
      4:begin
          // post changes to script (or editor closed)
          ReloadScripts;
        end;
    end;  
  end;
  

Notes:
See the values checked inside ProcessWM method (2,3,4) and the values passed back to the pdScript IDE by SendMessage functions (2,3). These values must correspond to the options, that you have specified in command-line parameters when you executed the pdScript IDE (comp-msg=2; run-msg=3; post-msg=4).


The last step is to adapt your methods for compiling and executing the scripts. In our example these methods are named CompileScript and CompileAndRunScript. Of course, this could be a one method for both operations.

These methods should return an integer value as a result and some string value as a report log. A return value of successfull compilation must corresponds to the option you have specified in command-line parameter when you executed the pdScript IDE (comp-exitcode=0). The compilation report log should be in the same format as the error messages produced by the RemObjects PascalScript engine (simply take the error messages from the engine one on each row).

Method ReloadScripts is intended to refresh a script file (reload it from a file). It is called when the pdScript IDE saves the file or when the IDE is closed.



3. Creating a code-completion template

You can also create your own code-completion template for pdScript IDE, that will help the developers to use a right syntax for your scripts.

This template may contains more files, where some of them define the code syntax and code snippets (.cih files) and the others define a "new script templates" (.dpas and .dfm files).

The template files should be stored in a pdScript IDE Templates folder. You have to create a subfolder named by your template and place the files there. There are already some predefined templates in a pdScript IDE distribution package, so you can take a look at them.

You can specify a template to use by declaring a "cc-templ" option in command-line parameters, when executing a pdScript IDE from your application (see above).


Code-completion template syntax:

Code-completion templates (.cih files) are text files with formatting options, that correspond to the format used in SynEdit Delphi component auto-completion window.

Each file must contain two sections - [ItemList] and [InsertList]. Third (optional) section is [Keywords].

[ItemList] section contains the text, that will be displayed to the user in a code-completion window. Each row in this section represents one item in a code-completion window. The text of an item can be formatted by using some special tags (described below).

[InsertList] section contains the text, that will be inserted at the cursor position in the editor. As obvious, the item index in InsertList section has to correspond to the item index in ItemList section. The text of an item can contain escape characters \n and \t that represents CRLF and TAB characters. The pipe character (|) represents a cursor position after inserting the text to the editor.

[Keywords] section should contain your script language keywords (such as begin, end, if, then, etc.), each on a separate row. These keywords do not have to be listed in ItemList and InsertList sections.


Code-completion template example:


  [Keywords]
  begin
  end
  
  [InsertList]
  StrToIntDef(|)
  try | except end;
  
  [ItemList]
  \image{2}\style{+B}StrToIntDef\style{-B}(s: String; def: LongInt): LongInt;
  \image{0}\style{+B}trye\style{-B} : try except
  

Text formatting tags:

\image{N}
A small icon with index N will be shown before an item. Images are predefined internally in pdScript IDE and the N-index can be in the range from 0 to 13.

\style{+B}..\style{-B}
The text between the starting and the closing tag will be displayed as bold.

\style{+I}..\style{-I}
The text between the starting and the closing tag will be displayed as italic.


New script templates syntax:

So called "new script templates" are standard script files (.dpas and .dfm files), that will be shown in a "File - New from template" menu item of pdScript IDE and the developer can use them to start creating a new script based on these templates.


4. Distribution

There are more available scenarios for distributing your products with pdScript IDE as a code editor. In all the cases, resulting folder structure for pdScript IDE files should be the same, as if the pdScript IDE was installed from its standard distribution package (no matter if it will be located under your product installation folder or separately in its own folder).

One very important thing is, that if you do not install the pdScript IDE in its portable mode, then it will expect its template files, help files, and other data files in the default application data folder. For example on Windows Vista/Seven it is a folder "C:\Users\%UserName%\AppData\Roaming\Precision\pdscript\".

The portable mode is defined by a presence of file named "ConfigPath.ini" with the following contents:

      
  [ConfigPath]
  Path=.
  

"Path" key may contain your own path (instead of ".") where the pdScript IDE's data files are located.


Distribution scenarios:

1. You can distribute your product as before and call the pdScript IDE that is installed separately.
In this case, you should find the pdScriptIDE.exe application by searching the registry or in the file system. The registry key for standard pdScript IDE installation is "\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{FB3669ED-BA78-4E0F-97D1-0EC0F3101A4C}_is1".
This applies also to the scenario where you are calling the pdScript IDE's setup process from your setup wizard.

2. You can distribute your product with pdScript IDE included.
In this case, you should install the pdScript IDE in a portable mode (see above). Not all files from standard pdScript IDE distribution package are required to successfully run the editor. In fact, only the "pdScriptIDE.exe" and the "Langs" subfolder are needed.

Of course, both presented scenarios require that the end-user have got the registered version of pdScript IDE. If you (as a distributor) purchase the Enterprise license of pdScript IDE, you can freely redistribute the pdScript IDE as a part of your product, with so called "anonymous license" included. This license will be created for you and for the specified product on request. If you have purchased only the standard pdScript IDE license, your end-users should buy the same license of pdScript IDE from Precision software & consulting (or you can perform this purchase for them in advance).