WARP & Warpgate: the evolution of IBM i development
WARP is a high-level programming language with declarative syntax, designed to optimize and speed up development on IBM i (AS/400) environments. Instead of hand-writing extensive RPGLE code or DDS definitions, WARP lets you define programs, interactive screens and tables in clean, expressive .warp files.
Through the Warpgate compiler, WARP code is automatically transformed into highly optimized RPGLE and DDS source, ready to run on the system.
Key features
- High-performance engineering: the Warpgate compiler is built 100% in Rust, guaranteeing ultra-fast compilation speed, memory safety and native portability across Windows, GNU/Linux and macOS.
- CI/CD pipeline integration: being a native, cross-platform executable capable of building from the command line, it integrates easily into continuous integration/deployment pipelines (GitHub Actions, GitLab CI, Azure DevOps, etc.), bringing modern DevOps practices to the IBM i platform.
- Multi-protocol remote deployment: transfers and deploys directly to remote IBM i servers over SSH, SFTP and FTP, configurable from the project file (see
@Connection/Protocol). - Modernization and productivity: drastically reduces the lines of code needed and removes the verbosity of traditional RPG, streamlining development teams’ workflow.
- Continuous evolution: constantly improving and gaining new language features and code generation capabilities.
Installation
Warpgate is distributed as a VSCode extension, available on the Visual Studio Marketplace:
- Open VSCode → Extensions tab (
Ctrl+Shift+X). - Search for “WaRPGate for IBM i” (publisher Software House) and install it.
The extension already includes the compiler for Windows, GNU/Linux and macOS — no separate install needed. To generate and deploy code (generate/build/reverse) a license is required — see Licensing.
Built by Software House
WARP and Warpgate are designed, developed and maintained by Giuliano Gonzales Zeballos, creator and lead architect at Software House, a firm dedicated to software engineering, systems architecture and modernization solutions for enterprise environments.
About this book
This book documents:
- The guides: how to build, step by step, each kind of object (
Program,Screen,Table) and the bigger features (DataGridView,Member/Exportreports). - The language reference: every section (
@Properties,@Variables,@Source, …), every data type, every control statement and every builtin function, with its exact syntax and validation rules.
What is a .warp file?
A .warp file describes one object: a Program (business logic, with or without printed output), a Screen (an interactive 5250 screen) or a table (a DDS physical file). The type is declared in @Properties/Type.
A .warp file is organized into top-level sections, marked with @Name { ... }:
@Documentation {
Author Name : "Programmer Name"
}
@Properties {
Type : Program
Name : PTESTFN
Description : "Minimal example"
}
@References {
Programs = []
Screens = []
Tables = []
}
@Variables {
global {
Username char(10)
}
}
@Parameters {
}
@Source {
&Username = USERID()
}
Which sections are valid, and in what order, depends on Type — a Program has @Layout/@Source, a table has @Structure/@Fields/@Indexes, a Screen has @Layout/@Source but no @Structure. Each reference chapter details which file type each section applies to.
How to compile
The compiler is a CLI binary (cli, distributed as warpgate/warpgate.exe inside the VSCode extension) that takes a project (.warproj) and a source file:
warpgate --project my-project.warproj --source programs/PMYPROG.warp --action validate
warpgate --project my-project.warproj --source programs/PMYPROG.warp --action generate
warpgate --project my-project.warproj --source programs/PMYPROG.warp --action build --profile Prod
validate: only runs semantic analysis (the same thing the VSCode extension triggers on save) — never touches the IBM i.generate: generates RPGLE/DDS/CL and writes it toOutput— purely local, never connects to the IBM i (even though the generated CL already references remote paths, based on@Connection/@Deployment).build: does the same asgenerate, and additionally uploads and compiles the generated code on the IBM i configured in@Connection(CRTPF/CRTLF/CRTBNDRPG/CRTDSPF, depending on the type).reverse: reconstructs a table.warpfile from an existing DDS on the IBM i (reverse engineering).
When the .warpcfg declares more than one @Profile (Dev/UAT/Prod, see Project file), generate/build/reverse and license-status need to know which one to use — either with the --profile <Name> flag, or, if omitted, with Default Profile in @Project. validate ignores this flag entirely.
See Project file for the .warproj/.warpcfg format.
Getting started: your first Program
A Program is the basic unit of business logic: it receives parameters, does something (reads/writes tables, computes, calls other programs) and finishes. It doesn’t have to show a screen or print anything.
1. Minimal file
@Documentation {
Author Name : "Your Name"
}
@Properties {
Type : Program
Name : PGREET
Description : "Minimal greeting program"
}
@References {
Programs = []
Screens = []
Tables = []
}
@Variables {
global {
Username char(10)
Greeting char(30)
}
}
@Parameters {
}
@Source {
&Username = USERID()
&Greeting = "Hello, " + &Username
}
This is already a valid .warp file: it declares the object (@Properties), its variables (@Variables) and its logic (@Source). @References/@Parameters are empty because this program doesn’t depend on other objects or receive parameters.
2. Adding parameters
A Program receives parameters declared in @Parameters, referencing variables already declared in @Variables/global:
@Variables {
global {
MyDate date
MyTime char(8)
}
}
@Parameters {
inout:&MyDate,
inout:&MyTime
}
The modes are in (input only), inout (input and can be modified) and out (output only) — see @Parameters.
3. Calling another program
To call an external program, add it to @References/Programs and use Call(...):
@References {
Programs = ["../programs/POTHER.warp"]
}
@Source {
Call(POTHER, &MyDate, &MyTime)
}
4. Local procedures and functions
Procedure/Function live inside @Source, with their own variables in @Variables (one entry per procedure/function name, in addition to global):
@Variables {
global {
Username char(10)
}
Greet {
name char(10)
}
}
@Source {
Function Greet(&name) char(20)
Return "Hello " + &name
EndFunc
&Username = USERID()
Message("Welcome", Info) // only valid in Screen; use Print() in Program
}
To call a Procedure (no return value) use Do Name(...); a Function (with a return value) is called as an expression: &variable = Name(...).
5. Next steps
- If your program needs to read/write a table: Tables.
- If it needs to show an interactive screen: Screens.
- If it needs to generate a report to a text file: Reports with Member()/Export().
For the full syntax of each section, see the Language reference.
Tables: @Structure, @Fields, @Indexes
A table .warp file describes a DDS physical file: its columns and its keyed access paths. It’s referenced from a Program/Screen via @References/Tables.
1. Minimal file
@Properties {
Type : "Table"
}
@Structure {
Name : "GRIDDEMO"
Description : "Sample customers"
@Fields {
CustCode char(6) Description("Customer code")
Name char(20) Description("Name")
City char(15) Description("City")
}
@Indexes {
PrimaryKey ( GRIDDEMOPK, [CustCode] )
Index ( GRIDDMNOM, [Name] )
}
}
@Fields: oneName type(...)line per column. Onlychar/number/date— see Data types. Available modifiers:Description(...),Default(...),AllowNull.@Indexes:PrimaryKey(required, its fields can’t beAllowNull),Unique,Index— each one asKind(Name, [Field, ...], "optional description").
2. Referencing it from a Program
@References {
Tables = ["../tables/TLGRIDDEMO.warp"]
}
The path is relative to the file that references it. Once referenced, its fields become available as bare identifiers inside a For Each/New (see below), and as field(FieldName) in @Variables to inherit its type.
3. Reading rows: For Each In
@Source {
For Each In GRIDDEMO Index GRIDDMNOM
Where CustCode = &VCustCode
&VName = Name
&VCity = City
When None
Message("Customer not found", Error)
EndFor
}
Index picks the access path (defaults to the sequential-access one if omitted); Where filters by equality; When None runs if no row was found.
4. Inserting rows: New
@Source {
New In GRIDDEMO
CustCode = &VCustCode
Name = &VName
City = &VCity
When Duplicate
Message("That code already exists", Error)
EndNew
}
When Duplicate runs if the insert collides with an existing unique key.
5. Extended documentation
A table can also carry @Documentation (nested inside @Structure, unlike a Program where it goes at the top level) with @Author, @Application, @Module, @Repository and @History (@Creation + @Change per changelog entry) — see @Structure, @Fields, @Indexes for the full detail of each sub-section.
6. Reverse engineering from an existing DDS
If the table already exists on the IBM i, there’s no need to write it by hand: the WaRPGate: Reverse Engineer command in the VSCode extension connects to the IBM i, reads the DDS (and optionally its associated LFs as @Indexes) and generates the corresponding .warp file.
Screens: @Layout and Screen
A Screen is an interactive 5250 screen: it defines its layout (@Layout) and reacts to user events (@Source).
1. Minimal structure
@Properties {
Type : Screen
Name : SFORM01
Description : "Sample screen"
Path : Screens
}
@References {
Tables = ["../tables/TLADD50.warp"]
}
@Variables {
Global {
Username char(10)
MyDate Date
}
}
@Parameters {
}
@Layout {
@Label(1,2,&Username)
@Label(1,70,&MyDate)
@Input("txtRequestNo", 3, 1, "Request No.: ", &RequestNo, true)
}
@Source {
Event Init
&Username = UserId()
&MyDate = Today()
EndEvent
}
Unlike a Program, a Screen doesn’t have its own @Structure — it only references tables to read/write.
2. @Layout controls
Label(x, y, text): static text/variable.x,y = 1,1(exact corner) is forbidden — DDS rejects it.Input("Name", x, y, text, &Variable, enabled): editable field."Name"is whatCurrentInput()returns when the cursor lands there.enabledis optional (defaulttrue): a literal,&Variable, or a function returningnumber.DataGridView("Name", x, y, rows)[...]: grid — see DataGridView.
Control names can be prefixed with @ (@Label, @Input) or not — it’s purely stylistic, same meaning.
3. Standard events
| Event | When it fires |
|---|---|
Init | Once, before the first display. |
Load | Once, before the first display — typically fills the DataGridView with LoadRow(). |
Enter | On pressing Enter (with no function key active). |
Refresh | On pressing F5, if declared in the project’s @FunctionKeys. |
Close | Fixed to F3 — always runs and closes the screen, no declaration needed. |
@Source {
Event Init
&Username = UserId()
EndEvent
Event Load
For Each In LADD50 Index LADD5010
Where SPT25COUNTRY = &nCountry
&cState = ADSTS50
LoadRow()
EndFor
EndEvent
Event Enter
&Control = CurrentInput()
Message("Active control: " + &Control, Info)
EndEvent
}
4. Custom function keys
Besides the standard events, you can attach an event to any F1-F24 key (F13-F24 via Shift):
@Source {
Event 'FindCustomer' 4
Message("F4 was pressed", Info)
EndEvent
}
The label shown in the attention line is configured in the project’s .warpcfg @FunctionKeys — see Project.
5. Messages to the user
Message("Rows found: " + String(&nRows, 5), Info)
Error (red), Warning (yellow), Info (normal color). Shown on the next refresh and cleared after one cycle.
6. Next step
If the screen needs to show a list of rows with selection/pagination, see DataGridView.
DataGridView: paginated grids
DataGridView shows a list of rows from a table on a Screen, with native pagination (Page Up/Down) over a DDS subfile. There can only be one per Screen.
1. Full example
@Properties {
Type : Screen
Name : SGRIDDEMO
}
@References {
Tables = ["tables/TLGRIDDEMO.warp"]
}
@Variables {
global {
VCustCode char(6)
VName char(20)
VCity char(15)
VTotal number(5)
VLast char(6)
}
}
@Layout {
DataGridView("GridDemo", 2, 2, 5)
[
Column("Code", &VCustCode)
Column("Name", &VName)
Column("City", &VCity)
]
}
@Source {
Event Load
For Each In GRIDDEMO Index GRIDDMNOM
&VCustCode = CustCode
&VName = Name
&VCity = City
LoadRow()
EndFor
EndEvent
Event Enter
&VTotal = 0
For Each Row
&VTotal = &VTotal + 1
&VLast = &VCustCode
EndFor
Message("Rows: " + String(&VTotal, 5) + " Last: " + &VLast, Info)
EndEvent
}
2. Loading the grid: Event Load + LoadRow()
LoadRow() takes the current values of the variables bound to each Column and appends a row to the grid. It’s only valid inside a For Each in the body of Event Load (that For Each must declare Index). It loads the entire result set in a single pass — Page Up/Down are native terminal scrolling over what’s already loaded; Load never runs again.
3. Reading the selection: RefreshSelectRow() and CurrentInput()
When the cursor lands inside the grid’s rectangle (header, attention row, or any data row), CurrentInput() returns the grid’s name. RefreshSelectRow() re-reads the row under the cursor and writes each column (including Hidden ones) back into its bound variable — it’s the reverse of LoadRow():
Event Enter
&Control = CurrentInput()
If &Control = "GridDemo"
RefreshSelectRow()
Message("You selected: " + &VCustCode, Info)
EndIf
EndEvent
4. Iterating over all loaded rows: For Each Row
For Each Row ... EndFor iterates over all rows already loaded into the subfile (not the database table), writing each Column into its bound variable before every iteration — same direction as RefreshSelectRow(). It doesn’t support Index/Where/When None because there’s no table being iterated:
Event Enter
&VTotal = 0
For Each Row
&VTotal = &VTotal + 1
EndFor
EndEvent
5. Hidden columns
Column("Internal ID", &VInternalId, Hidden)
A Hidden column loads its data (participates in LoadRow()/RefreshSelectRow()/For Each Row) but doesn’t show its cell or header title — useful for carrying an internal key without taking up screen space.
6. Editable columns (Input)
Column("Quantity", &VQuantity, Input)
Lets the user edit the value directly in the grid.
7. Rules to keep in mind
- The name (
"GridDemo") is required and must be unique amongInput/DataGridViewcontrols on the same screen. - Only one
DataGridViewperScreen. - Only valid inside a
Screen’s@Layout, never aProgram’s.
Reports with Member() / Export()
Pattern for generating a report: accumulate rows into a physical table (a data member exclusive to the user/run) and, when done, dump it to a text file on the IFS.
This feature uses
varcharin@Variables— see Data types.
1. Full example
@Properties {
Type : Program
Name : PTESTME
}
@References {
Tables = ["../tables/TLT3D01.warp"]
}
@Variables {
global {
User char(10)
Destination varchar(200)
T301TIPLOT char(1)
T301NROTAR char(19)
}
}
@Source {
&User = USERID()
&Destination = "/tmp/report.txt"
Member(LT3D01, &User)
For Each In LT3D01 Index LT3D0101
Where T301TIPLOT = &T301TIPLOT
Where T301NROTAR = &T301NROTAR
// ... accumulate rows into LT3D01, e.g. with New In LT3D01 ...
EndFor
Export(LT3D01, &Destination)
}
2. Member(Table, expr): one member per user/run
Member(LT3D01, &User)
Attaches a data member (parameterized by expr, typically &User so each user gets their own) to Table for the whole program:
- Creates it with
ADDPFMif it doesn’t exist yet (tolerates the error if it already exists). - Leaves it active with
OVRDBF— effective for the rest of the program, including any open of that table and any laterExport(...)over it.
Position rules (at most once per table, in either case):
- In a
Program: only at the top level of@Source(not nested insideIf/While/For/Event/etc.). - In a
Screen: only at the top level ofEvent Init(not nested, and not in any otherEvent) —Initruns exactly once, before the screen’s main loop, the same place aProgram’s prologue would go:
@Properties {
Type : Screen
Name : SGRIDDEMO
}
@Source {
Event Init
Member(LT3D01, &User)
EndEvent
Event Load
For Each In LT3D01 Index LT3D0101
// ... fills the DataGridView, or reads/writes rows ...
EndFor
EndEvent
}
Export(...) still isn’t allowed in a Screen — there’s no use case for exporting a report from an interactive screen.
Important:
Member(Table, expr)must appear before anyFor Each/NewoverTablein the same body — using it after already reading/writing the table is a runtime error, not a compile-time one.
3. Export(Table, destination): dump to IFS
Export(LT3D01, &Destination)
Copies Table’s active member (the one Member(...) set, if any) to an IFS stream file with CPYTOIMPF, comma-delimited format (RCDDLM(*CRLF) STRDLM(*NONE) FLDDLM(',')), replacing the destination if it already exists (MBROPT(*REPLACE)).
Rules: only valid in Program; unlike Member, no position restriction — use it wherever convenient, typically after the For Each/New that filled the report table.
Project: .warproj and .warpcfg
Every .warp file is compiled in the context of a project (.warproj), which in turn points to a generator config file (.warpcfg) holding the code generation rules and the IBM i deployment settings.
1. .warproj
@Project {
Name : Example
Description : "Sample project"
Version : 1.0
Default Profile : Dev
@Generator {
Name : "waRPGate generator"
Language : RPGLE
Description : "RPGLE generator"
@Paths {
Config : ../example/generator/rpg/rpg.warpcfg
Output : output/rpg
}
}
}
@Paths/Config: path to the.warpcfg(relative to the.warproj).@Paths/Output: folder where the generated RPGLE/DDS is written.
2. .warpcfg
@GeneratorConfig {
Date Format : DMY
Date Separator : /
Time Format : 24H
Time Separator : :
Print Mode : File
Commitment : True
Commit on Exit : True
DDS Name : <ObjectName>
@Screen {
Rows : 24
Columns : 80
@FunctionKeys {
F3 : "Exit"
F5 : "Refresh"
F24 : "More keys"
}
}
@Profile "Dev" {
@Connection {
Host : DEVSERVER
User : myuser
Auth Method : key
Key File : ~/.ssh/id_rsa_ibmi
Protocol : ssh
Port : 22
Timeout : 30
}
@Deployment {
CL Source : QCLSRC
Cleanup : true
Target Release : *Current
Optimization : 40
Debug : false
Temp Path : /tmp
Data Library : DTALIB02
Objects Library : OBJLIB01
Source Tables : QDDSSRC
Source Programs : QRPGLESRC
Compile Library : DTALIB01,DTALIB02,DTALIB03,OBJLIB01
}
}
@Profile "Prod" {
@Connection {
Host : PRODSERVER
User : myuser
Auth Method : key
Key File : ~/.ssh/id_rsa_ibmi
Protocol : ssh
Port : 22
Timeout : 30
}
@Deployment {
CL Source : QCLSRC
Cleanup : true
Target Release : *Current
Optimization : 40
Debug : false
Temp Path : /tmp
Data Library : DTALIB02
Objects Library : OBJLIB01
Source Tables : QDDSSRC
Source Programs : QRPGLESRC
Compile Library : DTALIB01,DTALIB02,DTALIB03,OBJLIB01
}
}
}
What each block controls
- Top level (
Date Format,Print Mode,Commitment,Commit on Exit,DDS Name): code generation rules, applied to the whole project.Commitment/Commit on Exitcan be overridden per object in a specific.warpfile’s@Properties. @Screen: default screen size (24x80 if omitted) and the function keys (@FunctionKeys) available to the project’sScreens — the label shown in the attention line. AScreenattaches a key to its own event withEvent 'Name' <n> ... EndEvent.@Profile "Name": a deployment environment (Dev,UAT,Prod, the name is free-form). Each profile groups its own@Connection,@Deployment, and, optionally,@License. A.warpcfgmust declare at least one@Profile.@Connection: how the compiler reaches the IBM i for--action build/reverse(doesn’t apply tovalidate/generate, which are purely local).@Deployment: libraries and source member where the generated code gets uploaded and compiled (CRTPF/CRTLF/CRTCLPGM).
With more than one @Profile, it’s necessary to indicate which one to use: either with --profile <Name> on the CLI, or by leaving Default Profile set in @Project (as in the .warproj example above). The VSCode extension has its own picker — see VSCode commands.
See Project file for the full list of keys and their allowed values.
Licensing
The compiler’s generate, build and reverse actions require a license. The validate action does not, so source validation (and the extension’s IntelliSense) always works.
The license is managed automatically: it’s taken when using the compiler and released when it’s done, with no manual intervention.
License modes
Organization
A license service manages a pool of seats. The compiler:
- Connects securely to your organization’s license service (identifying the server with
Fingerprint, in the project configuration).Host/Port/Fingerprintare handed out by whoever administers that service — see Setting up the license service if that’s you. - Each user holds a single seat at a time.
- If the compiler closes or loses the connection, the seat is released automatically — no one is left holding a license they’re not using.
- Compatible with Kerberos/Active Directory authentication, to integrate with your existing corporate credentials.
Individual
A signed license file, bound to the machine, validated locally with no network. The default path is:
- Linux/macOS:
~/.config/warpgate/license.json - Windows:
%APPDATA%\warpgate\license.json
Another path can be set with File (in @License), --license-file or WARPGATE_LICENSE_FILE.
Configuration
@License block in the .warpcfg
Optional block inside each @Profile (see Project file) — each profile (Dev/UAT/Prod) can have its own license configuration, to consume different licenses or licensing services depending on the environment. Properties are case-insensitive.
@GeneratorConfig {
@Profile "Prod" {
@License {
Host : "servidor01.dominio.local"
Port : 7443
Fingerprint : "3f2a9c41d87b05e6a1c4f0937be2d5688a1f4c0d29e7b3a65c8d1f0e4b7a9c23"
}
}
}
--profile/Default Profile picks which profile (and therefore which @License) to use on each run — see Project: .warproj and .warpcfg.
| Key | Description |
|---|---|
Host | License service host. If present, the mode is organization and File is ignored. |
Port | Service TCP port, 1 to 65535. Default 7443. |
Fingerprint | SHA-256 fingerprint of the service certificate: 64 hexadecimal characters (uppercase and : separators are accepted). |
SPN | Kerberos service name. Default warpgate-license/<host>. |
File | Path to the individual license file. |
Without Host, the mode is individual.
Flags and environment variables
| Flag | Environment variable | Equivalent to |
|---|---|---|
--license-host | WARPGATE_LICENSE_HOST | Host |
--license-port | WARPGATE_LICENSE_PORT | Port |
--license-fingerprint | WARPGATE_LICENSE_FINGERPRINT | Fingerprint |
--license-spn | WARPGATE_LICENSE_SPN | SPN |
--license-file | WARPGATE_LICENSE_FILE | File |
Precedence: flag > environment variable > .warpcfg.
With several @Profile declared, license-status also respects --profile/Default Profile to know which @License to read from — see Project: .warproj and .warpcfg.
Exit codes
| Code | Meaning |
|---|---|
3 | No license available. The compiler prints a message with what to do. |
4 | The license was lost mid-run. |
130 | Run interrupted (Ctrl-C/SIGTERM). |
Requesting a license
These support actions don’t take a seat and don’t need --source; --project is optional.
warpgate --action license-request --license-id <ID> --license-out request.json
It writes the request file (--license-out is optional; default request.json) and shows the machine identifier. Once you receive the license file, copy it to the default path (or to the one set with File). Installing a new license replaces the previous one: the previous one stops being valid by serial number.
How to get a license (free for now, time-limited)
Warpgate is currently distributed free of charge: Software House issues free individual licenses, valid for 90 days and renewable. This policy may change later, without affecting already-issued licenses.
- On the machine where the compiler will run, generate the request:
warpgate --action license-request --license-id <name or company> --license-out request.json - Send to warpgate@softwarehouse.pe:
- The generated
request.jsonfile. - The machine identifier the command prints on screen (needed to issue the license).
- The generated
- Software House replies with a signed
license.jsonfile, tied to that machine, valid for 90 days. - Copy that file to the default path (or the one set by
--license-file/File/WARPGATE_LICENSE_FILE):- Linux/macOS:
~/.config/warpgate/license.json - Windows:
%APPDATA%\warpgate\license.json
- Linux/macOS:
- Confirm with
warpgate --action license-status(it should show the status and the expiry date).
Once the 90 days are up, generate/build/reverse stop working until you renew — repeat this same procedure to get a new license. validate keeps working regardless, even without a license. For a license with seats for several users (organization mode), also write to warpgate@softwarehouse.pe.
To check the current state:
warpgate --action license-status
It shows the mode, the license, the expiration and, in organization mode, the seats in use and available.
License scope
A license covers one product and one major version (for example, the 0.x series). It can be perpetual or have an expiration date.
Development
Development builds accept WARPGATE_LICENSE=off to skip the requirement. Release binaries do not.
Related diagnostics
| Code | Situation |
|---|---|
E00284 | @License Port is 0. |
E00285 | @License Host is not a valid host. |
E00286 | Fingerprint is not a valid SHA-256 fingerprint. |
E00287 | Warning: Host without Fingerprint. |
E00288 | Warning: Host and File together (File is ignored). |
Setting up the license service (organization mode)
This page is for whoever administers your organization’s infrastructure (systems team, or the IBM i server administrator) — you don’t need to read it to program in WARP. It covers the one-time setup of the service that hands out licenses across your whole development team. People who just use Warpgate get three final values (Host, Port, Fingerprint) from the administrator and put them in their @License — see Licensing.
1. Download
license-service(administers the service): Linux · Windowslicense-gui(visual query tool, optional — see section 5): Linux · Windows- Checksums: SHA256SUMS.txt
On Linux, make them executable: chmod +x license-service license-gui.
2. Request the organization license
On the server where the service will run:
license-service create-request --license-id "<Your organization's name>" --out request.json
license-service gen-hwid
The second command prints several machine details; copy the primary_os_id value (a 32-character string). Send to warpgate@softwarehouse.pe:
- The
request.jsonfile. - The
primary_os_idvalue. - How many simultaneous seats your team needs.
Software House replies with a license.json file for that server, valid for 90 days and renewable — same free-of-charge model as the individual license, see Licensing.
3. Install the service
Windows
license-service install-license --license license.json
license-service gen-tls-cert --subject-alt-name <server-host-or-domain>
license-service install-windows-service --bind 0.0.0.0:7443
This registers license-service as a regular Windows service (starts automatically, restarts itself on failure) — manage it from the Services console like any other. The second command prints the SHA-256 fingerprint on screen: that value is the Fingerprint needed by anyone connecting.
Linux, as a systemd service (recommended)
- Copy the binary and create a dedicated user for the service:
sudo install -m 0755 license-service /usr/local/bin/license-service sudo useradd --system --no-create-home --shell /usr/sbin/nologin warpgate-license sudo install -d -o warpgate-license -g warpgate-license -m 0700 /var/lib/license-service - Install the license and generate the certificate, as that user:
Write down the SHA-256 fingerprint the second command prints: that value is thesudo -u warpgate-license env LICENSE_SERVICE_DIR=/var/lib/license-service \ license-service install-license --license /path/to/license.json sudo -u warpgate-license env LICENSE_SERVICE_DIR=/var/lib/license-service \ license-service gen-tls-cert --subject-alt-name <server-host-or-domain>Fingerprint. - Download the systemd unit file, copy it and enable the service:
sudo cp license-service.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now license-service
This makes the service start on boot and restart itself on failure. Logs: journalctl -u license-service -f. Only open TCP port 7443 to the subnet of Warpgate users.
Linux, quick test (no systemd)
To test before installing it as a permanent service:
license-service install-license --license license.json
license-service gen-tls-cert --subject-alt-name <server-host-or-domain>
license-service run --bind 0.0.0.0:7443
Runs in the foreground; stops when you close the terminal. Useful to verify everything works before the step above.
4. Hand the values to your team
Each developer puts these three values in their project’s @License (see Licensing):
- Host: the server where the service runs.
- Port:
7443(or whatever was set with--bind). - Fingerprint: the one
gen-tls-certprinted.
If your organization uses Active Directory, the service can require Kerberos authentication — contact Software House to enable it.
5. Checking the service with license-gui
license-gui is a separate window, no command line, for checking the license status without touching the seats in use (it doesn’t consume a seat): active license and expiry, seats in use and available, and each user’s session history (including whether a session closed normally or was lost).
On opening, it asks for the same three values as any client — Host, Port, Fingerprint — plus an optional SPN if the service requires Kerberos. Whoever administers the service can also install a new license from there, with no restart needed.
Structure of a .warp file
A .warp file is a sequence of @Name { ... } sections. Which sections apply depends on @Properties/Type:
| Section | Program | Screen | Table |
|---|---|---|---|
@Documentation | top level | top level | nested inside @Structure |
@Properties | yes | yes | yes (only Type) |
@Structure | no | no | yes |
@Fields | no | no | nested inside @Structure |
@Indexes | no | no | nested inside @Structure |
@References | yes | yes | no |
@Variables | yes | yes | no |
@Parameters | yes | no | no |
@Layout | yes (optional) | yes | no |
@Source | yes | yes | no |
A .warproj/.warpcfg doesn’t describe an object — they use their own top-level sections (@Project, @GeneratorConfig) — see Project file.
General syntax
- Comments:
//to end of line. - Strings: double or single quotes interchangeably (
"text"and'text'are semantically the same). - Variables: always with
&(&Username); table field names go without&inside aFor Each/New. - Every top-level section carries
@; control names inside@Layoutmay carry@optionally (@Label/Labelare equivalent).
Reference index
- @Documentation
- @Properties
- @Structure, @Fields, @Indexes
- @References
- @Variables
- Data types
- @Parameters
- @Layout
- @Source: control statements
- @Source: Event and Screen
- Builtin functions
- Expressions and operators
- Project file
@Documentation
Informational metadata, doesn’t affect code generation.
In Program/Screen (top level)
Flat Key : value pairs:
| Key | Description |
|---|---|
Author Name | Name of who wrote the program. |
Author User | User ID of who wrote it. |
App Id | Short application identifier. |
App Name | Visible application name. |
App Description | Long application description. |
Module Id | Short module identifier. |
Module Name | Visible module name. |
Module Description | Long module description. |
Created At | Free-form date/timestamp (e.g. "12/06/2025"), not parsed or validated. |
@Documentation {
Author Name : Programmer Name
Author User : User Code
App Id : Application ID
App Name : Application Name
Module Id : Module ID (if applicable)
Created At : "12/06/2025"
}
In Table (nested inside @Structure)
Here @Documentation has no keys of its own, only sub-sections:
@Author:Name,User,Company.@Application:Id,Name,Description.@Module:Id,Name,Description.@Repository:Official,Type(e.g.git),Branch,Version.@History: container for@Creationand any number of@Changeentries.@Creation:Date,Author,User,Company,Reason.@Change(repeat the block for each entry):Date,Type(by conventionbreaking/feature/fix/security/performance/refactor/config/docs, not enforced by the compiler),Impact(by conventionhigh/medium/low),Version,Reason,Author,User,Company.
@Structure {
Name : "GRIDDEMO"
@Documentation {
@Author {
Name : Programmer
}
@History {
@Creation {
Date : "2025-06-12"
Author : Programmer
Reason : "Initial creation"
}
@Change {
Date : "2026-01-10"
Type : feature
Impact : medium
Reason : "Adds City column"
}
}
}
@Fields { ... }
}
@Properties
Program
| Key | Description |
|---|---|
Type | The kind of object this file compiles to (Program). |
Name | Program name. |
Description | Program description. Maximum 50 characters: it ends up in the TEXT parameter of the compile-time CL commands (CRTBNDRPG/CRTDSPF/CHGPFM), which has that real limit on IBM i (see E00281). |
Print Mode | Print mode, for a report-style program. |
Commitment | Whether this program runs its modified tables under commitment control. Overrides the project’s Commitment (@GeneratorConfig) only for this program. Values: True/False. |
Commit on Exit | Whether this program issues a final commit before finishing. Overrides the project’s value only for this program; only effective if Commitment is enabled. Values: True/False. |
DDS | Name of the DDS this program is attached to. |
Path | Output folder for this program, relative to the project. |
@Properties {
Type : Program
Name : PTEST01
Description : "Test program"
Commitment : True
Commit on Exit : True
Path : "./programs/"
}
Screen
Same keys as Program (Type with value Screen), plus 5 of its own:
| Key | Description |
|---|---|
Popup | Generates this Screen’s DSPF with the DDS WINDOW keyword (a pop-up window) instead of a full screen. Requires the 4 keys below. Values: True/False. |
Window Row | Row (1-based) of the window’s top-left corner. Required if Popup: True. |
Window Column | Column (1-based) of the window’s top-left corner. Required if Popup: True. |
Window Rows | Window height, in rows. Required if Popup: True. |
Window Columns | Window width, in columns. Required if Popup: True. |
@Properties {
Type : Screen
Name : SCONFIRM
Popup : True
Window Row : 5
Window Column : 10
Window Rows : 10
Window Columns : 40
}
A Screen with Popup: True is generated, compiled and deployed exactly like any other Screen — only the DSPF changes (WINDOW instead of a full screen). To show it from another Screen, reference it in @References/Screens and call it with Call(Name, ...) — see @Source: control statements.
Table
Only Type:
@Properties {
Type : "Table"
}
The rest of a table’s description goes in @Structure — see @Structure, @Fields, @Indexes.
@Structure, @Fields, @Indexes
Only apply to table files (@Properties/Type : "Table").
@Structure
| Key | Description |
|---|---|
Name | Table name. Must start with a letter, letters/digits only, max 10 characters (DDS object name limit). |
Description | Table description. |
Also contains, nested: @Fields, @Indexes and optionally @Documentation (see @Documentation).
@Fields
One Name type(...) line per column:
@Fields {
CustCode char(6) Description("Customer code")
Name char(20) Description("Name")
Balance number(9,2) Default(0)
RegDate date
}
Allowed types: only char, number, date (not field/list/matrix — those are exclusive to @Variables, see Data types).
Modifiers:
| Modifier | Description |
|---|---|
Description("text") | Field description. Requires a quoted string. |
Default(value) | Default value: a quoted string or a bare number, depending on the field’s type. Not allowed on date fields. |
AllowNull | Allows NULL on this field. A PrimaryKey field can’t carry this modifier. |
@Indexes
One Kind(Name, [Fields...], "optional description") entry per access path:
@Indexes {
PrimaryKey ( GRIDDEMOPK, [CustCode] )
Unique ( GRIDDEMOEMAIL, [Email], "Unique email" )
Index ( GRIDDMNOM, [Name] )
}
| Kind | Description |
|---|---|
PrimaryKey | The table’s primary access path. Only one per table; its fields can’t be AllowNull. |
Unique | Unique secondary access path. |
Index | Non-unique secondary access path. |
Full example
@Properties {
Type : "Table"
}
@Structure {
Name : "GRIDDEMO"
Description : "Sample customers"
@Fields {
CustCode char(6) Description("Customer code")
Name char(20) Description("Name")
City char(15) Description("City")
}
@Indexes {
PrimaryKey ( GRIDDEMOPK, [CustCode] )
Index ( GRIDDMNOM, [Name] )
}
}
@References
Lists other .warp files this Program/Screen depends on. Unlike other sections, it uses Key = [ "..." ] (a bracketed list), not Key : value.
| Key | Description |
|---|---|
Programs | Other Program .warp files this one references (for Call(Name, ...) resolved at compile time). |
Screens | Screen .warp files this one references — same as Programs, enables calling it with Call(Name, ...) (typically a Screen with @Properties/Popup: True, shown as a pop-up window). |
Tables | Table .warp files this one references (enables For Each In/New In/field(...) over them). |
@References {
Programs = [
"../programs/POTHER.warp"
],
Screens = [
],
Tables = [
"../tables/TLADD50.warp",
"../tables/TLGRIDDEMO.warp"
]
}
Paths are relative to the file that declares them. Doesn’t apply to table files (a table doesn’t reference other objects).
@Variables
Declares variables inside a named scope: global (visible throughout the file) or the name of a Procedure/Function declared in @Source (visible only inside that procedure/function, in addition to global ones).
@Variables {
global {
Username char(10)
MyDate date
Balance number(9,2)
}
MyProcedure {
counter number(3)
}
Greet {
name char(10)
}
}
One Name type(...) line per variable — same format as @Fields, but with more available types (see Data types): char, varchar, number, date, field(Field), list(type), matrix(rows, cols, type), struct(TemplateName).
Struct: inline template definition
Inside the global scope (only there) you can also define a struct template with struct Name [ ... ], before or after instantiating it with struct(Name) — see Data types: Struct for the full syntax and its limits.
Modifier
Only Description("text") is available in @Variables (unlike @Fields, it doesn’t support Default/AllowNull):
@Variables {
global {
RequestNo field(ADHOJA50) Description("Request number")
}
}
Length/precision limits (standalone variable, different from a table field)
| Type | Limit in @Fields | Limit in @Variables |
|---|---|---|
char | 1–32,766 | 1–16,773,104 |
varchar | not valid in @Fields | 1–16,773,100 |
number (precision) | 1–30 | 1–63 |
These larger limits in @Variables reflect that a variable declared there isn’t tied to a DDS physical file the way an @Fields field is.
Data types
| Type | Syntax | Where | Description |
|---|---|---|---|
char | char(length) | @Fields, @Variables | Fixed-length string. length required (1-32,766 in @Fields; 1-16,773,104 in @Variables). |
varchar | varchar(length) | @Variables only | Variable-length string. length required (1-16,773,100). Length(...) returns the actual content length at runtime, unlike char. |
number | number(precision, scale) | @Fields, @Variables | Numeric. precision required (1-30 in @Fields; 1-63 in @Variables); scale optional, default 0. |
date | date | @Fields, @Variables | Date, no parameters. In @Fields it can’t carry Default(...). |
time | time | @Fields, @Variables | Time (hour/minute/second), no parameters. In @Fields it can’t carry Default(...). |
timestamp | timestamp | @Fields, @Variables | Combined date and time (with microseconds), no parameters. In @Fields it can’t carry Default(...). |
field | field(FieldName) | @Variables only | Inherits the type of a FieldName field from some referenced table’s @Structure. |
list | list(type) | @Variables only | Dynamic list of type (scalar, struct(Template), or field(...); another nested list or matrix is not allowed, see E00283). No size in the declaration — the maximum internal capacity is fixed (9999 elements) and not configurable. Read/write an element with &Variable[index]; add/remove/query with methods (see List: methods). |
matrix | matrix(rows, cols, type) | @Variables only | Fixed-size 2D grid of rows x cols of type. Read/write an element with &Variable[row, col]. |
struct | struct(TemplateName) | @Variables only | Instance of a struct TemplateName [ ... ] template defined inline in the global scope (see Struct: templates and member access). Access a member with &Variable.Member. |
Examples
@Variables {
global {
Username char(10)
Destination varchar(200)
Balance number(9,2)
RegDate date
RegTime time
RegTimestamp timestamp
Status field(ADSTS50)
List1 list(number(5))
Board matrix(8, 8, char(1))
}
}
char/varchar interoperability
char and varchar interoperate in assignments — the rule is purely about length (the source must fit into the destination), regardless of which one is fixed or variable:
&VarcharDestination = &CharSource // valid if length(CharSource) <= length(VarcharDestination)
&CharDestination = &VarcharSource
List/Matrix: indexing
&List1[1] = 100
&Board[3, 5] = "X"
The index is 1-based. List/Matrix are only valid in @Variables, never in @Fields.
List: methods
list carries no size in its declaration (list(type), no capacity parameter): the maximum internal capacity is fixed at 9999 elements, defined by the compiler and not configurable from the language. Instead of a fixed size up front, content is managed through methods, using member syntax (&Variable.Method(...)):
| Method | Use | Description |
|---|---|---|
Add(value) | statement | Appends value to the end of the list. value must be type-compatible with the list’s element type (if the element is struct(Template), value must be a &Variable of that exact same template). |
Clear() | statement | Empties the list (the element count goes back to 0). |
Remove(index) | statement | Removes the element at position index (1-based, number type) and shifts the following ones back one position. |
Count() | expression | Returns the current number of elements (number type). Can only be used inside an expression, never as a standalone statement. |
&List1.Add(10)
&List1.Add(20)
&List1.Add(30)
For &i = 1 To &List1.Count()
// ... &List1[&i] ...
EndFor
&List1.Remove(2)
&List1.Clear()
There’s no For Each over an in-memory list — traversal is done with a classic For counter (For &i = 1 To &List.Count()) plus indexing (&Variable[&i]); For Each In remains exclusive to database tables (see @Source: control statements).
If Add(value) is called while the list is already at its maximum capacity (9999 elements), the program issues a visible dsply with the message Lista {name} alcanzó su capacidad máxima (9999) and does not add the element — it doesn’t abort the whole program.
list(struct(Template)) is valid (a list of instances of the same struct). Nesting list/matrix inside each other is not, in any combination: list(list(...)), list(matrix(...)), matrix(list(...)), and matrix(matrix(...)) produce the compile error E00283 (“‘&{variable}’ es un {list/matrix} cuyo elemento no puede ser {list/matrix} (sin equivalente RPG generable): use un tipo escalar, struct(…) o field(…).”), caught in semantic analysis — it isn’t possible to declare an array whose element is itself another array.
Struct: templates and member access
A named template with members, defined inline inside @Variables’s global scope:
@Variables {
global {
struct Person [
Id number(10, 0)
Name varchar(50)
Email varchar(100)
Age number(3, 0)
]
globalPerson struct(Person)
Name varchar(50)
}
PrintPerson {
person struct(Person)
}
}
&globalPerson.Name = "Your Name"
&Name = &globalPerson.Name
Do PrintPerson(&globalPerson)
-
Template members support
char/varchar/number/date/time/timestamp/field(FieldName)— you can’t nest anotherstruct, nor uselist/matrixas a member. -
A member is read/assigned with
&Variable.Member(both read and write). -
It can be passed as a parameter to, and returned from, an in-file
Procedure/Function— see Procedure / Function. -
An array of
structis declared withlist(struct(TemplateName))— see List: methods. -
Indexing
&List[i]on alist(struct(Template))is valid, but only in two exact forms: as a read, the whole value of an assignment to another&Variablethat isstruct(...)of the same template (&otherVariable = &List[i]); as a write,&List[i] = &otherVariable(another&Variablestruct(...)of the same template, never an expression or a literal). If the side of the assignment that should be the struct variable isn’t syntactically a&Variable,E00241is reported; if the template doesn’t match exactly the list’s,E00242is reported. Index checks (count,numbertype, 1..9999 range) apply the same as for any otherlist:&otherRecord = &List[1] &List[1] = &otherRecord For &i = 1 To &List.Count() ¤t = &List[&i] EndFor -
Not supported yet: a
structnested inside anotherstruct, orstructas a parameter of an external program (@Parameters/Call) — only as a parameter of an internalProcedure/Function.
@Parameters
Declares a Program’s parameters: one mode:&Variable entry per line. The type comes from the matching declaration in @Variables/global, it isn’t repeated here.
| Mode | Description |
|---|---|
in | Input parameter: the caller passes a value, this program can’t return it modified. |
inout | Input/output: the caller passes a value, and this program can modify it back. |
out | Output: this program sets it, the caller only reads it back. |
@Variables {
global {
MyDate date
MyTime char(8)
}
}
@Parameters {
inout:&MyDate,
inout:&MyTime
}
Doesn’t apply to Screen (doesn’t receive parameters) or tables.
@Layout
Describes the layout of a screen or report: Label, Input, Column, DataGridView, Block, or any other Name(...) as a generic control. Control names may carry @ optionally (@Label/Label are equivalent).
Controls with their own grammar
| Control | Syntax | Valid in | Description |
|---|---|---|---|
Label | Label(x, y, text) | Program, Screen | Static label at x,y. text can be &Variable, "literal" or a bare field name. x,y = 1,1 (exact corner) is forbidden — DDS rejects any field there. |
Block | Block("Name")[ Label(...), ... ] | Program | A named, printable group of Labels — an instance is printed with Print("Name") from @Source. |
Input | Input("Name", x, y, text, &Variable[, enabled]) | Screen | Editable field at x,y. "Name" identifies the control (read with CurrentInput()). &Variable is where the entered value is stored. enabled optional (default true): a literal, &Variable or a function returning number (non-zero = enabled), re-evaluated before every redraw. x,y = 1,1 is also forbidden. |
Column | Column(text, variableOrField[, Input|Hidden]) | inside DataGridView only | The optional 3rd argument makes the column editable (Input) or invisible (Hidden). |
DataGridView | DataGridView("Name", x, y, rows[, separator])[ Column(...), ... ] | Screen | A named grid at x,y showing rows visible rows. The name is required and unique among Input/DataGridView controls. See DataGridView for the full guide. |
Example (Screen)
@Layout {
@Label(1,2,&Program)
@Label(1,70,&MyDate)
@Input("txtRequestNo",3,1,"Request No.: ",&RequestNo,true)
@DataGridView("GridRequests",6,1,8," ") [
@Column("Country",&nCountry)
@Column("User",&cUser)
]
}
Example (Program, with a printable Block)
@Layout {
@Block("Header") [
Label(1,1,"Sales Report")
Label(2,1,&Today)
]
}
@Source {
Print("Header")
}
@Layout is optional in Program (a purely batch program, with no printed output, doesn’t need it) but required in Screen. Doesn’t apply to tables.
@Source: control statements
The imperative body of a Program/Screen: assignments, control flow, table access, Procedure/Function declarations, calls and reports.
For Each In: iterating over a table
For Each In Table [Index IndexName]
[Where Field = expr]...
// body, runs for each row found
[When None
// runs if no row was found
]
EndFor
Index: optional, picks the access path (defaults to the sequential-access one).Where: zero or more, filters by equality.When None: optional, runs if the loop found no rows.
Delete(): deleting the current row
For Each In Table
If &Expired
Delete()
EndIf
EndFor
No arguments — deletes the row the iteration currently has loaded. Only valid inside a For Each In (not inside a For Each Row, which iterates a DataGridView’s subfile, not a table); valid in both Program and Screen. A deleted row isn’t also updated, even if the body assigned one of its fields earlier in the same iteration.
For Each Row: iterating over a DataGridView
For Each Row
// body — runs once per row already loaded into the subfile
EndFor
No Index/Where/When None (there’s no table being iterated). Only valid inside any Event of a Screen with a DataGridView. Before each iteration it writes each Column (including Hidden ones) into its bound variable — see DataGridView.
Classic For: counter
For &Variable = start To end [Step increment]
// body
EndFor
Counts from start to end, incrementing &Variable by increment each time around. Step is optional (default 1); with a negative Step, start must be greater than end to count downward.
Iterating over a List
There’s no For Each over an in-memory list (For Each In is exclusive to database tables, see below). It’s traversed with a classic For, using Count() as the limit and indexing (&Variable[&i]):
For &i = 1 To &List.Count()
// ... &List[&i] ...
EndFor
See Data types: List for the full syntax of Add/Clear/Remove/Count.
New: inserting a row
New In Table
Field = expr
...
[When Duplicate
// runs if the insert collides with an existing unique key
]
EndNew
If / While
If condition
...
Else
...
EndIf
While condition
...
EndWhile
condition combines comparisons with And/Or (e.g. &a = 1 And &b = 2 Or &c = 3). And binds tighter than Or (a And b Or c reads as (a And b) Or c).
Procedure / Function
Procedure Name(¶m)
...
EndProc
Function Name(¶m) returnType
...
Return expr
EndFunc
Procedure(no return value,subis a synonym): called withDo Name(expr, ...).Function(with a return value, type required right after the parameter list — same syntax as@Fields/@Variables): called as an expression,&variable = Name(expr, ...), type-checked against&variable.- A parameter/return value can be
struct(Template)— see Data types: Struct. The argument passed must always be a variable declared with that exact same template (never an expression nor a different template, even if it has the same members).
Call vs Do
| Statement | Use |
|---|---|
Call(Name, expr, ...) | Calls an external program or screen (one of @References/Programs or @References/Screens), resolved at compile time. |
Call("Name", expr, ...) | Calls an external program by name, resolved at runtime. |
Do Name(expr, ...) | Calls a Procedure from this same file (always an unquoted name). |
A Screen referenced in @References/Screens is called with Call(Name, ...) exactly like a Program — typically to show it as a pop-up window (see @Properties). The called Screen can declare @Parameters just like a Program.
Print("BlockName")
Prints an instance of a Block from @Layout — see @Layout. Writes 132-character lines to the QPRINT spool (OVRPRTF + open at the start, close at the end); with Print Mode: Screen the spool is also shown (DSPSPLF) and deleted (DLTSPLF) when the program finishes.
Member / Export: reports with data members
Member(Table, expr)
Export(Table, destination)
See the full guide: Reports with Member()/Export().
Member(Table, expr): attaches a data member toTablefor the whole program (ADDPFM+OVRDBF, before anyopen). InProgram, only at the top level of@Source; inScreen, only at the top level ofEvent Init(see @Source: Event and Screen). At most once per table.Export(Table, destination): copiesTable’s active member to an IFS stream file (CPYTOIMPF, comma-delimited). Only valid inProgram(not inScreen); no position restriction within it.
Return
Return [expr]
Exits a Function (the value is required if you want to honor the declared return type).
@Source: Event and Screen
Statements exclusive to Screen files.
Event
Event StandardName
...
EndEvent
Event 'Label' KeyNumber
...
EndEvent
Two forms: standard event (bare, unquoted name) or a custom event attached to a function key (quoted name + key number, 1-24, i.e. F1-F24; F13-F24 via Shift).
Standard events
| Event | When it fires |
|---|---|
Enter | On pressing Enter (with no function key active). CurrentInput() identifies which Input had focus, decoded from the cursor position after EXFMT. |
Close | Fixed to F3 in the generated DSPF (CF03) — always runs (even empty) and ends the screen; doesn’t require a declaration to attach the key. |
Load | Once, before the first display — typically contains the For Each/LoadRow() that fills the DataGridView. Page Up/Down are native scrolling over what’s already loaded; this event never runs again. |
Refresh | On pressing F5, if F5 is declared in the .warpcfg’s @FunctionKeys and isn’t taken by a custom Event 'Name' 5. |
Init | Once, the first time this screen starts, before the first display. Does nothing if not declared. The only place in a Screen where Member(...) is valid — see Reports with Member()/Export(). |
Custom events
Event 'FindCustomer' 4
Message("F4 was pressed", Info)
EndEvent
The label shown in the screen’s attention line is configured in the project’s .warpcfg @FunctionKeys (see Project), not in the Event itself.
Message
Message(text, Error|Warning|Info)
Shows a message on the screen’s message line: Error (red), Warning (yellow), Info (normal text color). Shown on the next screen refresh and cleared after one cycle.
LoadRow / RefreshSelectRow / For Each Row
Exclusive to screens with a DataGridView — see the full guide: DataGridView.
| Statement | Use |
|---|---|
LoadRow() | No arguments. Takes the current values of the variables bound to each Column and appends a row to the grid. Only valid inside a For Each in the body of Event Load (that For Each must declare Index). |
RefreshSelectRow() | No arguments. The reverse of LoadRow(): re-reads the row under the cursor and writes each column (including Hidden ones) back into its bound variable. Valid in any Event of a Screen with a DataGridView. |
For Each Row ... EndFor | Iterates over every row already loaded into the subfile (not the table), writing each Column into its bound variable before every iteration. No Index/Where/When None. |
CurrentInput
&Control = CurrentInput()
Returns the Name of the Input/DataGridView (from @Layout) that currently has focus. For a DataGridView, it matches if the cursor lands anywhere in its rectangle (header, attention row, or any data row), not just an exact cell. Only valid inside an Event’s body — using it anywhere else is an error.
Builtin functions
Usable as an expression anywhere, e.g. &Username = USERID(). Most take zero arguments with a fixed return type; Val() is the exception (see its own entry).
| Function | Return | Description |
|---|---|---|
USERID() | char(10) | IBM i user profile running this program. No arguments. |
PGNAME() | char(10) | Object name of this same program — a compile-time constant, not a runtime lookup. No arguments. |
TODAY() | date | Current system date. No arguments. |
TIME() | char(8) | Current system time formatted "HH:MM:SS" (8 characters). No arguments. |
Val(&CharVariable) | same as the assignment’s destination | Parses a char value into a Number, using the destination of the assignment’s precision/scale — unlike other builtins, it has no fixed return type. Only valid directly as &NumberVariable = Val(&CharExpr); using it anywhere else (nested in another expression, as a Do/Call argument, etc.) is an error. |
CurrentInput() | char(30) | See @Source: Event and Screen. |
String(&NumberVariable, integerDigits[, decimalDigits]) | char(integerDigits [+ 1 + decimalDigits]) | Converts a Number to char, e.g. to concatenate it in a Message(...) (which only accepts text). integerDigits/decimalDigits must be integer literals (compile-time constants) — the return width depends on them. decimalDigits is optional (default 0, no decimal separator in the result). Fixed width, zero-padded (no leading-zero suppression); doesn’t handle negatives specially. |
SubString(&Variable, start, length) | char(length) | Trims a fixed-width chunk out of a char value (variable or literal) so it fits into a smaller variable — e.g. &Short40 = SubString(&Long120, 1, 40). Without this, directly assigning a wider char to a narrower one is a compile-time error. start/length must be integer literals. When the source size is known at compile time, start + length - 1 exceeding it is also a compile-time error. |
Trim(&Variable) | same as the argument (char/varchar) | Removes blanks from both left and right. A single argument. |
LTrim(&Variable) | same as the argument | Removes blanks from the left. A single argument. |
RTrim(&Variable) | same as the argument | Removes blanks from the right. A single argument. |
Length(&Variable) | number | Size of a char/varchar value: for varchar, the actual runtime content length; for char, its declared fixed length. A single argument. |
IndexOf(needle, &haystack) | number | Searches for needle inside haystack and returns the raw position found (1-based if found, 0 if not), with no conversion applied. Exactly two arguments (text to search for, text to search in). |
Date, time and timestamp
Year/Month/Days/Hour/Minute/Second are polymorphic: they accept date/time/timestamp as appropriate (Year/Month/Days with date or timestamp; Hour/Minute/Second with time or timestamp) — there’s no need for a different name per type.
format (where it applies) is always optional: if omitted, it falls back to the value configured in @GeneratorConfig (Date Format/Time Format/Timestamp Format); if there’s no configuration either, the system default format is used.
| Function | Return | Description |
|---|---|---|
Year(&Variable) | number(4) | Year of a date/timestamp. |
Month(&Variable) | number(2) | Month of a date/timestamp. |
Days(&Variable) | number(2) | Day of the month of a date/timestamp. |
Hour(&Variable) | number(2) | Hour of a time/timestamp. |
Minute(&Variable) | number(2) | Minute of a time/timestamp. |
Second(&Variable) | number(2) | Second of a time/timestamp. |
DateDiff(&Date1, &Date2, Unit) | number | Difference between two dates, in Days/Months/Years (bare word, not a string). Positive when Date1 is later than Date2, negative otherwise. |
TimeDiff(&Time1, &Time2, Unit) | number | Same as DateDiff, over time, with Hours/Minutes/Seconds. |
TimestampDiff(&Ts1, &Ts2, Unit) | number | Same as DateDiff, over timestamp, with all 6 units enabled: Seconds/Minutes/Hours/Days/Months/Years. |
DateAdd(&Date, amount, Unit) | date | Adds (or subtracts, if amount is negative) days/months/years to a date. amount can be any number expression (not just a literal). |
TimeAdd(&Time, amount, Unit) | time | Same as DateAdd, over time, with Hours/Minutes/Seconds. |
TimestampAdd(&Ts, amount, Unit) | timestamp | Same as DateAdd, over timestamp, with all 6 units. |
IsDate(value[, format]) | number(1) | Validates whether value (char/varchar/number) is a valid date in format (optional — one of Iso/Usa/Eur/Jis/Mdy/Dmy/Ymd/Jul). Returns 1/0 (WARP has no boolean type). Not a pure expression — that’s why, just like Val(), it’s only valid directly as &NumberVariable = IsDate(...). |
IsTime(value[, format]) | number(1) | Same as IsDate, over time (format one of Hms/Iso/Usa/Eur/Jis). |
IsTimestamp(value[, format]) | number(1) | Same as IsDate, over timestamp (format one of Iso/Usa/Eur/Jis). Iso is the recommended, most widely compatible format. |
StringToDate(string[, format]) | date | Converts char/varchar to date. |
NumberToDate(number[, format]) | date | Converts number to date — same format rules as StringToDate. |
StringToTime(string[, format]) | time | Converts char/varchar to time. |
StringToTimestamp(string[, format]) | timestamp | Converts char/varchar to timestamp. |
DateToString(&Date[, format]) | char(10) | Converts date to text — same format rules. |
TimeToString(&Time[, format]) | char(8) | Converts time to text. |
TimestampToString(&Ts[, format]) | char(26) | Converts timestamp to text (26 = ISO width with microseconds). |
Now() | timestamp | Current system timestamp. No arguments. |
Examples
&Username = USERID()
&Program = PGNAME()
&Today = TODAY()
&CurrentTime = TIME()
&Quantity = Val(&QuantityText)
Message("Total: " + String(&Total, 9, 2), Info)
&Short = SubString(&Long, 1, 40)
&Clean = Trim(&WithSpaces)
&Size = Length(&Description)
&Pos = IndexOf("@", &Email)
&DueDate = DateAdd(&Today, 30, Days)
&DaysUntilDue = DateDiff(&DueDate, &Today, Days)
&IsValidDate = IsDate(&DateText, Dmy)
&Now = Now()
&HoursElapsed = TimestampDiff(&Now, &Start, Hours)
Expressions and operators
Arithmetic/concatenation operators
| Operator | Use |
|---|---|
+ | Numeric addition between Numbers, or concatenation between char/varchar (depending on the operand types). |
- | Numeric subtraction. |
* | Numeric multiplication. |
/ | Numeric division. |
&Total = &Price * &Quantity
&Greeting = "Hello, " + &Username
Comparison operators
=, <>, <, <=, >, >= — used in Where, If, While.
Logical operators
And/Or combine comparisons (typically in If/While): And binds tighter than Or.
If &a = 1 And &b = 2 Or &c = 3
...
EndIf
WARP has no boolean type of its own: each operand is validated like any other expression.
Literals
- Numbers:
100,9.5. - Text:
"text"or'text'(equivalent). - Date/time: obtained via
TODAY()/TIME(), there’s no date literal.
Variables and fields
- Variable: always with
&(&Username). - Table field: without
&, only inside aFor Each/Newover that table (CustCode,Name). list/matrixelement:&Variable[index]/&Variable[row, col](1-based).listmethod:&Variable.Method(...)(Add/Clear/Removeas a statement,Count()as an expression) — see Data types: List.structmember:&Variable.Member(read and assignment) — see Data types: Struct.
Function/procedure calls as an expression
&variable = FunctionName(expr, ...)
See Builtin functions and @Source: control statements for your own Functions.
Project file (.warproj / .warpcfg)
See also the guide: Project.
.warproj — @Project
| Key | Description |
|---|---|
Name | Project name. |
Description | Project description. |
Version | Project version (free-form string, e.g. 1.0). |
Default Profile | Name of the @Profile (defined in the .warpcfg, see below) used by generate/build/reverse/license-status when the CLI doesn’t receive --profile. Optional if the .warpcfg declares only one @Profile. |
@Generator (nested inside @Project)
| Key | Description |
|---|---|
Name | Generator name. |
Description | Generator description. |
Language | Target language. Values: RPGLE, RPG. |
@Paths (nested inside @Generator)
| Key | Description |
|---|---|
Config | Path to this project’s .warpcfg. |
Output | Folder where the generated DDS/RPG is written. |
.warpcfg — @GeneratorConfig
| Key | Description | Values |
|---|---|---|
Date Format | Native date format used to declare every Date variable (e.g. date(*dmy/) instead of plain date). | ISO, USA, EUR, JIS, MDY, DMY, YMD, JUL |
Date Separator | Date separator character (e.g. / in date(*dmy/)). A single character. | — |
Time Format | Native time format used to declare every Time variable (e.g. time(*hms:) instead of plain time), and default for IsTime/StringToTime/TimeToString when their format argument is omitted. Usa is 12-hour with an AM/PM suffix, no seconds — structurally different from the other 4 (24-hour hh:mm:ss). | Hms, Iso, Usa, Eur, Jis |
Time Separator | Time separator character (e.g. : in time(*hms:), also used by TIME()). A single character. Doesn’t apply to Usa (no separator between fields). | — |
Timestamp Format | Default for IsTimestamp/StringToTimestamp/TimestampToString when their format argument is omitted. The timestamp type doesn’t accept a format in its declaration, unlike date/time. Iso is recommended for its broader compatibility. | Iso, Usa, Eur, Jis |
Print Mode | What happens to the QPRINT spool file each Print("Block") writes to: with Screen the program displays it (DSPSPLF) and deletes it on exit; Printer/File just leave it spooled. Default Printer. | Printer, File, Screen |
Commitment | Enables commitment control for the whole project: generated programs declare their modified tables commit usropn and open them after a best-effort STRCMTCTL (tables must be journaled). A Program can override it with @Properties/Commitment. Default False. | True, False |
Commit on Exit | Whether generated programs issue a final commit before finishing. Overridable via @Properties/Commit on Exit. Only effective if Commitment is True. Default False. | True, False |
DDS Name | Pattern for naming each Screen’s display file DDS based on its object name — e.g. <ObjectName>D appends a D. Default <ObjectName> (unchanged). Only affects the DSPF; the generated RPG driver program keeps the object name as-is. The result is always uppercased and truncated to 10 characters (OS/400 object name limit) — the variants that append D truncate to 9 first so the D is never what gets truncated. | <ObjectName>, <ObjectName>D, D<ObjectName>, D<ObjectNameWithoutFirstChar> |
Top-level keys (Date Format, Print Mode, Commitment, etc.), @Screen/@FunctionKeys, and the date/time/timestamp formats are unique at the project level: they aren’t repeated per profile.
@Profile (nested inside @GeneratorConfig)
A .warpcfg declares one or more deployment profiles (typically Dev, UAT, Prod, though the name is free-form) inside @GeneratorConfig. Each @Profile "Name" groups its own @Connection, @Deployment, and @License — so a single project can point to different servers/libraries/licenses depending on the environment, without maintaining several .warpcfg files.
@GeneratorConfig {
@Profile "Dev" {
@Connection { ... }
@Deployment { ... }
}
@Profile "Prod" {
@Connection { ... }
@Deployment { ... }
@License { ... }
}
}
Declaring at least one @Profile is mandatory: @Connection/@Deployment/@License declared directly inside @GeneratorConfig (without wrapping them in @Profile) are no longer valid (error E00290). A @GeneratorConfig with no @Profile at all gives error E00291.
How the active profile is chosen for generate/build/reverse/license-status (the validate action ignores profiles):
- The CLI’s
--profile <Name>flag, case-insensitive. - If omitted,
Default Profilefrom@Project(see the.warproj — @Projectsection above). - If there’s no
Default Profileeither but the.warpcfgonly declares one@Profile, that one is used. - If none of the above applies and there are several profiles declared, the compiler fails with error
E00293, listing the available profiles.
Requesting a profile that doesn’t exist (via --profile or Default Profile) fails with error E00292, also listing the available profiles.
@Screen (nested inside @GeneratorConfig)
| Key | Description |
|---|---|
Rows | Number of screen rows. Numeric, default 24. |
Columns | Number of screen columns. Numeric, default 80. |
@FunctionKeys (nested inside @Screen)
One F<n> : "Label" entry per line (n from 1 to 24, e.g. F3 : "Exit"). Attached to its own event with Event 'Name' <n> ... EndEvent in a Screen’s @Source.
@Connection (nested inside @Profile)
How to reach the IBM i:
| Key | Description | Values |
|---|---|---|
Host | Hostname or IP of the IBM i. Required. | — |
User | User profile to connect with. Required. | — |
Auth Method | How to authenticate. Required. password reads the password from the WARPGATE_PASSWORD environment variable; interactive, when run from the VSCode extension, prompts for it with a dialog box on every Build & Deploy (running the raw binary from a terminal, it behaves the same as password). | key, password, interactive |
Key File | Path to the private key file. Required only if Auth Method: key. | — |
Protocol | Transport protocol. Required. ftp can’t be combined with Auth Method: key. | ssh, sftp, ftp |
Port | TCP port. Numeric, default 22. | — |
Timeout | Connection timeout in seconds. Numeric (-128 to 127), default 30. | — |
ssh/sftp/ftp use native clients: there’s no need to have any ssh/scp/sftp/ftp client installed on the machine running the compiler.
Examples by protocol and authentication method
SSH or SFTP with a key (ssh/sftp are equivalent: the native client uses the same transport for both):
@Connection {
Host : MYHOST
User : MYUSER
Auth Method : key
Key File : ~/.ssh/id_ed25519_ibmi
Protocol : ssh
Port : 22
Timeout : 30
}
SSH or SFTP with a password — the password isn’t written into the .warpcfg; it’s read from the WARPGATE_PASSWORD environment variable, which must exist before opening VSCode (or the terminal the compiler runs from):
@Connection {
Host : MYHOST
User : MYUSER
Auth Method : password
Protocol : sftp
Port : 22
Timeout : 30
}
FTP with a password — FTP has no concept of key-based authentication (Auth Method: key with Protocol: ftp is a validation error), so it only accepts password/interactive, both read from the same WARPGATE_PASSWORD variable:
@Connection {
Host : MYHOST
User : MYUSER
Auth Method : password
Protocol : ftp
Port : 21
Timeout : 30
}
Generating the Key File
Auth Method: key needs an SSH key pair: a private one (the one Key File points to) and a public one, which must be authorized on the IBM i user profile.
- Generate the key pair (on the machine running the compiler/VSCode, not on the IBM i):
This createsssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_ibmi -N ""id_ed25519_ibmi(private — the path that goes inKey File) andid_ed25519_ibmi.pub(public).-N ""leaves the key without a passphrase, a requirement forAuth Method: key. - Copy the contents of
id_ed25519_ibmi.pub(a single line) to the end of the.ssh/authorized_keysfile inside the IBM i user’s IFS home directory (e.g./home/MYUSER/.ssh/authorized_keys) — create the file and directory if they don’t exist. The IBM i’s SSH daemon (SSHD, from product 5733-SC1) must be active and configured to accept key-based authentication. - Verify that
Key Filein@Connectionpoints to the private key’s path on the local machine (not the.pubone), and thatAuth Methodiskey.
@Deployment (nested inside @Profile)
Where/how to deploy and compile on the IBM i:
| Key | Description | Values |
|---|---|---|
CL Source | Name of the generated CL source member (OS/400 object name, max. 10 characters). Required. | — |
Cleanup | Whether the staged source is removed after a successful compile. | true, false |
Target Release | Target OS/400 release for the compile (e.g. V7R4M0). | — |
Optimization | Compiler optimization level. | — |
Debug | Whether to compile with the debug view enabled. | true, false |
Temp Path | IFS staging directory used during deploy. Required. | — |
Data Library | Library where the compiled DDS physical/logical file objects are created (OS/400 object name). Required. | — |
Objects Library | Library holding the DDS/CL source members (QDDSSRC/QCLSRC) and where the compiled CL program object is created (OS/400 object name). Required. | — |
Source Tables | Source member holding the physical/logical DDS source (OS/400 object name). Required. | — |
Source Programs | Source member holding the generated RPGLE/CL. | — |
Compile Library | Comma-separated list of libraries added to the compile job’s library list, in order, instead of just Data Library — for programs whose tables live across several libraries. | — |
@License (nested inside @Profile)
Optional block with the licensing configuration, specific to each profile — so Dev/UAT/Prod can consume different licenses or licensing services. Keys are case-insensitive. See the Licensing guide.
| Key | Description | Values |
|---|---|---|
Host | License service host. With Host the mode is organization and File is ignored; without Host, the mode is individual. | — |
Port | Service TCP port. Default 7443. | 1-65535 |
Fingerprint | SHA-256 fingerprint of the service certificate (accepts : and uppercase). | 64 hex |
SPN | Kerberos service name. Default warpgate-license/<host>. | — |
File | Individual license file. Default ~/.config/warpgate/license.json (%APPDATA%\warpgate\license.json on Windows). | — |
@License {
Host : "servidor01.dominio.local"
Port : 7443
Fingerprint : "3f2a9c41d87b05e6a1c4f0937be2d5688a1f4c0d29e7b3a65c8d1f0e4b7a9c23"
}
Full example
See Project: .warproj and .warpcfg for a complete example with both files.
Diagnostics and error messages
The compiler reports every issue with an E##### code (e.g. E00221), a severity level, and a message — currently only in Spanish — with the concrete values interpolated.
Levels
| Level | Meaning |
|---|---|
error | Blocks code generation; the object doesn’t compile. |
warning | Doesn’t block compiling, but flags something probably unintentional (e.g. a variable declared and never used). |
What they look like
On the command line (human format):
[2026-07-24T21:26:47Z] ERROR E00221 (line: 12, column: 5): La variable 'x' declara char(16773105), fuera del rango permitido (1-16773104).
Or as JSON (--format json, the one the VSCode extension consumes to draw red/yellow underlines in the editor):
{"errors": [{"line": 12, "column": 5, "severity": "error", "message": "..."}]}
Some representative examples
| Code | Situation |
|---|---|
E00001 | A @Name section was expected and something else was found. |
E00128 | A file referenced by the .warpcfg (e.g. the SSH key in @Connection) doesn’t exist on this machine — doesn’t block validate/generate, but will be needed for build/reverse. |
E00152 | A variable from @Variables was declared but never used (warning). |
E00195 | Invalid use of RefreshSelectRow()/For Each Row outside a Screen with a DataGridView. |
E00221 | char outside the allowed range for a standalone variable in @Variables (1-16,773,104). |
E00222 | number with precision out of range (1-63) in @Variables. |
E00223 | number whose scale is greater than its precision. |
E00224 | varchar outside the allowed range (1-16,773,100). |
E00238 | struct(Name) references a template that doesn’t exist in @Variables. |
E00240 | &Variable.Member access to a member that doesn’t exist in the struct’s template. |
E00242 | A struct(OtherTemplate) variable was passed where a different template was expected (the comparison is by exact name, not by structure). |
E00269 | A table’s Description (@Structure) exceeds the 50 characters allowed by TEXT in DDS. |
E00270 | A field’s Description (@Fields) exceeds the 50 characters allowed by TEXT in DDS. |
E00271 | An index’s Description (@Indexes) exceeds the 50 characters allowed by TEXT in DDS. |
E00281 | The Description in @Properties (of a Program or Screen) exceeds the 50 characters allowed by TEXT in the compile-time CL commands (CRTBNDRPG/CRTDSPF/CHGPFM). Same limit and criterion as E00269/E00270/E00271. |
E00284 | @License Port is 0. |
E00285 | @License Host is not a valid host. |
E00286 | @License Fingerprint is not a valid SHA-256 fingerprint. |
E00287 | Warning: @License has Host but no Fingerprint. |
E00288 | Warning: @License has Host and File together (File is ignored). |
E00290 | @Connection/@Deployment/@License declared directly inside @GeneratorConfig, without wrapping them in a @Profile "Name". |
E00291 | @GeneratorConfig doesn’t declare any @Profile. |
E00292 | The profile requested with --profile or Default Profile doesn’t exist among the declared @Profiles (available ones are listed). |
E00293 | Several @Profiles are declared and it wasn’t indicated which one to use (neither --profile nor Default Profile), so the compiler can’t pick just one (available ones are listed). |
Each message has a code, a level (error/warning) and a template with the case’s actual details ({variable}, {length}, etc.) the compiler fills in when reporting it.
When they fire
- On saving/opening a
.warpfile in VSCode (warpgate.validateOnSave/validateOnOpen), if there’s a.warprojresolved for that file. - Manually with
warpgate --action validate— never touches the IBM i, purely local. - License note:
validatedoes not require a license;generate/build/reversedo (see Licensing). - As part of
generate/build/reverse— if there are errors, code generation/deployment doesn’t proceed.
IntelliSense in VSCode
The WaRPGate extension for VSCode adds language support for .warp files:
- Syntax highlighting: sections, keywords, data types, event names, function calls, operators.
- Autocomplete (
@/&as triggers): sections,@Sourcekeywords, data types, builtin functions (with a snippet), per-section property keys,@Layoutcontrols. - Hover: this same book’s documentation, summarized in one line, when hovering over any keyword/function/type.
- Live diagnostics: validates on open/save (see Diagnostics), drawing red/yellow underlines directly in the editor.
- Outline/breadcrumbs: section structure and
Procedure/Functionas navigable symbols. - Go to definition and document links: jump to the table/program/screen referenced in
@References. - Layout Preview: live preview of
@Layoutas an IBM i 5250 terminal screen (24x80 by default, or the size configured in the project’s.warpcfg@Screen). - Commands: see VSCode commands for a screenshot example of each (
Validate File,Generate,Build & Deploy,Select Project File,Reverse Engineer DDS,Preview Layout,Select Layout Block).
IntelliSense stays in sync with every new language release, so highlighting, autocomplete and diagnostics always reflect the compiler’s current capabilities.
Installation
The packaged .vsix includes the compiler binaries for all 3 platforms — there’s no need to install the compiler separately.
VSCode commands
The WaRPGate extension adds 8 commands to the command palette (Ctrl+Shift+P / F1), all prefixed with WaRPGate:. This page shows a real example of each, with screenshots.
WaRPGate: Validate File
Runs the compiler in validation mode on the active .warp file: checks syntax and semantics (types, references, undeclared variables, etc.) without generating any file. Errors and warnings show up in the “WaRPGate” output channel.
Example: a Program that assigns an undeclared variable.
@Source {
&CodigoCliente = &Inexistente
}
WaRPGate: Generate
Runs the full compiler on the active file: validates and, if there are no errors, generates the RPGLE/DDS/CL files locally (nothing is deployed to IBM i). It’s the equivalent of the CLI’s --action generate.
WaRPGate: Build & Deploy
Generates just like the previous command and additionally uploads the files to the IBM i configured in the .warpcfg’s @Connection, to compile them there (CRTPF/CRTLF/CRTBNDRPG/CRTDSPF). Since this connects to a real system, it always asks for confirmation before continuing.
WaRPGate: Select Project File (.warproj)
When the workspace has more than one .warproj, this command picks which one to use for validating/generating/deploying. The chosen project is remembered for the other commands.
WaRPGate: Reverse Engineer DDS…
Reconstructs a table .warp file from a DDS member that already exists on IBM i. It asks, in order: the QSYS.LIB path of the table’s DDS member, the index (LF) DDS members to include in @Indexes (optional), and the name of the .warp file to generate. At the end it confirms before connecting.
WaRPGate: Preview Layout
Shows the active file’s @Layout as an IBM i 5250 terminal screen (see IntelliSense in VSCode). If the Screen uses Popup: True, the preview draws the window at its real position and size within the full screen.
Example: a popup Screen with a 3-column DataGridView.
WaRPGate: Select Active Profile (@Profile)
Available from the command palette and from the editor’s context menu on a .warp file. Opens a picker with the profiles (@Profile) declared in the active project’s .warpcfg, plus the “Use project default” option. The choice is remembered for that project (persists across VSCode restarts) and is reflected in its own status bar item (layers icon), which also opens the same picker with a click.
Without an explicit choice, the extension doesn’t send --profile to the compiler and the CLI falls back to the Default Profile declared in @Project — see Project file.
WaRPGate: Select Layout Block
When a Program (report) has more than one Block in its @Layout, this command picks which one to show in the Layout Preview — the preview always shows a single Block at a time.