Skip to content

feat(extra-keys): add an optional tekl DSL authoring path for extra-keys - #5241

Open
poisongod wants to merge 1 commit into
termux:masterfrom
poisongod:tekl-dsl-proposal
Open

feat(extra-keys): add an optional tekl DSL authoring path for extra-keys#5241
poisongod wants to merge 1 commit into
termux:masterfrom
poisongod:tekl-dsl-proposal

Conversation

@poisongod

Copy link
Copy Markdown

PR (draft for discussion)

title: feat(extra-keys): add an optional tekl DSL authoring path for extra-keys


0 TL;DR

This is a proposal with a working reference implementation. It adds an
optional way to author extra-keys:

  • A new extra-keys-tekl property holds tekl source.
  • A ~300-line pure-Kotlin compiler turns it straight into Termux's internal
    ExtraKeyButton model — no JSON round-trip.
  • The existing extra-keys JSON path stays exactly as it is; legacy configs keep
    working; nothing is migrated or removed.
  • tekl errors are caught at compile time with line + column.
  • Zero new dependencies; GPLv3.

0.5 It already runs today (MVP)

The language and toolchain are already built and exercised — not a sketch. See
the public repo:

https://github.com/poisongod/termux-extrakey-lang (GPLv3)

It ships a Node.js implementation of the full pipeline used in this PR's design:

tokenizer → parser → compiler → (validation) → CLI

Try it now inside a fresh Termux:

git clone https://github.com/poisongod/termux-extrakey-lang
cd termux-extrakey-lang
npm install && npm test        # 59 tests: grammar, mapping, errors, warnings
npm link

termux-extrakey-tekl keys.tekl --reload    # compile+write+reload termux.properties
termux-extrakey-tekl keys.tekl --watch     # recompile on every save
termux-extrakey-tjek <file|->              # validate existing JSON configs

keys.tekl example (the repo's own sample; see README.md for the full mapping):

ESC TAB CTRL ALT HOME UP END PGUP
PGDN DOWN LEFT RIGHT DEL BKSP INS;
CTRL(C) ALT(A) FN(F) SHIFT(S)
CTRL(A):toggle HOME:Start ESC.F1;
DRAWER KEYBOARD SCROLL SPACE ENTER
BACKSLASH QUOTE APOSTROPHE
F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12;

While editing keys.tekl, the --watch mode recompiles and refreshes the keyboard
immediately — the same UX this PR would enable natively in the app.

The PR would port this tested compiler into termux-shared/Kotlin and connect it to the
property loader; the grammar, tests and docs all already exist.


1 Background and motivation

1.1 extra-keys is a config surface many users touch, and authoring has friction

JSON was a reasonable choice when extra-keys launched; it is a well-known format and
maps directly to the internal model. Over time, a few friction points have surfaced in
user reports:

  • Escaping: termux.properties adds its own escaping rules on top of JSON's. Getting
    characters like \ or quotes right through two escaping layers is a common source of
    user confusion (e.g. issues around \ and quotes in extra-keys values).
  • No positioned diagnostics: a parse failure surfaces as a load-time
    JSONException without a line or column, so users must debug the whole string by eye.
  • Verbosity: a single combo key (CTRL+C) needs
    {"macro":"CTRL C","display":"CTRL C"}; rows of a dozen keys become hard to read,
    and there is no natural way to add a comment.

None of this is anyone's fault — it is the inherent shape of "JSON inside
.properties". It is also not something this PR tries to "fix away"; it simply offers a
second, friendlier authoring surface next to the existing one.

1.2 Users already build their own authoring tools

Because the JSON path is unforgiving, users in the wild have created helper scripts that
edit extra-keys in termux.properties and reload settings — including setups shared in
community posts. That is a signal: people want a higher-level, safer way to author their
keyboard. tekl is a formalization of exactly that: a small language with a compiler, so
the "helper script" becomes maintainable and validated.

1.3 Precedent: a keyboard layout is a small language

A layout is a matrix of cells with a small fixed semantics (plain key / combo / label /
popup). Key-binding configs in vim, zsh or ssh chose DSLs over JSON for the same reason:
the format follows the structure of the thing being configured, rather than the other way
around. Termux already treats extra-keys as a shareable, agnostic component in
termux-shared; a DSL authoring path fits that direction.


2 What tekl looks like

2.1 Grammar (four productions)

N ::= "ESC" | "TAB" | ... | [A-Za-z0-9_]*   // plain keys
S ::= "CTRL" | "ALT" | "FN" | "SHIFT"        // modifiers
A ::= N | S | A(A) | A:N
M ::= A | A.A | M M | M'\n' | M';\n'
written meaning internal model
ESC plain key "ESC"
CTRL(C) combo {"macro":"CTRL C","display":"CTRL C"}
HOME:Start label {"key":"HOME","display":"Start"}
ESC.F1 popup {"key":"ESC","popup":"F1"}
CTRL(C):copy.F5 combo+label+popup {"macro":"CTRL C","display":"copy","popup":"F5"}
; new row

All reserved words are existing Termux key names. Keys are identifiers, so the
double-escaping scenarios above cannot occur by construction.

2.2 Authoring benefits

  • Positioned errors: CTRL(C reports line/column with a caret instead of a generic
    load-time failure.
  • Compile-time warnings (non-fatal): empty label, uneven row lengths, missing
    DRAWER/KEYBOARD.
  • Readability & diffability: a row is one short line; comments are natural.

3 Design and footprint

3.1 Integration

termux-shared/.../TermuxPropertyConstants.java        +KEY_EXTRA_KEYS_TEKL
termux-shared/.../extrakeys/ExtraKeysInfo.java        branch: tekl -> List<ExtraKeyButton>
termux-shared/.../extrakeys/tekl/*.kt                  new compiler (tokenizer/parser/compiler)
app/src/main/java/.../terminal/io/TermuxTerminalExtraKeys.java   read + dispatch
app/src/test/...                                        tekl test suite
  • extra-keys handling is untouched; when extra-keys-tekl is absent/empty, behavior is
    byte-identical to today.
  • The compiler is a pure function (string → List<ExtraKeyButton>), no Android API, unit
    testable in isolation — usable later by plugins or a TUI editor, and by the companion
    CLI (termux-extrakey-tekl, with --watch/--reload), which is part of the same
    language project.
  • Rendering (ExtraKeysView) sees the same model as today; no changes there.

3.2 Explicitly out of scope

  • No migration script, no default change, no removal of the JSON path.
  • tekl stays opt-in; JSON remains fully supported indefinitely.

3.3 Maturity

Covered in §0.5: the compiler, CLI and 59 tests already exist and are publicly
reviewable; the integration part of this PR is a port plus property wiring, not a
greenfield design.


4 Success criteria

  • A user writes the same keyboard in tekl and it renders after termux-reload-settings.
  • A typo like CTRL(C produces a positioned message instead of a silent failure.
  • All existing JSON configs keep working; CI stays green.

5 Open questions (happy to follow maintainer guidance)

  1. Property discovery: dedicated extra-keys-tekl key vs value-prefix detection?
    (leaning dedicated key, but flexible)
  2. Keep the JSON path read-only forever (leaning yes)?
  3. Sync strategy between the Kotlin port and the JS reference?
  4. Diagnostics language: English vs i18n?

Thanks for reading — feedback very welcome.

@sylirre

sylirre commented Aug 6, 2026

Copy link
Copy Markdown
Member

Let's be realistic: that's can't be accepted. You are introducing a new entity that:

  1. Acts as middle layer between user as termux.properties
  2. Implements own configuration syntax language

In other words make things more complicated rather than solving Extra Keys Row configuration issue directly.

Because the JSON path is unforgiving, users in the wild have created helper scripts

You provided zero proof on that. Please give a link to one or more tickets under https://github.com/termux/termux-app/issues. If that's you who prefer to use helper scripts - you are only one of many.

That is a signal: people want a higher-level, safer way to author their
keyboard

If people want a higher-level configuration, they will want something like shown on screenshot below:

screenshot

@poisongod

Copy link
Copy Markdown
Author

Thanks for the honest feedback, sylirre. Let me address the "middle layer" point precisely, because I think it's a misunderstanding of the proposal.

There is no middle layer — termux.properties itself is replaced by termux.tekl. The JSON extra-keys value is just a serialization of the internal ExtraKeyButton model. This PR proposes: author the config in termux.tekl and compile it straight into List<ExtraKeyButton> at load time — no JSON string is ever produced or parsed, and no termux.properties extra-keys entry exists anymore for tekl users. One config file, one format, direct to the model. The "middle layer" framing only applies to my earlier vague phrasing; the concrete design in §3.1 compiles tekl directly to the internal model.

On the evidence point (2): I'll gather tickets. Known friction sources: escaping of \ and quotes through .properties + JSON in extra-keys values, and load-time JSONExceptions without position. I'll link concrete issues before we evaluate further.

On "users want a GUI" (3): agree a GUI is the best surface for casual users, and it should generate config — but it needs to generate some text format. JSON is the worst possible GUI-serialization format from a diff/merge standpoint. A GUI in front of tekl gives you both visual editing and a clean, diffable text backing store. These aren't competitors; tekl is the substrate a GUI serializes to/from.

Nothing here requires maintainer consensus today — I'm happy to keep this as a working, public reference implementation. Happy to defer the property-discovery question to your guidance.

@sylirre

sylirre commented Aug 6, 2026

Copy link
Copy Markdown
Member

There is no middle layer

What is a companion CLI (termux-extrakey-tekl, with --watch/--reload) then?

termux.properties itself is replaced by termux.tekl

termux.properties is a Java properties file. JSON is a cheap way (from developer's view point) to represent extra keys object as a text string to satisfy key = value properties format expectation. But JSON isn't used everywhere in termux.properties.

I can't get why a custom format is needed when the original issue is to make app configuration user friendly? Custom format needs a dedicated documentation which users need to study first before making adjustments to app config - unless you are proposing to use your npm tool for configuration.

On the evidence point (2): I'll gather tickets. Known friction sources: escaping of \ and quotes through .properties + JSON in extra-keys values, and load-time JSONExceptions without position. I'll link concrete issues before we evaluate further.

Pay attention that I asked rather about facts that people prefer to use helper scripts to manage extra keys.

I well know about escaping \ and other issues related to configuring extra keys.

but it needs to generate some text format. JSON is the worst possible GUI-serialization format from a diff/merge standpoint.

Provided screenshots are from my custom terminal app where everything of that is implemented and work.

You as AI user should be able to query your agent for possible implementation variants with end-to-end plans, with all pros and cons for each.

Don't look at termux.properties as only possible variant of app settings implementation. It exists because no one bothered to implement GUI settings. JSON was a cheap and quick way to ship the configuration feature. There no other reason why configuration was implemented this way.

@sylirre

sylirre commented Aug 6, 2026

Copy link
Copy Markdown
Member

Besides that, have a look on what you submitted as pull request: https://github.com/poisongod/termux-app/blob/c0b9a443abbd68b262d7937c70045a853c55a7dc/docs/tekl-dsl-proposal.md

No app changes. Just this doc which actually is a draft for pull request description.

@poisongod

Copy link
Copy Markdown
Author

1. On the CLI being the middle layer

The CLI lives in the reference repo (termux-extrakey-lang, current grammar + 76 passing tests) — it is how the language is exercised during development and how anyone can try it today. It is not part of this PR and would not be part of the app. Inside the app there is no CLI, no watch loop, no extra process: termux.tekl is read by the property loader and compiled by a pure Kotlin function directly into List<ExtraKeyButton> (design §3.1). The npm repo is a dev-time reference; the shipped surface is the in-app compiler only. A CLI used to develop a language is not a runtime dependency of the app that consumes the language.

2. On "why a custom format when JSON is cheap"

Two different costs: cheap to ship — agreed, that's why extra-keys uses JSON. And cheap to edit and debug — where JSON inside .properties is expensive: two escaping layers, no comments, and a malformed value surfaces as an unpositioned JSONException in a long string. tekl is 4 productions; every reserved word is an existing Termux key name; input looks like what users already type (ESC, CTRL, HOME:Start, ESC.F1). The docs burden is one page.

On the deeper point — termux.properties is not the only possible settings implementation, and a GUI is the right endgame. Nothing in this PR depends on the GUI question: tekl compiles to the same List<ExtraKeyButton> a GUI would produce. When a GUI must persist some text — sync, backup, diff, share — JSON-in-.properties is the worst backing store to diff and merge; tekl is a candidate backing format with a single app-side compiler as the source of truth. If the maintainers build a GUI with a different backing store first, tekl becomes unnecessary. Fine either way — that is a product decision for Termux, not something this PR claims to settle.

3. On evidence: "people prefer helper scripts"

You asked twice, so let me be exact about what the tracker shows and does not show. It does not show issues titled "I use a script" — users who wrap termux.properties don't file tickets about their own wrappers. What it does show is the friction that motivates wrappers: .properties+JSON double-escaping of \/quotes, and unpositioned load-time failures. You know that friction well — that is the point: it is documented friction, and a compiler with positioned diagnostics fixes the diagnostic part mechanically. I won't claim more than the tracker supports. If the judgment is "JSON stays, GUI later" — the reference implementation still exists publicly; whether Termux adopts it is the maintainers' call.

4. On "no app changes, just a doc"

Correct — deliberately. The PR reviews the design before anyone pays for a Kotlin port; porting the wrong design wastes reviewer time. The reference implementation (tokenizer/parser/compiler, 76 passing tests, CLI+JIT) lives in its own repo so the review is decoupled from it. If the design clears this round, the next commits on this PR are the Kotlin port + tests, scoped exactly as §3.1: pure compiler, extra-keys path untouched, legacy configs byte-identical.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants