ref: f4122cfbc9ee6b99f69f1194ea38565fdfad36bb
parent: 578af37678b2ea16e29d950cbc352823cd2491ab
author: Jacob Moody <moody@posixcafe.org>
date: Tue Apr 2 00:01:56 EDT 2024
ktrans: graphical upgrade and feedback * scrollbar and mouse selection of candidate * arrow keys for moving selection cursor after first completion * user defined dictionaries that are merged on top * document using the plumber to change languages * loop candidates when reaching the start/end of the list. * skk2ktrans was using the wrong from encoding
--- a/lib/ktrans/skk2ktrans
+++ b/lib/ktrans/skk2ktrans
@@ -1,2 +1,2 @@
#!/bin/rc
-tcs -sf jis | awk '$1 !~ /;;/ {gsub("(^\/|\/$)", "", $2); gsub(" ", " "); gsub("\/", " ", $2);} {print}'
+tcs -sf ujis | awk '$1 !~ /;;/ {gsub("(^\/|\/$)", "", $2); gsub(" ", " "); gsub("\/", " ", $2);} {print}'
--- a/sys/man/1/ktrans
+++ b/sys/man/1/ktrans
@@ -24,29 +24,47 @@
.I kbdtap
file is given, it is used for both
input and output instead.
-.I Ktrans
-starts in a passthrough mode, echoing out
-the input with no conversions. Control characters
-are used to give instructions, the following
-control sequences are used to switch between languages:
+By default
+.I ktrans
+starts in passthrough mode, echoing out
+the input with no conversions. The initial
+language is set with the
+.B -l
+flag. After operation has begun, the language
+may be changed by either typing a control sequence
+and/or through the plumber.
+The following table provides the control
+sequence and
+.I lang
+strings accepted for each supported language respectfully.
.TP
-.B ctl-t
English (Passthrough).
+ctl-t and en
.TP
-.B ctl-n
Japanese Hiragana.
+ctl-n and jp
.TP
-.B ctl-k
Japanese Katakana.
+ctl-k and jpk
.TP
-.B ctl-c
Chinese.
+ctl-c and zh
.TP
-.B ctl-s
Korean.
+ctl-k and ko
.TP
-.B ctl-v
Vietnamese.
+ctl-v and vn
+.PP
+.I Ktrans
+listens on the
+.I lang
+plumber port for switching languages. The data accepted
+on this port is the same as the
+.B -l
+flag's
+.I lang
+argument.
.SH CONVERSION
Conversion is done in two layers, an implicit
layer for unambiguous mappings, and an explicit
@@ -74,7 +92,10 @@
Input is always passed along, when a match is found
.I Ktrans
will emit backspaces to clear the input sequence and replace
-it with the matched sequence.
+it with the matched sequence. Once
+.B ctl-\e
+has been used to start the selection of an explicit match, the
+up and down arrow keys may be used to thumb around the options.
.SH DISPLAY
.I Ktrans
will provide a graphical display of current explicit conversion
@@ -81,9 +102,17 @@
candidates as implicit conversion is done. Candidates are highlighted
as a user cycles through them. At the bottom of the list is an exit
button for quitting the program. Keyboard input typed in to the window is
-transliterated but discarded, providing a scratch input space. The
+transliterated but discarded, providing a scratch input space. The mouse
+may be used to scroll through and select candidates, but it requires that
+.I ktrans
+is started using
+.IR rio (1)'s
+.B -k
+flag.
+.PP
+The
.B -G
-option disables this display.
+flag disables the graphical display entirely.
.SH "KEY MAPPING"
For convenience, the control characters used by
.I ktrans
@@ -123,14 +152,17 @@
.BR /lib/ktrans .
The formats of which are specified within
.IR ktrans (6).
-Users may create and or modify existing dictionaries by binding over
-the system defaults.
+Additionally, dictionaries located in
+.B $home/lib/ktrans/
+will be merged on top of the system dictionaries.
+Merging is done at a list level only; Keys that appear
+replace all values of the previous definition.
.PP
For backwards compatibility the
.B jisho
and
.B zidian
-environment variables may also be set to pick explicit lookup dictionaries
+environment variables may also be set to pick alternate system dictionaries
for Japanese and Chinese respectfully.
.SH LANGUAGES
.SS JAPANESE
--- a/sys/src/cmd/ktrans/main.c
+++ b/sys/src/cmd/ktrans/main.c
@@ -160,8 +160,6 @@
if(h == nil)
h = hmapalloc(8192, sizeof(kouho));
- else
- hmapreset(h, 1);
while(p = Brdstr(b, '\n', 1)){
if(p[0] == '\0' || p[0] == ';'){
Err:
@@ -275,7 +273,7 @@
return hmapget(*h, s, m);
}
-enum { Msgsize = 64 };
+enum { Msgsize = 256 };
static Channel *dictch;
static Channel *output;
static Channel *input;
@@ -291,19 +289,22 @@
Mouse m;
Keyboardctl *kctl;
Rune key;
- char *kouho[Maxkouho+1], **s;
- Image *back, *text, *board, *high;
+ char *kouho[Maxkouho], **s, **e;
+ int i, page, total, height, round;
+ Image *back, *text, *board, *high, *scroll;
Font *f;
Point p;
- Rectangle r, exitr, selr;
+ Rectangle r, exitr, selr, scrlr;
int selected;
- enum { Adisp, Aresize, Amouse, Asel, Akbd, Aend };
+ char *mp, move[Msgsize];
+ enum { Adisp, Aresize, Amouse, Asel, Akbd, Amove, Aend };
Alt a[] = {
- [Adisp] { nil, kouho+1, CHANRCV },
+ [Adisp] { nil, kouho, CHANRCV },
[Aresize] { nil, nil, CHANRCV },
[Amouse] { nil, &m, CHANRCV },
[Asel] { nil, &selected, CHANRCV },
[Akbd] { nil, &key, CHANRCV },
+ [Amove] { nil, move, CHANNOP },
[Aend] { nil, nil, CHANEND },
};
@@ -324,7 +325,6 @@
sysfatal("failed to get keyboard: %r");
memset(kouho, 0, sizeof kouho);
- kouho[0] = "候補";
selected = -1;
f = display->defaultfont;
high = allocimagemix(display, DYellowgreen, DWhite);
@@ -331,6 +331,7 @@
text = display->black;
back = allocimagemix(display, DPaleyellow, DWhite);
board = allocimagemix(display, DBlack, DWhite);
+ scroll = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
a[Adisp].c = displaych;
a[Aresize].c = mctl->resizec;
@@ -337,11 +338,15 @@
a[Amouse].c = mctl->c;
a[Asel].c = selectch;
a[Akbd].c = kctl->c;
+ a[Amove].c = input;
threadsetname("display");
goto Redraw;
for(;;)
switch(alt(a)){
+ case Amove:
+ a[Amove].op = CHANNOP;
+ break;
case Akbd:
if(key != Kdel)
break;
@@ -350,29 +355,98 @@
case Amouse:
if(!m.buttons)
break;
- if(!ptinrect(m.xy, exitr))
+ if(ptinrect(m.xy, exitr)){
+ closedisplay(display);
+ threadexitsall(nil);
+ }
+ if(kouho[0] == nil)
break;
- closedisplay(display);
- threadexitsall(nil);
+ if(m.xy.x > scrlr.min.x && m.xy.x < scrlr.max.x){
+ if(m.xy.y > scrlr.min.y && m.xy.y < scrlr.max.y)
+ break;
+ if(m.xy.y < scrlr.min.y)
+ goto Up;
+ else
+ goto Down;
+ }
+
+ if(m.buttons & 7){
+ m.xy.y -= screen->r.min.y;
+ m.xy.y -= f->height;
+ if(m.xy.y < 0)
+ break;
+ i = round + m.xy.y/f->height + 1;
+ if(selected != -1)
+ i = i - selected - 1;
+ } else if(m.buttons == 8){
+ Up:
+ i = -1 * (selected % height + height);
+ if(selected + i < 0)
+ i = -(selected + (total % height));
+ } else if(m.buttons == 16){
+ Down:
+ i = height - (selected % height);
+ if(selected + i > total)
+ i = total - selected;
+ } else
+ break;
+
+ memset(move, 0, sizeof move);
+ move[0] = 'c';
+ if(i == 0)
+ break;
+ else if(i > 0)
+ memset(move+1, '', i);
+ else for(mp = move+1; i < 0; i++)
+ mp = seprint(mp, move + sizeof move, "%C", Kup);
+ a[Amove].op = CHANSND;
+ break;
case Aresize:
getwindow(display, Refnone);
case Adisp:
Redraw:
+ for(s = kouho, total = 0; *s != nil; s++, total++)
+ ;
+
r = screen->r;
+ height = Dy(r)/f->height - 2;
draw(screen, r, back, nil, ZP);
r.max.y = r.min.y + f->height;
draw(screen, r, board, nil, ZP);
+ round = selected - (selected % height);
- if(selected+1 > 0 && kouho[selected+1] != nil){
+ if(selected >= 0 && kouho[selected] != nil){
selr = screen->r;
- selr.min.y += f->height*(selected+1);
+ selr.min.y += f->height*(selected-round+1);
selr.max.y = selr.min.y + f->height;
draw(screen, selr, high, nil, ZP);
}
+ scrlr = screen->r;
+ scrlr.min.y += f->height;
+ scrlr.max.y -= f->height;
+ scrlr.max.x = scrlr.min.x + 10;
+ draw(screen, scrlr, scroll, nil, ZP);
+
+ if(kouho[0] != nil){
+ scrlr.max.x--;
+ page = Dy(scrlr) / (total / height + 1);
+ scrlr.min.y = scrlr.min.y + page*(round / height);
+ scrlr.max.y = scrlr.min.y + page;
+ /* fill to the bottom on last page */
+ if((screen->r.max.y - f->height) - scrlr.max.y < page)
+ scrlr.max.y = screen->r.max.y - f->height;
+ draw(screen, scrlr, back, nil, ZP);
+ }
+
r.min.x += Dx(r)/2;
p.y = r.min.y;
- for(s = kouho; *s != nil; s++){
+
+ p.x = r.min.x - stringwidth(f, "候補")/2;
+ string(screen, p, text, ZP, f, "候補");
+ p.y += f->height;
+
+ for(s = kouho+round, e = kouho+round+height; *s != nil && s < e; s++){
p.x = r.min.x - stringwidth(f, *s)/2;
string(screen, p, text, ZP, f, *s);
p.y += f->height;
@@ -414,7 +488,7 @@
Str line;
Str last;
Str okuri;
- int selected;
+ int selected, loop;
enum{
Kanji,
@@ -425,6 +499,7 @@
dict = jisho;
selected = -1;
+ loop = 0;
mode = Kanji;
memset(kouho, 0, sizeof kouho);
resetstr(&last, &line, &okuri, nil);
@@ -463,6 +538,24 @@
}
popstr(&line);
break;
+ case Kup:
+ if(selected == -1){
+ emitutf(output, p, 1);
+ break;
+ }
+ if(--selected < 0){
+ //wrap
+ while(kouho[++selected] != nil)
+ ;
+ selected--;
+ }
+ loop = 1;
+ goto Select;
+ case Kdown:
+ if(selected == -1){
+ emitutf(output, p, 1);
+ break;
+ }
case '':
selected++;
if(selected == 0){
@@ -472,20 +565,16 @@
line.p[-1] = '\0';
}
if(kouho[selected] == nil){
- /* cycled through all matches; bail */
- if(utflen(okuri.b) != 0)
- emitutf(output, backspace, utflen(okuri.b));
- emitutf(output, backspace, utflen(last.b));
- emitutf(output, line.b, 0);
- emitutf(output, okuri.b, 0);
- break;
+ selected = 0;
+ loop = 1;
}
+ Select:
send(selectch, &selected);
send(displaych, kouho);
if(okuri.p != okuri.b)
emitutf(output, backspace, utflen(okuri.b));
- if(selected == 0)
+ if(selected == 0 && !loop)
emitutf(output, backspace, utflen(line.b));
else
emitutf(output, backspace, utflen(last.b));
@@ -494,6 +583,7 @@
last.p = pushutf(last.b, strend(&last), kouho[selected], 0);
emitutf(output, okuri.b, 0);
mode = Kanji;
+ loop = 0;
continue;
case ',': case '.':
case L'。': case L'、':
@@ -585,6 +675,7 @@
{
int lang;
char m[Msgsize];
+ char *todict;
Map lkup;
char *p;
int n;
@@ -596,6 +687,7 @@
resetstr(&line, nil);
if(lang == LangJP || lang == LangZH)
emitutf(dictch, peek, 1);
+ todict = smprint("%C%C", Kup, Kdown);
threadsetname("keytrans");
while(recv(input, m) != -1){
@@ -624,7 +716,7 @@
emitutf(output, p, 1);
continue;
}
- if(utfrune("", r) != nil){
+ if(utfrune(todict, r) != nil){
resetstr(&line, nil);
emitutf(dictch, p, 1);
continue;
@@ -684,7 +776,7 @@
kbdtap(void*)
{
char m[Msgsize];
- char buf[128];
+ char buf[Msgsize];
char *p;
int n;
@@ -774,10 +866,13 @@
threadexits("usage");
}
+
+static char *kdir = "/lib/ktrans";
+
struct {
char *s;
Hmap **m;
-} inittab[] = {
+} initmaptab[] = {
"judou", &judou,
"hira", &hira,
"kata", &kata,
@@ -784,6 +879,14 @@
"hangul", &hangul,
"telex", &telex,
};
+struct {
+ char *env;
+ char *def;
+ Hmap **m;
+} initdicttab[] = {
+ "jisho", "kanji.dict", &jisho,
+ "zidian", "wubi.dict", &zidian,
+};
mainstacksize = 8192*2;
@@ -792,7 +895,8 @@
{
int nogui, i;
char buf[128];
- char *jishoname, *zidianname;
+ char *e, *home;
+ Hmap *m;
deflang = LangEN;
nogui = 0;
@@ -822,13 +926,17 @@
usage();
}
+ dictch = chancreate(Msgsize, 0);
+ input = chancreate(Msgsize, 0);
+ output = chancreate(Msgsize, 0);
+
/* allow gui to warm up while we're busy reading maps */
if(nogui || access("/dev/winid", AEXIST) < 0){
displaych = nil;
selectch = nil;
} else {
- selectch = chancreate(sizeof(int), 1);
- displaych = chancreate(sizeof(char*)*Maxkouho, 1);
+ selectch = chancreate(sizeof(int), 0);
+ displaych = chancreate(sizeof(char*)*Maxkouho, 0);
proccreate(displaythread, nil, mainstacksize);
}
@@ -835,26 +943,30 @@
memset(backspace, '\b', sizeof backspace-1);
backspace[sizeof backspace-1] = '\0';
- if((jishoname = getenv("jisho")) == nil)
- jishoname = "/lib/ktrans/kanji.dict";
- if((jisho = opendict(nil, jishoname)) == nil)
- sysfatal("failed to open jisho: %r");
+ if((home = getenv("home")) == nil)
+ sysfatal("$home undefined");
+ for(i = 0; i < nelem(initdicttab); i++){
+ e = getenv(initdicttab[i].env);
+ if(e != nil){
+ snprint(buf, sizeof buf, "%s", e);
+ free(e);
+ } else
+ snprint(buf, sizeof buf, "%s/%s", kdir, initdicttab[i].def);
+ if((*initdicttab[i].m = opendict(*initdicttab[i].m, buf)) == nil)
+ sysfatal("failed to open dict: %r");
+ snprint(buf, sizeof buf, "%s/%s/%s", home, kdir, initdicttab[i].def);
+ m = opendict(*initdicttab[i].m, buf);
+ if(m != nil)
+ *initdicttab[i].m = m;
+ }
+ free(home);
- if((zidianname = getenv("zidian")) == nil)
- zidianname = "/lib/ktrans/wubi.dict";
- if((zidian = opendict(nil, zidianname)) == nil)
- sysfatal("failed to open zidian: %r");
-
natural = nil;
- for(i = 0; i < nelem(inittab); i++){
- snprint(buf, sizeof buf, "/lib/ktrans/%s.map", inittab[i].s);
- if((*inittab[i].m = openmap(buf)) == nil)
+ for(i = 0; i < nelem(initmaptab); i++){
+ snprint(buf, sizeof buf, "%s/%s.map", kdir, initmaptab[i].s);
+ if((*initmaptab[i].m = openmap(buf)) == nil)
sysfatal("failed to open map: %r");
}
-
- dictch = chancreate(Msgsize, 0);
- input = chancreate(Msgsize, 0);
- output = chancreate(Msgsize, 0);
plumbfd = plumbopen("lang", OREAD);
if(plumbfd >= 0)
--- a/sys/src/cmd/ktrans/test.c
+++ b/sys/src/cmd/ktrans/test.c
@@ -9,7 +9,7 @@
"no", L"の",
"nno", L"んの",
"neko", L"猫",
- "neko", L"ねこ",
+ "neko", L"猫",
"watashi", L"私",
"tanoShi", L"楽し",
"oreNO", L"俺の",
@@ -116,7 +116,7 @@
goto Verify;
case 8:
if(nstack == 0)
- sysfatal("buffer underrun");
+ sysfatal("buffer underrun on: %s", set[i].input);
nstack--;
stack[nstack] = 0;
break;