/* * Copyright (c) 2002-2023 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Keyboard input processing for AG_Editable(3). */ #include #include #include #include #include #include #include #include #include #include static void RemoveSelection(AG_Editable *, Uint); /* Insert a new character at current cursor position. */ static int Insert(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { AG_EditableRevision *rev; AG_Char ins[3]; AG_Size len; int i, nIns, pos; if (keysym == 0) return (0); #ifdef __APPLE__ if ((keymod & AG_KEYMOD_LMETA) || (keymod & AG_KEYMOD_RMETA)) return (0); #endif #ifdef AG_UNICODE if (agLatinInput && !(ed->flags & AG_EDITABLE_NO_ALT_LATIN1)) { for (i = 0; ; i++) { const struct ag_key_mapping *km = &agKeymapLatin1[i]; if (keysym == km->key) { if (((keymod & AG_KEYMOD_ALT) && (keymod & AG_KEYMOD_SHIFT) && (km->modmask == (AG_KEYMOD_ALT|AG_KEYMOD_SHIFT)))) { ch = km->ch; break; } else if (keymod & AG_KEYMOD_ALT && km->modmask == AG_KEYMOD_ALT) { ch = km->ch; break; } } else if (km->key == AG_KEY_LAST) { break; } } } #endif /* AG_UNICODE */ if (ch == 0) { return (0); } if (ch == '\r') { ch = '\n'; } if (strcmp(ed->encoding, "US-ASCII") == 0 && (ch & ~0x7f) != 0) return (0); #ifdef AG_UNICODE if (agLatinComposition) { if ((nIns = AG_KeyComposeLatin(ed, ch, ins)) == 0) return (0); } else #endif { ins[0] = ch; nIns = 1; } ins[nIns] = '\0'; if (ed->selEnd > ed->selStart) { AG_EditableDelete(ed, buf); } if (AG_EditableGrowBuffer(ed, buf, ins, (AG_Size)nIns) == -1) { Verbose("Insert Failed: %s\n", AG_GetError()); return (0); } rev = AG_EditableBeginRevision(ed, buf); rev->posStart = ed->pos; rev->posEnd = ed->pos + nIns; rev->nCharsAdded = nIns; pos = ed->pos; len = buf->len; if (pos == len) { /* Append */ for (i = 0; i < nIns; i++) buf->s[len+i] = ins[i]; } else { /* Insert */ memmove(&buf->s[pos+nIns], &buf->s[pos], (len-pos)*sizeof(AG_Char)); for (i = 0; i < nIns; i++) buf->s[pos+i] = ins[i]; } buf->len += nIns; buf->s[buf->len] = '\0'; ed->pos += nIns; AG_EditableCommitRevision(ed, buf); #ifdef AG_UNICODE if (!(ed->flags & AG_EDITABLE_MULTILINE)) { /* Optimize case */ int wIns; AG_TextSizeInternal(ins, &wIns, NULL); ed->xScrollPx += wIns; } else #endif { ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; } ed->flags |= AG_EDITABLE_BLINK_ON; return (1); } /* Delete the character at cursor, or the active selection. */ static int Delete(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { AG_EditableRevision *rev; AG_Char *c; AG_Size len; #ifdef AG_UNICODE int wDel; #endif if ((len = buf->len) == 0) return (0); if (ed->selEnd > ed->selStart) { /* Active selection? */ AG_EditableDelete(ed, buf); return (1); } if (keysym == AG_KEY_BACKSPACE && ed->pos == 0) return (0); rev = AG_EditableBeginRevision(ed, buf); rev->nCharsRemoved = 1; rev->s = Malloc(2*sizeof(AG_Char)); if (keysym == AG_KEY_BACKSPACE) { rev->posStart = ed->pos - 1; rev->posEnd = ed->pos; rev->s[0] = buf->s[ed->pos - 1]; rev->s[1] = '\0'; } else { rev->posStart = ed->pos; rev->posEnd = ed->pos + 1; rev->s[0] = buf->s[ed->pos]; rev->s[1] = '\0'; } if (ed->pos == len) { ed->pos--; buf->s[--len] = '\0'; buf->len--; if (ed->flags & AG_EDITABLE_MULTILINE) { ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; } else { #ifdef AG_UNICODE if (len > 0) { AG_TextSizeInternal(&buf->s[len-1], &wDel, NULL); if (ed->x > 0) { ed->x -= wDel; } } #else /* TODO */ #endif } goto commit; } if (keysym == AG_KEY_BACKSPACE && ed->pos > 0) ed->pos--; if (ed->flags & AG_EDITABLE_MULTILINE) { ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; } else { #ifdef AG_UNICODE AG_Char cDel[2]; cDel[0] = buf->s[ed->pos]; cDel[1] = '\0'; AG_TextSizeInternal(cDel, &wDel, NULL); if (ed->x > 0) { ed->x -= wDel; } #else /* TODO */ #endif } for (c = &buf->s[ed->pos]; c < &buf->s[len+1]; c++) { *c = c[1]; if (*c == '\0') break; } buf->len--; commit: AG_EditableCommitRevision(ed, buf); return (1); } /* Copy the selection to clipboard. */ static int Copy(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { AG_EditableCopy(ed, buf, &agEditableClipbrd, 0); return (0); } /* Copy selection to clipboard and subsequently delete it. */ static int Cut(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { return AG_EditableCut(ed, buf, &agEditableClipbrd, 0); } /* Paste clipboard contents to current cursor position. */ static int Paste(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { return AG_EditablePaste(ed, buf, &agEditableClipbrd, 0); } /* * Kill the current selection or the remainder of the current line. * The contents are copied to a kill ring independent of the clipboard. */ static int Kill(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { if (ed->selEnd == ed->selStart) { /* Kill up to end of line */ const AG_Char *c; ed->selStart = ed->pos; ed->selEnd = ed->pos; for (c = &buf->s[ed->pos]; c < &buf->s[buf->len]; c++) { if (*c == '\n') { break; } ed->selEnd++; } } if (ed->selEnd > ed->selStart) { AG_EditableValidateSelection(ed, buf); AG_EditableCopyChunk(ed, &agEditableKillring, &buf->s[ed->selStart], (ed->selEnd - ed->selStart)); AG_EditableDelete(ed, buf); } return (1); } #if 0 /* * Paste the contents of a previous Kill at the current cursor position. * The contents are copied from a kill ring independent of the clipboard. */ static int Yank(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { return AG_EditablePaste(ed, buf, &agEditableKillring, 1); } #endif /* Undo last compound action */ static int Undo(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { AG_EditableUndo(ed, buf); return (1); } /* Redo last undone action */ static int Redo(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { AG_EditableRedo(ed, buf); return (1); } /* Seek one word backwards. */ static int WordBack(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { int newPos = ed->pos, i; AG_Char *c; if (newPos == 0) { return (0); } for (c = &buf->s[newPos]; newPos >= 0; c--, newPos--) { if (AG_CharIsSpaceOrLF(*c)) { while (AG_CharIsSpaceOrLF(*c) && newPos >= 0) { c--; newPos--; } while (!AG_CharIsSpaceOrLF(*c) && newPos >= 0) { c--; newPos--; } c++; newPos++; break; } } for (c = &buf->s[newPos], i = 0; c >= &buf->s[0] && (i <= AG_TEXT_ANSI_SEQ_MAX); c--, i++) { if (c[0] == 0x1b && c[1] >= 0x40 && c[1] <= 0x5f && c[2] != '\0') { AG_TextANSI ansi; const AG_TextState *ts = AG_TEXT_STATE_CUR(); if (AG_TextParseANSI(ts, &ansi, &c[1]) == 0 && i <= ansi.len) { newPos += (ansi.len + 1); } break; } } ed->pos = newPos; RemoveSelection(ed, keymod); ed->flags |= AG_EDITABLE_MARKPREF; ed->flags |= AG_EDITABLE_BLINK_ON; ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; AG_Redraw(ed); return (0); } /* Seek one word forward. */ static int WordForw(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { AG_Char *c; int newPos = ed->pos; if (newPos == buf->len) { return (0); } if (AG_CharIsSpaceOrLF(buf->s[newPos])) { for (c = &buf->s[newPos]; *c != '\0' && newPos <= buf->len; c++, newPos++) { if (AG_CharIsSpaceOrLF(*c)) { while (AG_CharIsSpaceOrLF(*c) && newPos <= buf->len) { c++; newPos++; } break; } } } else { for (c = &buf->s[newPos]; *c != '\0' && newPos <= buf->len; c++, newPos++) { if (AG_CharIsSpaceOrLF(*c)) { while (AG_CharIsSpaceOrLF(*c) && newPos <= buf->len) { c++; newPos++; } break; } } } if (c[0] == 0x1b && c[1] >= 0x40 && c[1] <= 0x5f && c[2] != '\0') { AG_TextANSI ansi; const AG_TextState *ts = AG_TEXT_STATE_CUR(); if (AG_TextParseANSI(ts, &ansi, &c[1]) == 0) newPos += (ansi.len + 1); } ed->pos = newPos; RemoveSelection(ed, keymod); ed->flags |= AG_EDITABLE_MARKPREF; ed->flags |= AG_EDITABLE_BLINK_ON; ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; AG_Redraw(ed); return (0); } /* Select all. */ static int SelectAll(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { AG_EditableSelectAll(ed, buf); return (0); } /* Move cursor to beginning of line. */ static int CursorHome(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { int newPos = ed->pos; AG_Char *c; if (ed->flags & AG_EDITABLE_MULTILINE) { if (newPos == 0) { return (0); } for (c = &buf->s[newPos - 1]; c >= &buf->s[0] && newPos >= 0; c--, newPos--) { if (*c == '\n') { c++; break; } } } else { newPos = 0; c = &buf->s[0]; } if (c[0] == 0x1b && c[1] >= 0x40 && c[1] <= 0x5f && c[2] != '\0') { AG_TextANSI ansi; const AG_TextState *ts = AG_TEXT_STATE_CUR(); if (AG_TextParseANSI(ts, &ansi, &c[1]) == 0) newPos += (ansi.len + 1); } ed->pos = newPos; RemoveSelection(ed, keymod); ed->x = 0; ed->flags |= AG_EDITABLE_MARKPREF; AG_Redraw(ed); return (0); } /* Move cursor to end of line. */ static int CursorEnd(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { int newPos = ed->pos; AG_Char *c; if (ed->flags & AG_EDITABLE_MULTILINE) { if (newPos == buf->len || buf->s[newPos] == '\n') { return (0); } for (c = &buf->s[newPos + 1]; c <= &buf->s[buf->len] && newPos <= buf->len; c++, newPos++) { if (*c == '\n') { newPos++; break; } } if (newPos > buf->len) newPos = buf->len; } else { newPos = buf->len; } ed->pos = newPos; RemoveSelection(ed, keymod); ed->flags |= AG_EDITABLE_MARKPREF; ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; AG_Redraw(ed); return (0); } /* Move cursor left by one character. */ static int CursorLeft(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { const AG_TextState *ts = AG_TEXT_STATE_CUR(); const AG_Char *c; AG_TextANSI ansi; int i; if ((ed->pos - 1) < 0) { goto out; } ed->pos--; for (c = &buf->s[ed->pos], i = 0; c >= &buf->s[0] && (i <= AG_TEXT_ANSI_SEQ_MAX); c--, i++) { if (c[0] == 0x1b && c[1] >= 0x40 && c[1] <= 0x5f && c[2] != '\0') { if (AG_TextParseANSI(ts, &ansi, &c[1]) == 0 && i <= ansi.len) { ed->pos -= (ansi.len + 1); } break; } } if (ed->flags & AG_EDITABLE_SHIFT_SELECT) { RemoveSelection(ed, keymod); } else { if (ed->selEnd > ed->selStart) { ed->pos = ed->selStart; c = &buf->s[ed->pos]; if (c[0] == 0x1b && c[1] >= 0x40 && c[1] <= 0x5f && c[2] != '\0' && AG_TextParseANSI(ts, &ansi, &c[1]) == 0) { ed->pos += ansi.len+1; } if ((ed->flags & AG_EDITABLE_SHIFT_SELECT) == 0) RemoveSelection(ed, keymod); } } out: ed->flags |= AG_EDITABLE_MARKPREF; ed->flags |= AG_EDITABLE_BLINK_ON; ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; AG_Redraw(ed); return (0); } /* Move the cursor right by one character. */ static int CursorRight(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { AG_Char *c; if (ed->pos >= buf->len) { goto out; } ed->pos++; c = &buf->s[ed->pos]; if (c[0] == 0x1b && c[1] >= 0x40 && c[1] <= 0x5f && c[2] != '\0') { AG_TextANSI ansi; const AG_TextState *ts = AG_TEXT_STATE_CUR(); if (AG_TextParseANSI(ts, &ansi, &c[1]) == 0) ed->pos += (ansi.len + 1); } if (ed->flags & AG_EDITABLE_SHIFT_SELECT) { RemoveSelection(ed, keymod); } else { if (ed->selEnd > ed->selStart) { ed->pos = ed->selEnd; RemoveSelection(ed, keymod); } } out: ed->flags |= AG_EDITABLE_MARKPREF; ed->flags |= AG_EDITABLE_BLINK_ON; ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; AG_Redraw(ed); return (0); } /* Move cursor up in a multi-line string. */ static int CursorUp(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { const AG_Char *c; if (!(ed->flags & AG_EDITABLE_MULTILINE)) return (0); if (AG_EditableMapPosition(ed, buf, ed->xCursPref, (ed->yCurs - ed->y - 1)*ed->lineSkip + 1, &ed->pos) == 0) RemoveSelection(ed, keymod); c = &buf->s[ed->pos]; if (c[0] == 0x1b && c[1] >= 0x40 && c[1] <= 0x5f && c[2] != '\0') { AG_TextANSI ansi; const AG_TextState *ts = AG_TEXT_STATE_CUR(); if (AG_TextParseANSI(ts, &ansi, &c[1]) == 0) ed->pos += (ansi.len + 1); } ed->flags |= AG_EDITABLE_BLINK_ON; ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; AG_Redraw(ed); return (0); } /* Move cursor down in a multi-line string. */ static int CursorDown(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { AG_Char *c; if (!(ed->flags & AG_EDITABLE_MULTILINE)) { if (ed->complete && /* Autocomplete */ ed->complete->winName[0] != '\0') { AG_Window *win; AG_Tlist *tl; if ((win = AG_WindowFind(ed->complete->winName)) != NULL && (tl = AG_ObjectFindChild(win, "tlist0")) != NULL) AG_TlistSelectIdx(tl, 0); } return (0); } if (AG_EditableMapPosition(ed, buf, ed->xCursPref, (ed->yCurs - ed->y + 1)*ed->lineSkip + 1, &ed->pos) == 0) RemoveSelection(ed, keymod); c = &buf->s[ed->pos]; if (c[0] == 0x1b && c[1] >= 0x40 && c[1] <= 0x5f && c[2] != '\0') { AG_TextANSI ansi; const AG_TextState *ts = AG_TEXT_STATE_CUR(); if (AG_TextParseANSI(ts, &ansi, &c[1]) == 0) ed->pos += (ansi.len + 1); } ed->flags |= AG_EDITABLE_BLINK_ON; ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; AG_Redraw(ed); return (0); } /* Remove the active selection. */ static void RemoveSelection(AG_Editable *ed, Uint keymod) { if (keymod & AG_KEYMOD_SHIFT) { if (ed->posKbdSel < ed->pos) { ed->selStart = ed->posKbdSel; ed->selEnd = ed->pos; } else if (ed->posKbdSel > ed->pos) { ed->selStart = ed->pos; ed->selEnd = ed->posKbdSel; } else { ed->selStart = 0; ed->selEnd = 0; } } else { ed->selStart = 0; ed->selEnd = 0; } } /* Move cursor one page up in a multi-line string. */ static int PageUp(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { if (!(ed->flags & AG_EDITABLE_MULTILINE)) return (0); AG_EditableMoveCursor(ed, buf, ed->xCurs, (ed->yCurs - ed->y - ed->yVis)*ed->lineSkip + 1); RemoveSelection(ed, keymod); ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; AG_Redraw(ed); return (0); } /* Move cursor one page down in a multi-line string. */ static int PageDown(AG_Editable *_Nonnull ed, AG_EditableBuffer *_Nonnull buf, AG_KeySym keysym, Uint keymod, AG_Char ch) { if (!(ed->flags & AG_EDITABLE_MULTILINE)) return (0); AG_EditableMoveCursor(ed, buf, ed->xCurs, (ed->yCurs - ed->y + ed->yVis)*ed->lineSkip + 1); RemoveSelection(ed, keymod); ed->xScrollTo = &ed->xCurs; ed->yScrollTo = &ed->yCurs; AG_Redraw(ed); return (0); } /* * Map AG_KeySym(3) and AG_KeyMod(3) values to AG_Editable functions. * Variants with modifier keys must appear first in the list. The * modifiers string is passed to AG_CompareKeyMods(3). * * Available flags: * "w" = Require a writeable buffer (enabled widget state & !READONLY). * "k" = Require [K]ill and [Y]ank to be enabled (!NO_KILL_YANK). */ const struct ag_keycode agKeymap[] = { #ifdef __APPLE__ { AG_KEY_LEFT, AG_KEYMOD_CTRL|AG_KEYMOD_META, CursorHome, "" }, { AG_KEY_RIGHT, AG_KEYMOD_CTRL|AG_KEYMOD_META, CursorEnd, "" }, { AG_KEY_LEFT, AG_KEYMOD_ALT, WordBack, "" }, { AG_KEY_RIGHT, AG_KEYMOD_ALT, WordForw, "" }, { AG_KEY_A, AG_KEYMOD_META, SelectAll, "" }, { AG_KEY_C, AG_KEYMOD_META, Copy, "" }, { AG_KEY_X, AG_KEYMOD_META, Cut, "w" }, { AG_KEY_V, AG_KEYMOD_META, Paste, "w" }, { AG_KEY_K, AG_KEYMOD_META, Kill, "kw" }, /* { AG_KEY_Y, AG_KEYMOD_META, Yank, "kw" }, */ { AG_KEY_Z, AG_KEYMOD_SHIFT|AG_KEYMOD_META, Redo, "w" }, { AG_KEY_Z, AG_KEYMOD_META, Undo, "w" }, { AG_KEY_Y, AG_KEYMOD_META, Redo, "w" }, #else /* __APPLE__ */ { AG_KEY_LEFT, AG_KEYMOD_CTRL, WordBack, "" }, { AG_KEY_LEFT, AG_KEYMOD_ALT, WordBack, "" }, { AG_KEY_RIGHT, AG_KEYMOD_CTRL, WordForw, "" }, { AG_KEY_RIGHT, AG_KEYMOD_ALT, WordForw, "" }, { AG_KEY_A, AG_KEYMOD_CTRL, SelectAll, "" }, { AG_KEY_C, AG_KEYMOD_CTRL, Copy, "" }, { AG_KEY_X, AG_KEYMOD_CTRL, Cut, "w" }, { AG_KEY_V, AG_KEYMOD_CTRL, Paste, "w" }, { AG_KEY_K, AG_KEYMOD_CTRL, Kill, "kw" }, /* { AG_KEY_Y, AG_KEYMOD_CTRL, Yank, "kw" }, */ { AG_KEY_Z, AG_KEYMOD_CTRL, Undo, "w" }, { AG_KEY_Y, AG_KEYMOD_CTRL, Redo, "w" }, #endif /* !__APPLE__ */ { AG_KEY_HOME, 0, CursorHome, "" }, { AG_KEY_END, 0, CursorEnd, "" }, { AG_KEY_LEFT, 0, CursorLeft, "" }, { AG_KEY_RIGHT, 0, CursorRight, "" }, { AG_KEY_UP, 0, CursorUp, "" }, { AG_KEY_DOWN, 0, CursorDown, "" }, { AG_KEY_PAGEUP, 0, PageUp, "" }, { AG_KEY_PAGEDOWN, 0, PageDown, "" }, { AG_KEY_BACKSPACE, 0, Delete, "w" }, { AG_KEY_DELETE, 0, Delete, "w" }, { AG_KEY_LAST, 0, Insert, "w" }, };