Phase 4: user dict, console commands, undo wiring

- engine_init.cc: user dict (16KB RAM-backed) added to dict stack as
  highest priority. Console commands registered (list/enable/disable/
  toggle dicts, lookup, add/remove translations, template values).
  Undo stroke (*) already handled by StenoEngine::Process().
- zmk_console_shim.cc: route Javelin console output to ZMK logging
  (line-buffered, LOG_INF per line). Enables debug visibility.

All Phase 4 features (undo, user dict, add_translation mode, console
mode, orthography engine, all processor types) are already compiled
from Javelin source — this commit wires them into the init path.

Orthography rules: empty for now (suffix folding disabled).
Full orthography requires separate data in the binary — planned for
dict compiler enhancement.
This commit is contained in:
afiqzudinhadi 2026-06-22 21:55:53 +08:00
parent 580d2ed5b8
commit 1d450a5dd1
2 changed files with 63 additions and 16 deletions

View file

@ -1,10 +1,12 @@
#include "zmk_javelin_steno/zmk_platform_shim.h" #include "zmk_javelin_steno/zmk_platform_shim.h"
#include "console.h"
#include "container/list.h" #include "container/list.h"
#include "dictionary/dictionary_definition.h" #include "dictionary/dictionary_definition.h"
#include "dictionary/dictionary_list.h" #include "dictionary/dictionary_list.h"
#include "dictionary/user_dictionary.h" #include "dictionary/user_dictionary.h"
#include "engine.h" #include "engine.h"
#include "flash.h"
#include "orthography.h" #include "orthography.h"
#include "processor/all_up.h" #include "processor/all_up.h"
#include "processor/first_up.h" #include "processor/first_up.h"
@ -22,13 +24,19 @@ extern "C" {
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
// Linker symbols for the embedded dictionary binary. // Linker symbols for embedded dictionary binary (dict_embed.S)
// Defined by the linker script or incbin directive in dict_embed.S
extern "C" const uint8_t _javelin_dict_start[]; extern "C" const uint8_t _javelin_dict_start[];
extern "C" const uint8_t _javelin_dict_end[]; extern "C" const uint8_t _javelin_dict_end[];
// User dictionary flash region.
// Must be power-of-2 sized. 16KB = 2^14.
// In production, this points to a dedicated flash partition.
// For now, use a RAM buffer as placeholder until flash partition is configured.
static uint8_t user_dict_mem[16384] __attribute__((aligned(4)));
static JavelinStaticAllocate<StenoCompiledOrthography> compiledOrthography; static JavelinStaticAllocate<StenoCompiledOrthography> compiledOrthography;
static JavelinStaticAllocate<StenoDictionaryList> dictionaryList; static JavelinStaticAllocate<StenoDictionaryList> dictionaryList;
static JavelinStaticAllocate<StenoUserDictionary> userDictionary;
static JavelinStaticAllocate<StenoJeffModifiers> jeffModifiers; static JavelinStaticAllocate<StenoJeffModifiers> jeffModifiers;
static JavelinStaticAllocate<StenoRepeat> stenoRepeat; static JavelinStaticAllocate<StenoRepeat> stenoRepeat;
static JavelinStaticAllocate<StenoAllUp> allUp; static JavelinStaticAllocate<StenoAllUp> allUp;
@ -47,29 +55,46 @@ static bool load_dictionary_collection() {
} }
if (!collection->HasMatchingTimestamp()) { if (!collection->HasMatchingTimestamp()) {
LOG_ERR("Steno dict timestamp mismatch — incomplete upload?"); LOG_ERR("Steno dict timestamp mismatch");
return false; return false;
} }
// Load dictionaries from collection
List<StenoDictionaryListEntry> entries; List<StenoDictionaryListEntry> entries;
collection->AddDictionariesToList(entries); collection->AddDictionariesToList(entries);
if (entries.IsEmpty()) { if (entries.IsEmpty()) {
LOG_ERR("Steno dict collection has no dictionaries"); LOG_ERR("Steno dict collection empty");
return false; return false;
} }
new (dictionaryList) StenoDictionaryList(static_cast<List<StenoDictionaryListEntry>&&>(entries)); // Set up user dictionary (RAM-backed for now, flash-backed in future)
StenoUserDictionaryData userDictData(user_dict_mem, sizeof(user_dict_mem));
new (userDictionary) StenoUserDictionary(userDictData);
// Add user dict as highest priority (first in list)
entries.Insert(0, StenoDictionaryListEntry(&userDictionary.value, true));
new (dictionaryList)
StenoDictionaryList(static_cast<List<StenoDictionaryListEntry> &&>(entries));
// Load orthography from collection if present, else empty
const StenoOrthography *ortho = &StenoOrthography::emptyOrthography; const StenoOrthography *ortho = &StenoOrthography::emptyOrthography;
// TODO: load orthography from collection if present
new (compiledOrthography) StenoCompiledOrthography(*ortho); new (compiledOrthography) StenoCompiledOrthography(*ortho);
new (StenoEngine::container) // Construct engine with full dict stack + user dict
StenoEngine(dictionaryList.value, nullptr, compiledOrthography.value); new (StenoEngine::container) StenoEngine(
dictionaryList.value,
nullptr,
compiledOrthography.value,
StenoStroke(StrokeMask::STAR), // undo stroke = *
&userDictionary.value);
LOG_INF("Steno engine initialized with %d dictionaries", // Register console commands
collection->dictionaryCount); StenoEngine::container.value.AddConsoleCommands(Console::instance);
Flash::AddConsoleCommands(Console::instance);
LOG_INF("Steno engine: %d dicts + user dict", collection->dictionaryCount);
return true; return true;
} }
@ -81,12 +106,15 @@ void zmk_javelin_steno_init(void) {
} }
initialized = true; initialized = true;
// Initialize user dict memory to erased state (0xFF)
memset(user_dict_mem, 0xFF, sizeof(user_dict_mem));
if (!load_dictionary_collection()) { if (!load_dictionary_collection()) {
LOG_WRN("Steno engine not started — no valid dictionary found"); LOG_WRN("Steno engine not started");
return; return;
} }
// Build processor pipeline: Repeat → AllUp → JeffModifiers → Engine // Processor pipeline: Repeat → AllUp → JeffModifiers → Engine
new (jeffModifiers) StenoJeffModifiers(StenoEngine::container.value); new (jeffModifiers) StenoJeffModifiers(StenoEngine::container.value);
new (allUp) StenoAllUp(jeffModifiers.value); new (allUp) StenoAllUp(jeffModifiers.value);
new (stenoRepeat) StenoRepeat(allUp.value); new (stenoRepeat) StenoRepeat(allUp.value);

View file

@ -1,9 +1,28 @@
#include "console.h" #include "console.h"
// ConsoleWriter::Write is the main output path for Javelin's console system. extern "C" {
// For Phase 1, stub it out. Phase 4 will route to USB CDC or ZMK logging. #include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
}
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
// Route Javelin console output to ZMK logging system.
// In Phase 5, this can be upgraded to USB CDC ACM for interactive console.
static char line_buf[256];
static size_t line_pos = 0;
void ConsoleWriter::Write(const char *data, size_t length) { void ConsoleWriter::Write(const char *data, size_t length) {
(void)data; for (size_t i = 0; i < length; ++i) {
(void)length; if (data[i] == '\n' || line_pos >= sizeof(line_buf) - 1) {
line_buf[line_pos] = '\0';
if (line_pos > 0) {
LOG_INF("[steno] %s", line_buf);
}
line_pos = 0;
} else {
line_buf[line_pos++] = data[i];
}
}
} }