blob: 040051a562dddff0d04aad2c8258573849a37142 [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include "log.h"
#include "strfmt.h"
#include "utf8.h"
#include "cmd.h"
#include "brl_cmds.h"
#include "ktb.h"
#include "ktb_list.h"
#include "ktb_cmds.h"
struct CommandGroupHookDataStruct {
ListGenerationData *const lgd;
const KeyContext *const ctx;
int ok;
};
static inline void
listCommandSubgroup (
int (*list) (ListGenerationData *lgd, const KeyContext *ctx),
CommandGroupHookData *cgh
) {
cgh->ok = list(cgh->lgd, cgh->ctx);
}
static int
handleCommandGroupHook (CommandGroupHook *handler, CommandGroupHookData *cgh) {
if (!handler) return 1;
cgh->ok = 0;
handler(cgh);
return cgh->ok;
}
static void *
getMethodData (ListGenerationData *lgd) {
return lgd->list.internal? lgd: lgd->list.data;
}
static int
writeHeader (ListGenerationData *lgd, const wchar_t *text, int level) {
return lgd->list.methods->writeHeader(text, level, getMethodData(lgd));
}
static int
writeLine (ListGenerationData *lgd, const wchar_t *line) {
return lgd->list.writeLine(line, lgd->list.data);
}
static int
writeBlankLine (ListGenerationData *lgd) {
return writeLine(lgd, WS_C(""));
}
static int
addCharacters (ListGenerationData *lgd, const wchar_t *characters, size_t count) {
size_t newLength = lgd->line.length + count;
if (newLength > lgd->line.size) {
size_t newSize = (newLength | 0X3F) + 1;
wchar_t *newCharacters = realloc(lgd->line.characters, ARRAY_SIZE(newCharacters, newSize));
if (!newCharacters) {
logSystemError("realloc");
return 0;
}
lgd->line.characters = newCharacters;
lgd->line.size = newSize;
}
wmemcpy(&lgd->line.characters[lgd->line.length], characters, count);
lgd->line.length = newLength;
return 1;
}
static int
putCharacters (ListGenerationData *lgd, const wchar_t *characters, size_t count) {
if (lgd->line.length == 0) {
if (lgd->list.elementLevel > 0) {
const unsigned int indent = 2;
const unsigned int count = indent * lgd->list.elementLevel;
wchar_t characters[count];
wmemset(characters, WC_C(' '), count);
characters[count - indent] = lgd->list.elementBullet;
lgd->list.elementBullet = WC_C(' ');
if (!addCharacters(lgd, characters, count)) return 0;
}
}
return addCharacters(lgd, characters, count);
}
static int
putCharacter (ListGenerationData *lgd, wchar_t character) {
return putCharacters(lgd, &character, 1);
}
static int
putCharacterString (ListGenerationData *lgd, const wchar_t *string) {
return putCharacters(lgd, string, wcslen(string));
}
static int
putUtf8String (ListGenerationData *lgd, const char *string) {
size_t size = strlen(string) + 1;
wchar_t characters[size];
wchar_t *character = characters;
convertUtf8ToWchars(&string, &character, size);
return putCharacters(lgd, characters, character-characters);
}
static void
clearLine (ListGenerationData *lgd) {
lgd->line.length = 0;
}
static void
trimLine (ListGenerationData *lgd) {
while (lgd->line.length > 0) {
size_t last = lgd->line.length - 1;
if (lgd->line.characters[last] != WC_C(' ')) break;
lgd->line.length = last;
}
}
static int
finishLine (ListGenerationData *lgd) {
trimLine(lgd);
if (!putCharacter(lgd, 0)) return 0;
return 1;
}
static int
endLine (ListGenerationData *lgd) {
if (lgd->topicHeader) {
if (!writeHeader(lgd, lgd->topicHeader, 1)) return 0;
lgd->topicHeader = NULL;
}
if (lgd->listHeader) {
const char *string = lgd->listHeader;
lgd->listHeader = NULL;
size_t size = strlen(string) + 1;
wchar_t characters[size];
wchar_t *character = characters;
convertUtf8ToWchars(&string, &character, size);
if (!writeHeader(lgd, characters, 2)) return 0;
}
if (!finishLine(lgd)) return 0;
if (!writeLine(lgd, lgd->line.characters)) return 0;
clearLine(lgd);
return 1;
}
static int
beginList (ListGenerationData *lgd, const char *header) {
lgd->listHeader = header;
return 1;
}
static int
endList (ListGenerationData *lgd) {
return lgd->list.methods->endList(getMethodData(lgd));
}
static int
beginElement (ListGenerationData *lgd, unsigned int level) {
return lgd->list.methods->beginElement(level, getMethodData(lgd));
}
static int
searchKeyNameEntry (const void *target, const void *element) {
const KeyValue *value = target;
const KeyNameEntry *const *kne = element;
return compareKeyValues(value, &(*kne)->value);
}
const KeyNameEntry *
findKeyNameEntry (KeyTable *table, const KeyValue *value) {
const KeyNameEntry *const *array = table->keyNames.table;
unsigned int count = table->keyNames.count;
const KeyNameEntry *const *kne = bsearch(value, array, count, sizeof(*array), searchKeyNameEntry);
if (!kne) return NULL;
while (kne > array) {
if (compareKeyValues(value, &(*--kne)->value) != 0) {
kne += 1;
break;
}
}
return *kne;
}
STR_BEGIN_FORMATTER(formatKeyName, KeyTable *table, const KeyValue *value)
const KeyNameEntry *kne = findKeyNameEntry(table, value);
if (kne) {
STR_PRINTF("%s", kne->name);
} else if (value->number != KTB_KEY_ANY) {
const KeyValue anyKey = {
.group = value->group,
.number = KTB_KEY_ANY
};
if ((kne = findKeyNameEntry(table, &anyKey))) {
STR_PRINTF("%s.%u", kne->name, value->number+1);
}
}
if (STR_LENGTH == 0) STR_PRINTF("?");
STR_END_FORMATTER
static int
putKeyName (ListGenerationData *lgd, const KeyValue *value) {
char string[0X100];
formatKeyName(string, sizeof(string), lgd->keyTable, value);
return putUtf8String(lgd, string);
}
STR_BEGIN_FORMATTER(formatKeyCombination, KeyTable *table, const KeyCombination *combination)
char keyDelimiter = 0;
unsigned char dotCount = 0;
const char *dotPrefix = "dot";
const size_t dotPrefixLength = strlen(dotPrefix);
const size_t dotNameLength = dotPrefixLength + 1;
for (unsigned char index=0; index<combination->modifierCount; index+=1) {
char keyName[0X100];
formatKeyName(keyName, sizeof(keyName), table,
&combination->modifierKeys[combination->modifierPositions[index]]);
if (strlen(keyName) == dotNameLength) {
if (strncasecmp(keyName, dotPrefix, dotPrefixLength) == 0) {
char dotNumber = keyName[dotPrefixLength];
if ((dotNumber >= '1') && (dotNumber <= '8')) {
if (++dotCount == 1) goto FIRST_DOT;
if (dotCount == 2) {
char firstDot = *STR_POP();
STR_PRINTF("s%c", firstDot);
}
STR_PRINTF("%c", dotNumber);
continue;
}
}
}
dotCount = 0;
FIRST_DOT:
if (keyDelimiter) {
STR_PRINTF("%c", keyDelimiter);
} else {
keyDelimiter = '+';
}
STR_PRINTF("%s", keyName);
}
if (combination->flags & KCF_IMMEDIATE_KEY) {
if (keyDelimiter) STR_PRINTF("%c", keyDelimiter);
STR_FORMAT(formatKeyName, table, &combination->immediateKey);
}
STR_END_FORMATTER
static int
putKeyCombination (ListGenerationData *lgd, const KeyCombination *combination) {
char string[0X100];
formatKeyCombination(string, sizeof(string), lgd->keyTable, combination);
return putUtf8String(lgd, string);
}
static int
putCommandDescription (ListGenerationData *lgd, const BoundCommand *cmd, int details) {
char description[0X60];
describeCommand(description, sizeof(description), cmd->value,
(details? (CDO_IncludeOperand | CDO_DefaultOperand): 0));
return putUtf8String(lgd, description);
}
static int
putKeyboardFunction (ListGenerationData *lgd, const KeyboardFunction *kbf) {
if (!beginElement(lgd, 1)) return 0;
if (!putCharacterString(lgd, WS_C("braille keyboard "))) return 0;
if (!putUtf8String(lgd, kbf->name)) return 0;
if (!putCharacterString(lgd, WS_C(": "))) return 0;
return 1;
}
static int
listKeyboardFunctions (ListGenerationData *lgd, const KeyContext *ctx) {
if (ctx->mappedKeys.count > 0) {
for (unsigned int index=0; index<ctx->mappedKeys.count; index+=1) {
const MappedKeyEntry *map = &ctx->mappedKeys.table[index];
if (!(map->flags & MKF_HIDDEN)) {
const KeyboardFunction *kbf = map->keyboardFunction;
if (!putKeyboardFunction(lgd, kbf)) return 0;
if (!putKeyName(lgd, &map->keyValue)) return 0;
if (!endLine(lgd)) return 0;
}
}
{
const KeyboardFunction *kbf = keyboardFunctionTable;
const KeyboardFunction *end = kbf + keyboardFunctionCount;
while (kbf < end) {
if (ctx->mappedKeys.superimpose & kbf->bit) {
if (!putKeyboardFunction(lgd, kbf)) return 0;
if (!putCharacterString(lgd, WS_C("superimposed"))) return 0;
if (!endLine(lgd)) return 0;
}
kbf += 1;
}
}
}
return 1;
}
void
commandGroupHook_keyboardFunctions (CommandGroupHookData *cgh) {
listCommandSubgroup(listKeyboardFunctions, cgh);
}
static int
listHotkeyEvent (ListGenerationData *lgd, const KeyValue *keyValue, const char *event, const BoundCommand *cmd) {
if (cmd->value != BRL_CMD_NOOP) {
if (!beginElement(lgd, 1)) return 0;
if ((cmd->value & BRL_MSK_BLK) == BRL_CMD_BLK(CONTEXT)) {
const KeyContext *ctx = getKeyContext(lgd->keyTable, (KTB_CTX_DEFAULT + (cmd->value & BRL_MSK_ARG)));
if (!ctx) return 0;
if (!putUtf8String(lgd, "switch to ")) return 0;
if (!putCharacterString(lgd, ctx->title)) return 0;
} else {
if (!putCommandDescription(lgd, cmd, (keyValue->number != KTB_KEY_ANY))) return 0;
}
if (!putCharacterString(lgd, WS_C(": "))) return 0;
if (!putUtf8String(lgd, event)) return 0;
if (!putCharacter(lgd, WC_C(' '))) return 0;
if (!putKeyName(lgd, keyValue)) return 0;
if (!endLine(lgd)) return 0;
}
return 1;
}
static int
listHotkeys (ListGenerationData *lgd, const KeyContext *ctx) {
const HotkeyEntry *hotkey = ctx->hotkeys.table;
unsigned int count = ctx->hotkeys.count;
while (count) {
if (!(hotkey->flags & HKF_HIDDEN)) {
if (!listHotkeyEvent(lgd, &hotkey->keyValue, "press", &hotkey->pressCommand)) return 0;
if (!listHotkeyEvent(lgd, &hotkey->keyValue, "release", &hotkey->releaseCommand)) return 0;
}
hotkey += 1, count -= 1;
}
return 1;
}
void
commandGroupHook_hotkeys (CommandGroupHookData *cgh) {
listCommandSubgroup(listHotkeys, cgh);
}
static int
saveBindingLine (
ListGenerationData *lgd, size_t keysOffset,
const BoundCommand *command, const KeyBinding *binding
) {
if (lgd->binding.count == lgd->binding.size) {
size_t newSize = lgd->binding.size? (lgd->binding.size << 1): 0X10;
BindingLine **newLines = realloc(lgd->binding.lines, ARRAY_SIZE(newLines, newSize));
if (!newLines) {
logMallocError();
return 0;
}
lgd->binding.lines = newLines;
while (lgd->binding.size < newSize) {
lgd->binding.lines[lgd->binding.size++] = NULL;
}
}
{
BindingLine *line;
size_t size = sizeof(*line) + (sizeof(line->text[0]) * lgd->line.length);
if (!(line = malloc(size))) {
logMallocError();
return 0;
}
line->command = command;
line->keyCombination = &binding->keyCombination;
line->keysOffset = keysOffset;
wmemcpy(line->text, lgd->line.characters, (line->length = lgd->line.length));
lgd->binding.lines[lgd->binding.count++] = line;
}
clearLine(lgd);
return 1;
}
static void
removeBindingLine (ListGenerationData *lgd, int index) {
{
BindingLine *bl = lgd->binding.lines[index];
free(bl);
}
if (--lgd->binding.count > index) {
memmove(&lgd->binding.lines[index], &lgd->binding.lines[index+1],
((lgd->binding.count - index) * sizeof(*lgd->binding.lines)));
}
}
static void
removeBindingLines (ListGenerationData *lgd) {
size_t *count = &lgd->binding.count;
while (*count > 0) removeBindingLine(lgd, (*count - 1));
}
static int
sortBindingLines (const void *element1, const void *element2) {
const BindingLine *const *line1 = element1;
const BindingLine *const *line2 = element2;
int command1 = (*line1)->command->value;
int command2 = (*line2)->command->value;
int cmd1 = command1 & BRL_MSK_CMD;
int cmd2 = command2 & BRL_MSK_CMD;
if (cmd1 < cmd2) return -1;
if (cmd1 > cmd2) return 1;
if (command1 < command2) return -1;
if (command1 > command2) return 1;
const KeyCombination *combination1 = (*line1)->keyCombination;
const KeyCombination *combination2 = (*line2)->keyCombination;
if (combination1->anyKeyCount < combination2->anyKeyCount) return -1;
if (combination1->anyKeyCount > combination2->anyKeyCount) return 1;
if (combination1 < combination2) return -1;
if (combination1 > combination2) return 1;
return 0;
}
static int
listBindingLine (ListGenerationData *lgd, int index, int *isSame) {
const BindingLine *bl = lgd->binding.lines[index];
int asList = *isSame;
if (*isSame) {
*isSame = 0;
} else {
if (!beginElement(lgd, 1)) return 0;
if (!putCharacters(lgd, bl->text, bl->keysOffset)) return 0;
}
{
int next = index + 1;
if (next < lgd->binding.count) {
const BindingLine *nl = lgd->binding.lines[next];
if (bl->command->value == nl->command->value) {
if (bl->keyCombination->anyKeyCount == nl->keyCombination->anyKeyCount) {
if (!asList) {
if (!endLine(lgd)) return 0;
}
asList = 1;
*isSame = 1;
}
}
}
}
if (asList) {
if (!beginElement(lgd, 2)) return 0;
}
if (!putCharacters(lgd, &bl->text[bl->keysOffset], (bl->length - bl->keysOffset))) return 0;
if (!endLine(lgd)) return 0;
removeBindingLine(lgd, index);
return 1;
}
static int
listBindingLines (ListGenerationData *lgd, const KeyContext *ctx) {
if (lgd->binding.count > 0) {
qsort(lgd->binding.lines, lgd->binding.count,
sizeof(*lgd->binding.lines), sortBindingLines);
{
const CommandGroupEntry *grp = commandGroupTable;
const CommandGroupEntry *grpEnd = grp + commandGroupCount;
while (grp < grpEnd) {
const CommandListEntry *cmd = grp->commands.table;
const CommandListEntry *cmdEnd = cmd + grp->commands.count;
CommandGroupHookData cgh = {
.lgd = lgd,
.ctx = ctx
};
if (!beginList(lgd, grp->name)) return 0;
if (!handleCommandGroupHook(grp->before, &cgh)) return 0;
while (cmd < cmdEnd) {
int first = 0;
int last = lgd->binding.count - 1;
while (first <= last) {
int current = (first + last) / 2;
const BindingLine *bl = lgd->binding.lines[current];
int command = bl->command->value & BRL_MSK_CMD;
if (command < cmd->code) {
first = current + 1;
} else {
last = current - 1;
}
}
int isSame = 0;
int current = cmd->code;
// Binding lines are removed as they're processed, so, even though
// first isn't being incremented, the count will be decremented.
while (first < lgd->binding.count) {
const BindingLine *bl = lgd->binding.lines[first];
int next = bl->command->value;
if ((next & BRL_MSK_CMD) != current) {
int blk = next & BRL_MSK_BLK;
if (!blk) break;
if (blk != (current & BRL_MSK_BLK)) break;
}
if (!listBindingLine(lgd, first, &isSame)) return 0;
}
cmd += 1;
}
if (!handleCommandGroupHook(grp->after, &cgh)) return 0;
if (!endList(lgd)) return 0;
grp += 1;
}
{
int isSame = 0;
if (!beginList(lgd, "Uncategorized Bindings")) return 0;
while (lgd->binding.count > 0) {
if (!listBindingLine(lgd, 0, &isSame)) return 0;
}
if (!endList(lgd)) return 0;
}
}
}
return 1;
}
static int listKeyBindings (ListGenerationData *lgd, const KeyContext *ctx, const wchar_t *keysPrefix);
static int
listKeyBinding (ListGenerationData *lgd, const KeyBinding *binding, int longPress, const wchar_t *keysPrefix) {
const BoundCommand *cmd = longPress? &binding->secondaryCommand: &binding->primaryCommand;
if (cmd->value == BRL_CMD_NOOP) return 1;
if (!putCommandDescription(lgd, cmd, !binding->keyCombination.anyKeyCount)) return 0;
if (!putCharacterString(lgd, WS_C(": "))) return 0;
size_t keysOffset = lgd->line.length;
if (keysPrefix) {
if (!putCharacterString(lgd, keysPrefix)) return 0;
if (!putCharacterString(lgd, WS_C(", "))) return 0;
}
if (longPress) {
if (!putCharacterString(lgd, WS_C("long "))) return 0;
}
if (!putKeyCombination(lgd, &binding->keyCombination)) return 0;
if ((cmd->value & BRL_MSK_BLK) == BRL_CMD_BLK(CONTEXT)) {
const KeyContext *ctx = getKeyContext(lgd->keyTable, (KTB_CTX_DEFAULT + (cmd->value & BRL_MSK_ARG)));
if (!ctx) return 0;
{
size_t length = lgd->line.length - keysOffset;
wchar_t keys[length + 1];
wmemcpy(keys, &lgd->line.characters[keysOffset], length);
keys[length] = 0;
clearLine(lgd);
if (isTemporaryKeyContext(lgd->keyTable, ctx)) {
if (!listKeyBindings(lgd, ctx, keys)) return 0;
} else {
if (!putCharacterString(lgd, WS_C("switch to "))) return 0;
if (!putCharacterString(lgd, ctx->title)) return 0;
if (!putCharacterString(lgd, WS_C(": "))) return 0;
keysOffset = lgd->line.length;
if (!putCharacterString(lgd, keys)) return 0;
if (!saveBindingLine(lgd, keysOffset, cmd, binding)) return 0;
}
}
} else {
if (!saveBindingLine(lgd, keysOffset, cmd, binding)) return 0;
}
return 1;
}
static int
listKeyBindings (ListGenerationData *lgd, const KeyContext *ctx, const wchar_t *keysPrefix) {
const KeyBinding *binding = ctx->keyBindings.table;
unsigned int count = ctx->keyBindings.count;
while (count) {
if (!(binding->flags & KBF_HIDDEN)) {
if (!listKeyBinding(lgd, binding, 0, keysPrefix)) return 0;
if (!listKeyBinding(lgd, binding, 1, keysPrefix)) return 0;
}
binding += 1, count -= 1;
}
return 1;
}
static int
listKeyContext (ListGenerationData *lgd, const KeyContext *ctx) {
lgd->topicHeader = ctx->title;
if (!listKeyBindings(lgd, ctx, NULL)) return 0;
if (!listBindingLines(lgd, ctx)) return 0;
return 1;
}
static int
listSpecialKeyContexts (ListGenerationData *lgd) {
static const unsigned char contexts[] = {
KTB_CTX_DEFAULT,
KTB_CTX_MENU
};
const unsigned char *context = contexts;
unsigned int count = ARRAY_COUNT(contexts);
while (count) {
const KeyContext *ctx = getKeyContext(lgd->keyTable, *context);
if (ctx) {
if (!listKeyContext(lgd, ctx)) return 0;
}
context += 1, count -= 1;
}
return 1;
}
static int
listPersistentKeyContexts (ListGenerationData *lgd) {
for (unsigned int context=KTB_CTX_DEFAULT+1; context<lgd->keyTable->keyContexts.count; context+=1) {
const KeyContext *ctx = getKeyContext(lgd->keyTable, context);
if (ctx && !isTemporaryKeyContext(lgd->keyTable, ctx)) {
if (!listKeyContext(lgd, ctx)) return 0;
}
}
return 1;
}
static int
listKeyTableTitle (ListGenerationData *lgd) {
if (!putUtf8String(lgd, gettext("Key Table"))) return 0;
if (lgd->keyTable->title) {
if (!putCharacterString(lgd, WS_C(": "))) return 0;
if (!putCharacterString(lgd, lgd->keyTable->title)) return 0;
if (!finishLine(lgd)) return 0;
if (!writeHeader(lgd, lgd->line.characters, 0)) return 0;
clearLine(lgd);
}
return 1;
}
static int
listKeyTableNotes (ListGenerationData *lgd) {
if (!beginList(lgd, "Notes")) return 0;
for (unsigned int noteIndex=0; noteIndex<lgd->keyTable->notes.count; noteIndex+=1) {
const wchar_t *line = lgd->keyTable->notes.table[noteIndex];
unsigned int level;
int prefixed;
if (*line == WC_C('*')) {
level = 0;
prefixed = 1;
} else if (*line == WC_C('+')) {
level = 2;
prefixed = 1;
} else {
level = 1;
prefixed = 0;
}
if (level > 0) {
if (!beginElement(lgd, level)) return 0;
}
if (prefixed) {
line += 1;
while (iswspace(*line)) line += 1;
}
if (!putCharacterString(lgd, line)) return 0;
if (!endLine(lgd)) return 0;
}
if (!endList(lgd)) return 0;
return 1;
}
static int
listCommandMacros (ListGenerationData *lgd) {
unsigned int count = lgd->keyTable->commandMacros.count;
if (count > 0) {
lgd->topicHeader = WS_C("Command Macros");
const CommandMacro *macro = lgd->keyTable->commandMacros.table;
const CommandMacro *endMacros = macro + count;
unsigned int number = 0;
while (macro < endMacros) {
{
char buffer[0X100];
snprintf(buffer, sizeof(buffer), "Command Macro #%u:", ++number);
if (!putUtf8String(lgd, buffer)) return 0;
}
{
const BoundCommand *command = macro->commands;
const BoundCommand *endCommands = command + macro->count;
while (command < endCommands) {
if (!putCharacter(lgd, WC_C(' '))) return 0;
if (!putUtf8String(lgd, command->entry->name)) return 0;
command += 1;
}
}
if (!endLine(lgd)) return 0;
macro += 1;
}
if (!endLine(lgd)) return 0;
}
return 1;
}
static int
listHostCommands (ListGenerationData *lgd) {
unsigned int count = lgd->keyTable->hostCommands.count;
if (count > 0) {
lgd->topicHeader = WS_C("Host Commands");
const HostCommand *hc = lgd->keyTable->hostCommands.table;
const HostCommand *end = hc + count;
unsigned int number = 0;
while (hc < end) {
{
char buffer[0X100];
snprintf(buffer, sizeof(buffer), "Host Command #%u:", ++number);
if (!putUtf8String(lgd, buffer)) return 0;
}
{
char **argument = hc->arguments;
while (*argument) {
if (!putCharacter(lgd, WC_C(' '))) return 0;
if (!putUtf8String(lgd, *argument)) return 0;
argument += 1;
}
}
if (!endLine(lgd)) return 0;
hc += 1;
}
if (!endLine(lgd)) return 0;
}
return 1;
}
static int
listKeyTableSections (ListGenerationData *lgd) {
typedef int Lister (ListGenerationData *lgd);
static Lister *const listerTable[] = {
listKeyTableTitle,
listKeyTableNotes,
listCommandMacros,
listHostCommands,
listSpecialKeyContexts,
listPersistentKeyContexts
};
Lister *const *lister = listerTable;
Lister *const *end = lister + ARRAY_COUNT(listerTable);
while (lister < end) {
if (!(*lister)(lgd)) return 0;
lister += 1;
}
return 1;
}
static int
internalWriteHeader (const wchar_t *text, unsigned int level, void *data) {
static const wchar_t characters[] = {WC_C('='), WC_C('-')};
ListGenerationData *lgd = data;
if (!writeLine(lgd, text)) return 0;
if (level < ARRAY_COUNT(characters)) {
size_t length = wcslen(text);
wchar_t underline[length + 1];
wmemset(underline, characters[level], length);
underline[length] = 0;
if (!writeLine(lgd, underline)) return 0;
if (!writeBlankLine(lgd)) return 0;
}
return 1;
}
static int
internalBeginElement (unsigned int level, void *data) {
ListGenerationData *lgd = data;
static const wchar_t bullets[] = {
WC_C('*'),
WC_C('+'),
WC_C('-')
};
lgd->list.elementLevel = level;
lgd->list.elementBullet = bullets[level - 1];
return 1;
}
static int
internalEndList (void *data) {
ListGenerationData *lgd = data;
if (lgd->list.elementLevel > 0) {
lgd->list.elementLevel = 0;
lgd->listHeader = NULL;
if (!writeBlankLine(lgd)) return 0;
}
return 1;
}
int
listKeyTable (KeyTable *table, const KeyTableListMethods *methods, KeyTableWriteLineMethod *writeLine, void *data) {
static const KeyTableListMethods internalMethods = {
.writeHeader = internalWriteHeader,
.beginElement = internalBeginElement,
.endList = internalEndList
};
ListGenerationData lgd = {
.keyTable = table,
.topicHeader = NULL,
.listHeader = NULL,
.line = {
.characters = NULL,
.size = 0,
.length = 0,
},
.list = {
.methods = methods? methods: &internalMethods,
.writeLine = writeLine,
.data = data,
.internal = !methods,
.elementLevel = 0
},
.binding = {
.lines = NULL,
.size = 0,
.count = 0
}
};
int result = listKeyTableSections(&lgd);
if (lgd.binding.lines) {
removeBindingLines(&lgd);
free(lgd.binding.lines);
}
if (lgd.line.characters) free(lgd.line.characters);
return result;
}
int
forEachKeyName (KEY_NAME_TABLES_REFERENCE keys, KeyNameEntryHandler *handleKeyNameEntry, void *data) {
const KeyNameEntry *const *knt = keys;
while (*knt) {
const KeyNameEntry *kne = *knt;
if (knt != keys) {
if (!handleKeyNameEntry(NULL, data)) {
return 0;
}
}
while (kne->name) {
if (!handleKeyNameEntry(kne, data)) return 0;
kne += 1;
}
knt += 1;
}
return 1;
}
typedef struct {
KeyTableWriteLineMethod *const writeLine;
void *const data;
} ListKeyNameData;
static int
listKeyName (const KeyNameEntry *kne, void *data) {
const ListKeyNameData *lkn = data;
const char *name = kne? kne->name: "";
size_t size = strlen(name) + 1;
wchar_t characters[size];
wchar_t *character = characters;
convertUtf8ToWchars(&name, &character, size);
return lkn->writeLine(characters, lkn->data);
}
int
listKeyNames (KEY_NAME_TABLES_REFERENCE keys, KeyTableWriteLineMethod *writeLine, void *data) {
ListKeyNameData lkn = {
.writeLine = writeLine,
.data = data
};
return forEachKeyName(keys, listKeyName, &lkn);
}