#include <Arduino.h>
#include "input.h"
Termcap termcap;
void Termcap::init() {
width=80; height=24; flags=0;
Serial.println("\e[6n"); // Cursorposition abfragen und Cursor nach links zurück
char answer[16] __attribute__((unused));
size_t alen=0;
for (auto tic=millis();millis()-tic<100;) {
auto c=Serial.read();
if (c>=0) {
if (alen<sizeof answer) answer[alen]=c;
alen++;
}
}
if (alen) fColor=true; // Wenn Antwort dann Terminal (putty) sonst dummer Arduino Serial Monitor
}
void Termcap::_setattr(unsigned n,...) const{
if (!fColor) return; // nichts ausspucken
Serial.print('\e');
Serial.print('[');
va_list va;
va_start(va,n); // <n> durch Semikolon getrennte nichtnegative Zahlen ausgeben
while (n) {
Serial.print(va_arg(va,unsigned)); // Integer ausgeben (kein printf bemühen)
if (!--n) break;
Serial.print(';');
}
va_end(va);
Serial.print('m'); // schließlich das "m" ausgeben
}
// String mit max. <bufsize-1> Zeichen auf serieller Konsole (Arduino, putty) eingeben
// Mit Kursorsteuerfunktion, ohne History, ohne Startmarkierung
// In einem Dialog müsste man mit ESC, TAB und evtl. vertikalen Kursortasten rauskommen
// Die Arduino-GUI 2.0.1 kommt NUR mit Startmarkierung zurecht und untestützt keine Farben.
// (vergebliche Liebesmüh, Zeileneditorfunktion nur bei Control-Zeichen, Löschen nicht möglich)
int input(char*buf,int bufsize,KeyCb keycb, void*cbdat) {
auto backspace=[](int n=1) {if (n) do Serial.print('\b'); while (--n);};
auto ding=[]() {return Serial.print('\a'),false;}; // Simple Lambdafunktion
auto strcount=[](const char*s,const char*e) { // Zählt "ganze" UTF8-Zeichen
int i=0;
while (s!=e) if (byte(*s++&0xC0)!=0x80) i++; // Non-Trailbytes zählen
return i;
};
auto hilite=[]() {termcap.setattr(1,37,44);}; // fett schaltet Vordergrundfarbe hell, zumindest bei ANSI.SYS, für mehr Kontrast
auto unmark=[]() {termcap.setattr();};
int l, i; // i wird auf UTF8-Sequenzgrenzen gehalten
auto goLeft=[&]() {
if (!i) return ding();
backspace();
while (byte(buf[--i]&0xC0)==0x80 && i); // UTF8-Trailbytes übergehen
return true;
};
auto goRight=[&]() {
if (i==l) return ding();
Serial.print(buf[i++]);
while (byte(buf[i]&0xC0)==0x80 && i<l) Serial.print(buf[i++]); // UTF8-Trailbytes ausgeben
return true;
};
auto delChar=[&]() {
if (i==l) return ding(); // Cursor hinter letztem Zeichen geht nicht
int j=i;
while (++j!=l && byte(buf[j]&0xC0)==0x80);
memmove(buf+i,buf+j,l-j+1); // Zeichen bei Index löschen
l-=j-i; // String kürzen
Serial.print(buf+i); // String-Rest ausgeben (kann leer sein)
unmark();
Serial.print(' '); // Leerzeichen zum Zeichen löschen dahinter, uninvertiert
backspace(strcount(buf+i,buf+l)+1); // Kursor repositionieren
hilite();
return true;
};
auto clearBuf=[&]() { // String von vorn anfangen
backspace(strcount(buf,buf+i)); i=l;
unmark();
if (i) do Serial.print(' '); while(--i); // Dargestellte Zeichen löschen
backspace(l);
hilite();
buf[l=0]=0;
};
auto onNewStr=[&]() {
l=strlen(buf);
if (l>bufsize-1) l=bufsize-1; // Platz für '\0' behalten
buf[i=l]=0; // ggf. abhacken
hilite();
Serial.print(buf); // Kursor dahinter
};
// Hier geht's los
onNewStr(); // Vorgabe ausspucken
// Mal schnell einen Zeileneditor gebastelt:
int arg=-1; // Numerisches Escape-Argument (nur eins)
for(;;) {
int c=Serial.read();
if (c<0) continue;
if (arg>=0) { // Escape-Modus?
if ('0'<=c && c<='9') {arg=arg*10+c-'0'; continue;} // Ziffern zu Dezimalzahl zusammensetzen
switch (c) {
case '[':
case 'O': continue;
// Kursortasten zum Editieren auswerten
// (Mit Shift markieren sowie Copy&Paste ist nicht möglich.)
case 'C': c='F'-'@'; break; // Pfeil rechts
case 'D': c='B'-'@'; break; // Pfeil links
case 'K':
case 'F': c='E'-'@'; break; // Ende
case 'H': c='A'-'@'; break; // Pos1
case '~': switch (arg) {
case 1: c='A'-'@'; break; // Pos1 (PuTTY)
case 3: delChar(); c=-1; break; // Entf
case 4: c='E'-'@'; break; // Ende (PuTTY)
default: c='\a';
}break;
default: c='\a';
}
arg=-1; // Ende Escape-Seqenz
}
if (arg<0 && c>=0) switch (c) {
case 'A'-'@': backspace(strcount(buf,buf+i)); i=0; break; // Pos1
case 'B'-'@': goLeft(); break;
case 'C'-'@': goto raus; // Abbruch mit ^C
case 'E'-'@': Serial.print(buf+i); i=l; break; // Ende
case 'F'-'@': goRight(); break;
case 'P'-'@':
case 'N'-'@': if (keycb) do switch (keycb(c,cbdat)) {
case clear_call_again: clearBuf(); c=-1; continue; // mit c==-1 erneut rufen; der zweite Callback sollte einen neuen String laden
case string_replaced: onNewStr(); break;
default: ding();
}while(false); else ding();
break;
case '\a': ding(); break; // Bimmel als -Echo
case '\b':
case 127: if (goLeft()) delChar(); break;
case '\v':
case '\t': ding(); break; // TAB verwerfen
case '\r': // Enter-Taste (mal so, mal so)
case '\n': arg=l; goto raus; // Stringlänge reporten
case 'U'-'@': clearBuf(); break;
case '\e': arg=0; break;
// TODO: Akzentuierte Buchstaben bei Kursorpositionierung beachten
default: // Buchstaben oder UTF-8-Bruchstücke
int needspace=c&0x80 ? c&0x40 ? c&0x20 ? c&0x10 ? 4 : 3 : 2 : 0 : 1;
if (l+needspace<bufsize-1) {
if (needspace) {
l+=needspace;
memmove(buf+i+needspace,buf+i,l-i); // String verlängern
memset(buf+i,0xC2,needspace);
}
buf[i]=c; // Byte einfügen
Serial.print(buf+i++); // Ab da ausgeben
backspace(strcount(buf+i,buf+l)); // Kursor repositionieren
}else ding();
}//switch
}//for
raus:
unmark();
Serial.println();
return arg;
}
int input(const char*prompt,char*buf,int bufsize,KeyCb keycb,void*cbdat) {
Serial.print(prompt);
Serial.print(": ");
return input(buf,bufsize,keycb,cbdat);
}
Detected encoding: UTF-8 | 0
|