A native Qt5 editor — and, in its bundled form, a completely self-contained IDE — for writing Ada and building / flashing / debugging the bare-metal ESP32-S3 Ada runtime. A clean-room successor to SETEdit (Turbo Vision, text-mode) rebuilt on Qt + QScintilla, reusing SETEdit's Ada highlighting rules and a Borland-flavoured keybinding UX.
Prebuilt Linux AppImages are published on the Releases page.
No install, no dependencies — download, chmod +x, run:
| Download | Size | What's inside |
|---|---|---|
AdaEdit-full-x86_64.AppImage |
~680 MB | The whole IDE: editor + GNAT xtensa-esp32-elf cross-compiler + gprbuild + ada_language_server + OpenOCD/gdb + the ESP32-S3 SDK (runtime + ./x). Builds, flashes and debugs firmware with nothing else installed — the 1980s Borland-box experience. |
AdaEdit-x86_64.AppImage |
~30 MB | Editor only (Qt5 + QScintilla). Language and build features use whatever ada_language_server / toolchain are already on the host. |
chmod +x AdaEdit-full-x86_64.AppImage
./AdaEdit-full-x86_64.AppImage
# if FUSE isn't available (e.g. some WSL2 setups):
APPIMAGE_EXTRACT_AND_RUN=1 ./AdaEdit-full-x86_64.AppImage- Requirements: x86_64, glibc ≥ 2.35 (built on Ubuntu 22.04). Runs natively on Linux and under WSL2 on Windows (Win11/WSLg for the GUI).
- First run seeds a writable SDK workspace at
~/.local/share/adaedit/sdk; later AppImage updates re-sync bundled SDK fixes into it automatically. - Flashing / on-chip debug needs one-time USB device access — the editor
offers Build ▸ Set up device access… (installs a udev rule; see §Device
access). On WSL2 you also bridge the board in with
usbipd-win.
To build the editor (or the AppImages) from source instead, see §Build.
- Self-contained: the full AppImage carries the entire toolchain, runtime, language server and debugger. Build/flash/debug works offline, out of the box.
- The folder is the project — open any folder; AdaEdit derives its build commands from the folder (an in-tree SDK example, or a standalone project).
- Native serial monitor and a multi-board device selector (no external terminal, no python).
- Refactoring via the Ada Language Server: project-wide Rename plus ALS code actions (Extract subprogram, Name parameters, …).
- Tabbed Ada editor (QScintilla) with the
AdaLexer(Ada 95 keywords from SETEdit + Ada 2005/2022 additions), line numbers, folding, auto-indent. Right-click a tab to Close, Close all others, or close everything to the left / to the right. - File: New / Open / Save / Save As / Save All (with unsaved-changes guard).
- Edit: Undo / Redo / Cut / Copy / Paste, Format (Ada) (whole file or the selection, via the language server), Rename symbol (F2) and Code actions (Ctrl+.) — see §Refactoring.
- Search: Find / Replace / Find next / Go to line / Go to definition / Complete. (Replace acts on the whole document.)
- Session restore: closing the editor remembers the window size/position, the dock layout, the open files and the active tab — reopening restores them.
- Settings (File → Settings): Dark mode; separate interface font and editor & dock font (monospaced by default); and a Keyboard Shortcuts editor — every command is rebindable, with conflict detection and reset.
There is no separate project file. A project is just a folder, and AdaEdit derives its Build/Flash/Run/Debug commands from the folder's structure:
- a folder under the SDK's
examples/→ driven via the SDK's./x {example}; - a standalone folder (its own
app.gpr+build.sh) → driven viaesp32-ada -C {root}(a project can live anywhere on disk).
From the File menu:
- New project… — pick (or create) a folder; AdaEdit scaffolds a buildable
standalone project into it (
esp32-ada init:src/main.adb,app.gpralreadywith-ing the runtime + HAL,board.ads,build.sh, …). - Open project… — open any folder (this also replaces the old "Open folder").
- Save project as… (duplicate) — copy the current project into a different / new folder (sources only — build outputs are skipped), then switch to the copy.
The explorer tree roots at the project folder; double-click any file to open it.
- The editor targets the ESP32-S3 only. Build / Flash / Run / Monitor are on the toolbar and the Build menu; they run at the project root and Save All first, so they never compile stale source.
- Runtime profile selector (toolbar): Auto (example default) /
Jorvik (light-tasking) / Embedded / Full. The choice maps to
ESP32S3_RTS_PROFILEand selects the runtime (crates/esp32s3_rts/<profile>-esp32s3). Persisted inQSettings. - Show runtime path (Build menu) echoes the runtime directory a build will link, flagging when that runtime isn't built yet.
The full AppImage bundles the ada_esp32s3 SDK; on first run it seeds a
writable copy at
~/.local/share/adaedit/sdk
This is where the ./x / esp32-ada launchers, the ESP32-S3 runtime (the
light-tasking / embedded / full profiles) and the build scripts live, and
where build outputs and compiled runtimes are written. AppImage updates re-sync
bundled SDK fixes into it automatically. It includes:
- Peripheral drivers — a HAL, still under active development. A reusable
hardware-abstraction layer in
libs/esp32s3_hal/: packages such asESP32S3.GPIO,ESP32S3.UART,ESP32S3.SPI,ESP32S3.I2C,ESP32S3.ADC,ESP32S3.Timer,ESP32S3.RMT,ESP32S3.LEDC,ESP32S3.I2S,ESP32S3.RTC,ESP32S3.RNG,ESP32S3.Temperature, … over an svd2ada-generated register layer. Coverage and APIs are still evolving — expect gaps and changes. A new project'sapp.gpralreadywiths the HAL, sowith ESP32S3.GPIO;works out of the box. (Some handle-based drivers need the embedded or full profile; the lock-free ones — GPIO, RNG, Temperature — build under all profiles.) - Examples in
examples/— a worked project per peripheral, e.g.esp32s3_gpio0_blink,esp32s3_adc_read,esp32s3_i2c_loopback,esp32s3_timer_count,esp32s3_rmt_loopback,esp32s3_lcd_i8080. Open one with File ▸ Open project to build / flash / debug it as a starting point.
(Building from source instead of the AppImage? Point the editor at a checkout of
the ada_esp32s3 SDK — the same tree.)
The latest SDK adds five peripheral drivers, each with a worked example that was
run on real hardware (every example's README.md captures the actual serial
output from a board). On the test setup the four I2C parts share one bus
(SDA = IO8, SCL = IO7), demonstrating the HAL's per-device + per-host locking.
| Driver | Device | Bus / addr | What was exercised on hardware |
|---|---|---|---|
ESP32S3.TCA9555 |
TI 16-bit I2C GPIO expander | I2C 0x20 |
probe (present); both output-port registers written + read back (PASS); per-pin set/clear; polarity register. INT line not wired. |
ESP32S3.SHT41 |
Sensirion SHT41-AD1B temp/humidity | I2C 0x44 |
detected at 0x44; temperature + humidity read from a live sensor sharing the bus with the RTC/IMU. |
ESP32S3.QMI8658C |
QST 6-axis IMU (accel + gyro) | I2C 0x6B |
WHO_AM_I = 0x05 confirmed; accelerometer + gyroscope sampled. Raw scaling is subject to QST's board-level calibration; INT polled (not wired). |
ESP32S3.PCF85063A |
NXP real-time clock | I2C 0x51 |
detected at 0x51; set/get time and a seconds-match alarm driven on a live chip. INT not wired on the test board. |
ESP32S3.GPS (+ .NMEA, .L76K) |
NMEA-0183 GPS receiver | UART | NMEA sentences parsed from a live L76K module; PCAS04 constellation-select tested. PCAS01 baud-rate command is coded but untested. |
These join the existing ESP32S3.* HAL. As above, the HAL is still under
development: the interrupt paths are largely coded-but-unwired on the test boards,
and some device features (IMU calibration, GPS baud configuration) aren't yet
exercised — see each example's README for the exact tested behaviour and caveats.
A native Serial monitor dock backed by Qt's QSerialPort — no external
miniterm/picocom/screen, no python:
- pick the port and baud, Connect, watch live output, and send a line (selectable CR/LF). The dock has a View toggle and remembers the last port/baud.
- Run = build + flash, then auto-opens the monitor; Flash/Run release the port first so the flasher can use it.
A Device dropdown on the toolbar enumerates connected USB serial devices —
labelled with description + serial number, so two identical boards are
distinguishable — plus a Rescan button. The selection is exported as
$ESPPORT, which steers flash, debug and the monitor to the same board at
once (OpenOCD maps the tty to that board's USB-JTAG adapter serial and pins it).
Auto leaves the SDK default (/dev/ttyACM0).
Flashing (/dev/ttyACM*) and USB-JTAG debug need device permissions the AppImage
can't grant itself. Build ▸ Set up device access… runs the SDK's one-time
installer (via pkexec): it drops a udev rule (60-esp32-ada.rules, covering the
Espressif 303a USB-JTAG and common CP210x/CH340/FTDI bridges) and adds you to
the device groups. The editor also offers this automatically if a flash/run fails
on permissions. Equivalent CLI: ./x setup-device.
- Rename symbol (right-click ▸ Refactor, Edit menu, or F2): a project-wide
rename via
textDocument/rename— every file that references the symbol is updated (left modified for review → Save All). - Code actions (right-click ▸ Refactor ▸ Code actions, Edit menu, or Ctrl+.): every refactoring/quick-fix ALS offers at the cursor — e.g. Name parameters in the call, Extract Procedure — applied via the server.
- Start debugging launches OpenOCD (:3333), runs gdb in MI mode, connects and
stops at
app_main. Continue / Step over / Step into / Restart / Stop / Pause drive the session; the current line is marked and a Debug console shows gdb output and takes raw gdb commands. - Breakpoints: click the left margin (or Toggle breakpoint); a Breakpoints pane lists them (enable/disable, double-click to jump). They re-arm on each Start, are cleared when you switch projects, and fall back to matching by file name if the full path doesn't resolve.
- Dual-core (SMP) presents both LX7 cores as gdb threads; Attach
(post-mortem) connects and
monitor halts in place (no reset) to inspect a hang. Threads / Call stack / Variables & watch panes track the stopped target. - On-chip flash software-breakpoints are off by default (OpenOCD's flash
auto-probe is unreliable for some bare-metal images and can reject the GDB
connect); debug uses the two hardware breakpoints instead, which works in every
profile. Set
ESP_FLASH_SIZE=<MB>to re-enable flash breakpoints.
Defaults follow modern conventions for editing and Borland Turbo C/C++ for build/debug. All are rebindable in Settings → Keyboard shortcuts.
| File | Ctrl+N New · Ctrl+O Open file · Ctrl+K Ctrl+O Open project · Ctrl+S Save · Ctrl+Shift+S Save As · Ctrl+Alt+S Save All · Ctrl+Q Exit |
| Edit | Ctrl+Z Undo · Ctrl+Y Redo · Ctrl+X/C/V Cut/Copy/Paste · Ctrl+Shift+F Format · F2 Rename · Ctrl+. Code actions |
| Search | Ctrl+F Find · Ctrl+H Replace · F3 Find next · Ctrl+G Goto line · F12 Go to definition · Ctrl+Space Complete |
| View | Ctrl+Shift+1..9 toggle docks |
| Build | F9 Build · Alt+F9 Flash · Shift+F9 Run · Alt+F5 Monitor |
| Debug | F5 Start · Ctrl+F9 Continue · F8 Step over · F7 Step into · Ctrl+F6 Pause · Ctrl+Shift+F2 Restart · Ctrl+F2 Stop · Ctrl+F8 Toggle breakpoint · Ctrl+F7 Add watch |
Opening an Ada file starts ada_language_server (bundled in the full AppImage, or
auto-located from PATH / the AdaCore VS Code/Zed extension) pointed at the nearest
.gpr. Go to definition (F12), Quick info (hover), Format, Rename
and Code actions all flow over LSP/JSON-RPC; the buffer is synced before each
request. Diagnostics appear as squiggles and in a Problems pane.
Completion (Ctrl+Space) draws from ALS.
Semantic highlighting layers ALS's semanticTokens over the fast Scintilla
lexer: identifiers are recoloured by their meaning — packages, types,
subprograms, parameters and enum literals each get their own colour (theme-aware),
something a lexer can't determine. It refreshes (debounced) as you type and falls
back to plain lexer highlighting whenever ALS is absent or still indexing, so
editing stays responsive.
Note: ALS indexes the project for a few seconds after a file opens — a request made before indexing finishes may return "not found"; just retry.
cmake -S . -B build
cmake --build build -j
./build/adaedit some_file.adbThe repo packages the AppImages with packaging/build-appimage.sh
(BUNDLE=editor or BUNDLE=full); external components are fetched at build time
and pinned in packaging/manifest.env (nothing heavy is committed). GitHub
Actions builds both on every push and publishes them on a v* tag.
Only Qt5 + QScintilla — no other third-party libraries.
| Dependency | Tested |
|---|---|
| C++17 compiler (GCC/Clang/MSVC) | — |
| CMake ≥ 3.16 | 3.31 |
| Qt5 — Widgets + PrintSupport + SerialPort | 5.15 |
| QScintilla for Qt5 (dev) | 2.14 |
# Debian/Ubuntu
sudo apt install build-essential cmake qtbase5-dev qtbase5-dev-tools libqscintilla2-qt5-dev libqt5serialport5-dev
# Fedora
sudo dnf install gcc-c++ cmake qt5-qtbase-devel qscintilla-qt5-devel qt5-qtserialport-devel
# Arch
sudo pacman -S base-devel cmake qt5-base qscintilla-qt5 qt5-serialport
# macOS (Homebrew) — then: cmake -S . -B build -DCMAKE_PREFIX_PATH=$(brew --prefix qt@5)
brew install cmake qt@5 qscintilla2CMake locates QScintilla via Qt's own qmake paths plus the common prefixes; if
it lives somewhere unusual, pass -DQSCINTILLA_ROOT=<prefix>.
The app compiles cross-platform, but Build/Flash/Run/Monitor/Debug shell out via
/bin/sh and bash, so full functionality assumes a POSIX environment
(Linux/macOS, or WSL2 on Windows).
The full AppImage bundles all of these; a from-source editor needs them on the host for its actions to work:
ada_language_serveronPATH— completion, hover, diagnostics, format, refactor.- The GNAT
xtensa-esp32-elftoolchain +gprbuild(e.g. via Alire). - The
ada_esp32s3SDK —./x/esp32-ada, the runtime crate, build scripts. - OpenOCD + the pinned xtensa esp gdb — for on-chip debugging.
CMakeLists.txt build (Qt5 + QScintilla + SerialPort; portable QScintilla finder)
src/main.cpp entry point, CLI file args, session restore
src/mainwindow.* editor shell: tabs, menus, docks, settings, profile, device, actions
src/adalexer.* Ada highlighting via native Scintilla SCLEX_ADA
src/project.* derived ESP32-S3 command profiles + token expansion
src/debugger.* GDB/MI debug engine (server + gdb, step/continue/marker)
src/lspclient.* Ada Language Server client (definition, hover, completion, format, rename, code actions)
src/serialmonitor.* native QSerialPort serial console
packaging/ AppImage build script, AppRun hook, pinned manifest
docs/design-self-contained-ide.md self-contained IDE design notes