emoji: add one-shot candidate search

This commit is contained in:
2026-08-11 22:24:59 +09:00
parent 82276bf37f
commit 0fff17c8b8
14 changed files with 891 additions and 346 deletions

View File

@@ -42,5 +42,6 @@ test:
verify-map:
python3 map/mktelex.py | cmp - map/telex.map
python3 -B map/mkemoji | cmp - map/emoji.dict
.PHONY: all check test verify-map clean xim bench

View File

@@ -66,12 +66,32 @@ Switch input modes with Ctrl + key:
S Hangul
T English
V Vietnamese (Telex)
E Emoji
P Toggle preedit/candidate popup window
P Toggle preedit echo
Type romanized input. Select candidates with 1-9 or arrow keys.
Tab or Enter to commit.
### Emoji and symbols
Ctrl+E starts a one-shot symbol search; it is not a mode. Any current preedit
is committed, then the previous language is kept for the next input. Type an
English alias or the phonetic keys of the current layout (Korean aliases work,
for example). The candidate list is shown even when Ctrl+P has hidden normal
preedit echo.
안녕 → Ctrl+E → smile → Enter → 😀
There is no auto-insert. Enter commits the first candidate (or the current
one); arrows and Tab move, Alt+1 through Alt+9 select, Esc cancels. Plain
digits are part of the query.
`map/emoji.src` is the source of truth: each UTF-8 TSV row is a result followed
by one or more tab-separated aliases. Aliases may be multilingual. Run
`map/mkemoji > map/emoji.dict` after editing it; `make verify-map` checks the
generated dictionary and the Telex map.
😀 웃음 웃다 스마일 smile grin
## Architecture
Threads communicate via CSP channels:

1
dat.h
View File

@@ -156,6 +156,7 @@ struct Dictres
Str key;
Str kouho[Maxkouho];
int nkouho;
int lang;
};
extern Lang langs[];

1
dict.c
View File

@@ -12,6 +12,7 @@ dictlookup(Dictreq *req, Dictres *res)
res->key = req->pre;
res->nkouho = 0;
res->lang = req->lang;
if(req->key.n == 0)
return;
l = getlang(req->lang);

View File

@@ -0,0 +1,226 @@
! ⚠ ≠
!! ⚠
!= ≠
* ★
** ★
+ ±
+- ±
- →
-> →
. · … ÷
.. · …
... …
./ ÷
: ☹ ☺
:( ☹
:) ☺
< ← ≤ ≠ ♥
<- ←
<3 ♥
<= ≤
<> ≠
= ≡ ⇒
== ≡
=> ⇒
> ≥
>= ≥
^ ⁽ ⁾ ⁺ ⁻ ⁼ ⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁱ ⁿ
^( ⁽
^) ⁾
^+ ⁺
^- ⁻
^0 ⁰
^1 ¹
^2 ²
^3 ³
^4 ⁴
^5 ⁵
^6 ⁶
^7 ⁷
^8 ⁸
^9 ⁹
^= ⁼
^i ⁱ
^n ⁿ
_ ₍ ₎ ₊ ₋ ₌ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₐ ₑ ₒ ₓ
_( ₍
_) ₎
_+ ₊
_- ₋
_0 ₀
_1 ₁
_2 ₂
_3 ₃
_4 ₄
_5 ₅
_6 ₆
_7 ₇
_8 ₈
_9 ₉
_= ₌
_a ₐ
_e ₑ
_o ₒ
_x ₓ
a α
al α
alp α
alph α
alpha α
b β 😊
be β
bet β
beta β
bl 😊
blu 😊
blus 😊
blush 😊
c χ
ch χ
chi χ
d ° δ Δ ↓
de Δ ° δ
deg °
del δ
delt δ
delta δ
dn ↓
e ε η
ep ε
eps ε
et η
eta η
f 凸
fu 凸
fuc 凸
fuck 凸
g γ Γ 😀
ga Γ γ
gam γ
gamm γ
gamma γ
gr 😀
gri 😀
grin 😀
h 😄 😊 ❤️
ha 😄 😊
hap 😄 😊
happ 😄 😊
happy 😄 😊
he ❤️
hea ❤️
hear ❤️
heart ❤️
i ∫ ∞ ι
ii ∫
in ∞
inf ∞
io ι
iot ι
iota ι
j 😂
jo 😂
joy 😂
k κ
ka κ
kap κ
kapp κ
kappa κ
l λ Λ 😂 ❤️ 👍
la Λ λ
lam λ
lamb λ
lambd λ
lambda λ
li 👍
lik 👍
like 👍
lo 😂 ❤️
lol 😂
lov ❤️
love ❤️
m × μ
mu μ ×
mul ×
n ν
nu ν
o ω Ω ●
om Ω ω
ome ω
omeg ω
omega ω
oo ●
p φ Φ π Π ∏ ψ Ψ
ph Φ φ
phi φ
pi π Π
pp ∏
ps Ψ ψ
psi ψ
r ρ
rh ρ
rho ρ
s σ Σ √ ∑ 😀 😄 😊
si Σ σ
sig σ
sigm σ
sigma σ
sm 😀 😄 😊
smi 😀 😄 😊
smil 😀 😄 😊
smile 😀 😄 😊
sq √
ss ∑
t τ θ Θ 👍
ta τ
tau τ
th Θ θ 👍
the θ
thet θ
theta θ
thu 👍
thum 👍
thumb 👍
thumbs 👍
thumbsu 👍
thumbsup 👍
u ↑ υ
up ↑ υ
ups υ
v ✓
vv ✓
x ξ Ξ ✗
xi ξ Ξ
xx ✗
z ζ
ze ζ
zet ζ
zeta ζ
~ ≈
~= ≈
따 👍
따봉 👍
미 😄 😊
미소 😄 😊
방 😊
방긋 😊
사 ❤️
사랑 ❤️
스 😀 😄
스마 😀 😄
스마일 😀 😄
엄 👍
엄지 👍
웃 😀 😄 😂
웃겨 😂
웃다 😀
웃음 😀 😄 😂
좋 👍
좋아 👍
좋아요 👍
찬 👍
찬성 👍
폭 😂
폭소 😂
하 ❤️
하트 ❤️

View File

@@ -1,183 +0,0 @@
! !
!! ⚠
!= ≠
* *
** ★
+ +
+- ±
- -
-> →
. .
.. ·
... …
./ ÷
: :
:( ☹
:) ☺
< <
<- ←
<3 ♥
<= ≤
<> ≠
= =
== ≡
=> ⇒
> >
>= ≥
D D
De Δ
G G
Ga Γ
I I
II ∫
L L
La Λ
O O
Om Ω
P P
PP ∏
Ph Φ
Pi Π
Ps Ψ
S S
SS ∑
Si Σ
T T
Th Θ
X X
Xi Ξ
^ ^
^( ⁽
^) ⁾
^+ ⁺
^- ⁻
^0 ⁰
^1 ¹
^2 ²
^3 ³
^4 ⁴
^5 ⁵
^6 ⁶
^7 ⁷
^8 ⁸
^9 ⁹
^= ⁼
^i ⁱ
^n ⁿ
_ _
_( ₍
_) ₎
_+ ₊
_- ₋
_0 ₀
_1 ₁
_2 ₂
_3 ₃
_4 ₄
_5 ₅
_6 ₆
_7 ₇
_8 ₈
_9 ₉
_= ₌
_a ₐ
_e ₑ
_o ₒ
_x ₓ
a a
al al
alp alp
alph alph
alpha α
b b
be be
bet bet
beta β
c c
ch ch
chi χ
d d
de de
deg °
del del
delt delt
delta δ
dn ↓
e e
ep ep
eps ε
et et
eta η
f f
fu fu
fuc fuc
fuck 凸
g g
ga ga
gam gam
gamm gamm
gamma γ
i i
in in
inf ∞
io io
iot iot
iota ι
k k
ka ka
kap kap
kapp kapp
kappa κ
l l
la la
lam lam
lamb lamb
lambd lambd
lambda λ
m m
mu μ
mul ×
n n
nu ν
o o
om om
ome ome
omeg omeg
omega ω
oo ●
p p
ph ph
phi φ
pi π
ps ps
psi ψ
r r
rh rh
rho ρ
s s
si si
sig sig
sigm sigm
sigma σ
sq √
t t
ta ta
tau τ
th th
the the
thet thet
theta θ
u u
up ↑
ups υ
v v
vv ✓
x x
xi ξ
xx ✗
z z
ze ze
zet zet
zeta ζ
~ ~
~= ≈

View File

@@ -1,100 +1,108 @@
!! ⚠
!= ≠
**
+- ±
->
.. ·
...
./ ÷
:(
:)
<-
<=
<>
==
=>
>=
^(
^)
^+
^-
^=
_(
_)
_+
_-
_=
~=
^0
_0
^1 ¹
_1
^2 ²
_2
<3
^3 ³
_3
^4
_4
^5
_5
^6
_6
^7
_7
^8
_8
^9
_9
_a
alpha α
beta β
chi χ
deg °
delta δ
De Δ
dn
_e
eps ε
eta η
fuck
gamma γ
Ga Γ
^i
II
inf
iota ι
kappa κ
lambda λ
La Λ
mul ×
mu μ
^n
nu ν
omega ω
Om Ω
oo
_o
phi φ
Ph Φ
pi π
Pi Π
PP
psi ψ
Ps Ψ
rho ρ
sigma σ
Si Σ
sq
SS
tau τ
theta θ
Th Θ
up
ups υ
vv
xi ξ
Xi Ξ
xx
_x
zeta ζ
# Result first, followed by one or more TAB-separated aliases.
⚠ !!
**
± +-
->
· ..
...
÷ ./
:(
:)
<-
<=
<> !=
==
=>
>=
^(
^)
^+
^-
^=
_(
_)
_+
_-
_=
~=
^0
_0
¹ ^1
_1
² ^2
_2
<3
³ ^3
_3
^4
_4
^5
_5
^6
_6
^7
_7
^8
_8
^9
_9
_a
α alpha
β beta
χ chi
° deg
δ delta
Δ De
dn
_e
ε eps
η eta
fuck
γ gamma
Γ Ga
^i
II
inf
ι iota
κ kappa
λ lambda
Λ La
× mul
μ mu
^n
ν nu
ω omega
Ω Om
oo
_o
φ phi
Φ Ph
π pi
Π Pi
PP
ψ psi
Ψ Ps
ρ rho
σ sigma
Σ Si
sq
SS
τ tau
θ theta
Θ Th
up
υ ups
vv
ξ xi
Ξ Xi
xx
_x
ζ zeta
# Small multilingual emoji seed. Shared aliases intentionally produce choices.
😀 웃음 웃다 스마일 smile grin
😄 웃음 미소 스마일 smile happy
😂 웃음 폭소 웃겨 joy lol
😊 미소 방긋 smile blush happy
❤️ 사랑 하트 heart love
👍 좋아요 찬성 따봉 엄지 thumbsup like

View File

@@ -1,39 +1,71 @@
#!/bin/sh
#!/usr/bin/env python3
"""Write emoji.dict to stdout from a result-first UTF-8 TSV file."""
awk '
BEGIN { FS = "\t" }
/^$/ { next }
{
key = $1
val = $2
map[key] = val
for(i = 1; i < length(key); i++){
p = substr(key, 1, i)
if(!(p in map))
map[p] = p
}
}
END {
for(k in map)
print k "\t" map[k]
}
' $1 | sort -k1,1 > emoji.map
import sys
import unicodedata
from pathlib import Path
awk '
BEGIN { FS = "\t" }
/^$/ { next }
{
key = $1
val = $2
for(i = 1; i <= length(key); i++){
p = substr(key, 1, i)
dict[p] = dict[p] " " val
}
}
END {
for(p in dict){
sub(/^ /, "", dict[p])
print p "\t" dict[p]
}
}
' $1 | sort -k1,1 > emoji.dict
SOURCE = Path(__file__).with_name("emoji.src")
def fold(s):
s = "".join(chr(ord(c) + 32) if "A" <= c <= "Z" else c for c in s)
return unicodedata.normalize("NFC", s)
def read(path):
entries = []
with path.open(encoding="utf-8") as src:
for lineno, raw in enumerate(src, 1):
line = raw.rstrip("\r\n")
if not line.strip() or line.lstrip().startswith("#"):
continue
fields = line.split("\t")
if len(fields) < 2:
raise ValueError(f"{path}:{lineno}: need a result and an alias")
result = unicodedata.normalize("NFC", fields[0])
if not result or any(c.isspace() for c in result):
raise ValueError(f"{path}:{lineno}: bad result")
for field in fields[1:]:
alias = fold(field)
if not alias or alias != alias.strip():
raise ValueError(f"{path}:{lineno}: bad alias")
entries.append((result, alias))
return entries
def add(table, key, result):
values = table.setdefault(key, [])
if result not in values:
values.append(result)
def build(entries):
exact = {}
prefix = {}
for result, alias in entries:
for n in range(1, len(alias) + 1):
add(exact if n == len(alias) else prefix, alias[:n], result)
for key in sorted(exact.keys() | prefix.keys()):
values = exact.get(key, []) + prefix.get(key, [])
values = list(dict.fromkeys(values))
yield f"{key}\t{' '.join(values)}"
def main():
if len(sys.argv) > 2:
print(f"usage: {sys.argv[0]} [emoji.src]", file=sys.stderr)
return 2
path = Path(sys.argv[1]) if len(sys.argv) == 2 else SOURCE
try:
for line in build(read(path)):
print(line)
except (OSError, UnicodeError, ValueError) as error:
print(error, file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

279
strans.c
View File

@@ -3,8 +3,18 @@
static Im im;
static int popup = 0;
static int visible = 0;
static void dictqmap(Im*);
typedef struct Search Search;
struct Search
{
int on;
Str raw;
Str text;
};
static Search search;
static void
backmap(Im *im)
{
@@ -16,7 +26,7 @@ Lang langs[] = {
{LangJP, "hira", "kanji", transmap, backmap, dictqmap, nil, nil},
{LangJPK, "kata", "kanji", transmap, backmap, dictqmap, nil, nil},
{LangKO, "hangul", nil, transko, backko, dictqmap, nil, nil},
{LangEMOJI, "emoji", "emoji", transmap, backmap, dictqmap, nil, nil},
{LangEMOJI, nil, "emoji", nil, nil, nil, nil, nil},
{LangVI, "telex", nil, transvi, backvi, dictqmap, nil, nil},
};
int nlang = nelem(langs);
@@ -44,13 +54,15 @@ static void
show(void)
{
Drawcmd dc;
Str *pre;
int i, first, n;
if(!popup)
return;
sclear(&dc.pre);
if(!mapget(im.l->map, &im.pre, &dc.pre))
dc.pre = im.pre;
pre = search.on ? &search.text : &im.pre;
if(popup){
if(search.on || im.l->map == nil || !mapget(im.l->map, pre, &dc.pre))
dc.pre = *pre;
}
first = im.sel >= Maxdisp ? im.sel - Maxdisp + 1 : 0;
n = im.nkouho - first;
if(n > Maxdisp) n = Maxdisp;
@@ -58,6 +70,9 @@ show(void)
dc.sel = im.sel >= 0 ? im.sel - first : -1;
for(i = 0; i < n; i++)
dc.kouho[i] = im.kouho[first + i];
if(dc.pre.n == 0 && n == 0 && !visible)
return;
visible = dc.pre.n != 0 || n != 0;
chansend(drawc, &dc);
}
@@ -110,7 +125,7 @@ commit(Str *com)
{
Str val;
if(mapget(im.l->map, &im.pre, &val))
if(im.l->map != nil && mapget(im.l->map, &im.pre, &val))
sappend(com, &val);
else
sappend(com, &im.pre);
@@ -209,13 +224,240 @@ transmap(Im *im, Rune c)
return e;
}
static Rune
ctrlkey(Rune c)
{
if(c >= 'a' && c <= 'z')
return c - 'a' + 1;
return c;
}
static void
foldascii(Str *s)
{
int i;
for(i = 0; i < s->n; i++)
if(s->r[i] >= 'A' && s->r[i] <= 'Z')
s->r[i] += 'a' - 'A';
}
static void
transstr(Lang *l, Str *raw, Str *out)
{
static Im q;
Emit e;
Str mapped;
int i;
sclear(out);
if(l == nil || l->trans == nil || (l->trans == transmap && l->map == nil)){
*out = *raw;
return;
}
memset(&q, 0, sizeof q);
q.l = l;
for(i = 0; i < raw->n; i++){
e = l->trans(&q, raw->r[i]);
sappend(out, &e.s);
q.pre = e.next;
if(!e.eat && out->n < Maxrunes)
sputr(out, raw->r[i]);
}
if(q.pre.n == 0)
return;
if(l->map != nil && mapget(l->map, &q.pre, &mapped))
sappend(out, &mapped);
else
sappend(out, &q.pre);
}
static void
addkouho(Str *s)
{
int i;
for(i = 0; i < im.nkouho; i++)
if(scmp(&im.kouho[i], s) == 0)
return;
if(im.nkouho < Maxkouho)
im.kouho[im.nkouho++] = *s;
}
static void
emojilookup(Str *key, Dictres *res)
{
Dictreq req;
req.key = *key;
req.pre = req.key;
req.lang = LangEMOJI;
dictlookup(&req, res);
}
static void
emojiquery(void)
{
static Dictres res;
Str raw, local;
int i, rawhit, localhit;
raw = search.raw;
foldascii(&raw);
transstr(im.l, &search.raw, &local);
foldascii(&local);
clearkouho();
emojilookup(&raw, &res);
rawhit = res.nkouho != 0;
for(i = 0; i < res.nkouho; i++)
addkouho(&res.kouho[i]);
localhit = 0;
if(scmp(&raw, &local) != 0){
emojilookup(&local, &res);
localhit = res.nkouho != 0;
for(i = 0; i < res.nkouho; i++)
addkouho(&res.kouho[i]);
}
search.text = rawhit || !localhit ? raw : local;
show();
}
static void
endsearch(void)
{
search.on = 0;
sclear(&search.raw);
sclear(&search.text);
clearkouho();
show();
}
static void
startsearch(Str *com)
{
if(im.pre.n > 0)
commit(com);
sclear(&im.pre);
sclear(&im.raw);
clearkouho();
search.on = 1;
sclear(&search.raw);
sclear(&search.text);
show();
}
static void
commitsearch(Str *com)
{
sappend(com, &search.text);
endsearch();
}
static int
pickemoji(int n, Str *com)
{
if(n < 0 || n >= im.nkouho)
return 0;
sappend(com, &im.kouho[n]);
endsearch();
return 1;
}
static int
searchkey(u32int ks, u32int mod, Str *com)
{
int n, off;
Rune c;
if(ks == Ksuper || ks == Kshift)
return 1;
if(ks == Kdown || ks == Kup){
if(im.nkouho == 0)
return 1;
if(ks == Kdown && im.sel < im.nkouho - 1)
im.sel++;
if(ks == Kup && im.sel > 0)
im.sel--;
show();
return 1;
}
n = ks - '1';
off = im.sel >= Maxdisp ? im.sel - Maxdisp + 1 : 0;
if((mod & Malt) && n >= 0 && n < Maxdisp){
pickemoji(off + n, com);
return 1;
}
if(ks == Ktab){
if(im.nkouho == 0)
return 1;
if(mod & Mshift){
if(im.sel <= 0)
im.sel = im.nkouho - 1;
else
im.sel--;
}else if(im.sel >= im.nkouho - 1)
im.sel = 0;
else
im.sel++;
show();
return 1;
}
if(ks == Kret){
if(im.nkouho > 0)
return pickemoji(im.sel >= 0 ? im.sel : 0, com);
commitsearch(com);
return 1;
}
if(ks == Kback){
if(search.raw.n == 0)
return 1;
spopr(&search.raw);
emojiquery();
return 1;
}
if(ks == Kesc){
endsearch();
return 1;
}
if(mod & Mctrl){
c = ctrlkey(ks);
if(c == LangEMOJI){
endsearch();
return 1;
}
if(c == 0x10){
popup = !popup;
show();
return 1;
}
endsearch();
if(setlang(c))
return 1;
return 0;
}
if(ks >= Kspec || ks < ' ' || (mod & (Malt|Msuper))){
commitsearch(com);
return 0;
}
if(search.raw.n >= Maxrunes){
commitsearch(com);
return 0;
}
sputr(&search.raw, ks);
emojiquery();
return 1;
}
static int
keystroke(u32int ks, u32int mod, Str *com)
{
int n, off;
Rune c;
if(ks == Ksuper || ks == Kshift)
return 1;
if(search.on)
return searchkey(ks, mod, com);
if(ks == Kdown || ks == Kup){
if(im.nkouho == 0)
return 0;
@@ -271,14 +513,18 @@ keystroke(u32int ks, u32int mod, Str *com)
return 0;
}
if(mod & Mctrl){
reset();
if(ks >= 'a' && ks <= 'z')
ks -= 'a' - 1;
if(ks == 0x10){
popup = !popup;
c = ctrlkey(ks);
if(c == LangEMOJI){
startsearch(com);
return 1;
}
if(setlang(ks))
if(c == 0x10){
popup = !popup;
show();
return 1;
}
reset();
if(setlang(c))
return 1;
return 0;
}
@@ -305,6 +551,8 @@ init(void)
{
memset(&im, 0, sizeof(im));
im.l = getlang(LangEN);
memset(&search, 0, sizeof search);
visible = 0;
}
static void
@@ -316,15 +564,15 @@ imhandlekey(Keyreq *kr)
sclear(&res.preedit);
res.eaten = keystroke(kr->ks, kr->mod, &res.commit);
if(kr->want)
res.preedit = im.pre;
res.preedit = search.on ? search.text : im.pre;
chansend(kr->reply, &res);
}
void
imthread(void*)
{
static Dictres res;
Keyreq kr;
Dictres res;
Alt alts[] = {
{keyc, &kr, CHANRCV, nil},
{dictresc, &res, CHANRCV, nil},
@@ -339,7 +587,8 @@ imthread(void*)
imhandlekey(&kr);
break;
case 1:
if(scmp(&res.key, &im.pre) == 0){
if(!search.on && res.lang == im.l->lang &&
scmp(&res.key, &im.pre) == 0){
setkouho(&res);
show();
}

View File

@@ -25,6 +25,7 @@ dictionary_candidates(struct ct *t)
dictlookup(&req, &res);
if(!CT_EQ_INT(t, 2, res.nkouho))
goto cleanup;
CT_EQ_INT(t, LangJP, res.lang);
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
checkstr(t, "candidate 1", "候補1", &res.kouho[0]);
checkstr(t, "candidate 2", "候補2", &res.kouho[1]);
@@ -44,6 +45,7 @@ dictionary_candidates(struct ct *t)
dictlookup(&req, &res);
if(!CT_EQ_INT(t, Maxkouho, res.nkouho))
goto cleanup;
CT_EQ_INT(t, LangJP, res.lang);
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
@@ -81,6 +83,7 @@ dictionary_misses_clear_result(struct ct *t)
if(res.nkouho != 0)
CT_ERRORF(t, "%s: want 0 candidates, got %d",
cases[i].name, res.nkouho);
CT_EQ_INT(t, LangJP, res.lang);
checkstr(t, cases[i].name, cases[i].pre, &res.key);
}
hmapfree(lang->dict);

View File

@@ -255,3 +255,177 @@ engine_language_switch_state(struct ct *t)
}
vi->map = saved;
}
typedef struct Emojifix Emojifix;
struct Emojifix
{
Im im;
Search search;
Hmap *dict;
int popup;
int visible;
};
static int
draindraw(Drawcmd *last)
{
Drawcmd dc = {0};
int n;
n = 0;
while(channbrecv(drawc, &dc) > 0){
n++;
if(last != nil)
*last = dc;
}
return n;
}
static void
setemoji(Hmap **dict, char *key, char *val)
{
Str s;
s = mkstr(key);
hmapset(dict, &s, val, strlen(val));
}
static void
emojibegin(Emojifix *f, int showpre)
{
Lang *l;
l = getlang(LangEMOJI);
f->im = im;
f->search = search;
f->dict = l->dict;
f->popup = popup;
f->visible = visible;
draindraw(nil);
l->dict = hmapalloc(8);
setemoji(&l->dict, "alpha", "α");
setemoji(&l->dict, "smil", "😀 😄");
setemoji(&l->dict, "smile", "😀 😄");
setemoji(&l->dict, "웃음", "😀 😄 😂");
init();
im.l = getlang(LangKO);
popup = showpre;
}
static void
emojiend(Emojifix *f)
{
Lang *l;
Hmap *dict;
l = getlang(LangEMOJI);
dict = l->dict;
l->dict = f->dict;
hmapfree(dict);
draindraw(nil);
im = f->im;
search = f->search;
popup = f->popup;
visible = f->visible;
}
static int
typekeys(struct ct *t, char *keys, Str *com)
{
for(; *keys != '\0'; keys++)
if(!CT_CHECK(t, keystroke((uchar)*keys, 0, com)))
return 0;
return 1;
}
void
engine_emoji_single_candidate(struct ct *t)
{
Drawcmd dc = {0};
Emojifix f;
Str com;
emojibegin(&f, 0);
sclear(&com);
CT_CHECK(t, keystroke('e', Mctrl, &com));
CT_CHECK(t, search.on);
CT_EQ_INT(t, LangKO, im.l->lang);
if(typekeys(t, "alpha", &com)){
checkstr(t, "no automatic commit", "", &com);
checkstr(t, "raw English query", "alpha", &search.text);
CT_CHECK(t, search.on);
CT_EQ_INT(t, 1, im.nkouho);
checkstr(t, "single candidate", "α", &im.kouho[0]);
CT_CHECK(t, draindraw(&dc) > 0);
CT_EQ_INT(t, 0, dc.pre.n);
CT_EQ_INT(t, 1, dc.nkouho);
CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "explicit candidate commit", "α", &com);
CT_CHECK(t, !search.on);
CT_EQ_INT(t, LangKO, im.l->lang);
}
emojiend(&f);
}
void
engine_emoji_queries(struct ct *t)
{
Drawcmd dc = {0};
Emojifix f;
Str com;
emojibegin(&f, 1);
sclear(&com);
CT_CHECK(t, keystroke('e', Mctrl, &com));
if(typekeys(t, "smile", &com)){
checkstr(t, "English alias", "smile", &search.text);
CT_EQ_INT(t, 2, im.nkouho);
CT_CHECK(t, keystroke(Kback, 0, &com));
checkstr(t, "backspaced query", "smil", &search.text);
CT_EQ_INT(t, 2, im.nkouho);
CT_CHECK(t, keystroke('e', 0, &com));
CT_CHECK(t, keystroke('2', Malt, &com));
checkstr(t, "selected English alias", "😄", &com);
CT_EQ_INT(t, LangKO, im.l->lang);
}
sclear(&com);
CT_CHECK(t, keystroke('e', Mctrl, &com));
if(typekeys(t, "dntdma", &com)){
checkstr(t, "localized Korean query", "웃음", &search.text);
CT_EQ_INT(t, 3, im.nkouho);
CT_CHECK(t, draindraw(&dc) > 0);
checkstr(t, "popup query", "웃음", &dc.pre);
CT_EQ_INT(t, 3, dc.nkouho);
CT_CHECK(t, keystroke(Kesc, 0, &com));
checkstr(t, "cancel commit", "", &com);
CT_CHECK(t, !search.on);
CT_EQ_INT(t, LangKO, im.l->lang);
}
emojiend(&f);
}
void
engine_emoji_start_and_unknown(struct ct *t)
{
Emojifix f;
Str com;
emojibegin(&f, 0);
sclear(&com);
if(typekeys(t, "dkssud", &com)){
CT_CHECK(t, keystroke('e', Mctrl, &com));
checkstr(t, "committed Korean preedit", "안녕", &com);
CT_CHECK(t, search.on);
CT_EQ_INT(t, LangKO, im.l->lang);
if(typekeys(t, "xyz", &com)){
checkstr(t, "unknown displayed query", "xyz", &search.text);
CT_EQ_INT(t, 0, im.nkouho);
CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "unknown query commit", "안녕xyz", &com);
CT_CHECK(t, !search.on);
CT_EQ_INT(t, LangKO, im.l->lang);
}
}
emojiend(&f);
}

View File

@@ -33,6 +33,9 @@ void engine_selects_visible_candidate(struct ct*);
void engine_commit_contract(struct ct*);
void engine_language_switch_state(struct ct*);
void engine_telex_history_bound(struct ct*);
void engine_emoji_single_candidate(struct ct*);
void engine_emoji_queries(struct ct*);
void engine_emoji_start_and_unknown(struct ct*);
void dictionary_candidates(struct ct*);
void dictionary_misses_clear_result(struct ct*);

View File

@@ -80,6 +80,9 @@ static const struct ct_test tests[] = {
{ "engine/commit-contract", engine_commit_contract },
{ "engine/language-switch-state", engine_language_switch_state },
{ "engine/telex-history-bound", engine_telex_history_bound },
{ "engine/emoji-single-candidate", engine_emoji_single_candidate },
{ "engine/emoji-queries", engine_emoji_queries },
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
{ "dict/candidates", dictionary_candidates },
{ "dict/misses-clear-result", dictionary_misses_clear_result },
};
@@ -89,7 +92,7 @@ threadmain(int argc, char **argv)
{
int i, status;
drawc = chancreate(sizeof(Drawcmd), 4);
drawc = chancreate(sizeof(Drawcmd), 1024);
keyc = chancreate(sizeof(Keyreq), 0);
dictreqc = chancreate(sizeof(Dictreq), 64);
dictresc = chancreate(sizeof(Dictres), 0);

21
win.c
View File

@@ -90,16 +90,18 @@ fill(u32int *buf, int n, u32int color)
static void
drawkouho(Drawcmd *dc, int first, int n, int w, int h)
{
int sely, y, i;
int npre, sely, y, i;
Str *s;
fill(img, w * h, Colbg);
npre = dc->pre.n != 0;
if(npre)
drawstr(img, 0, 0, dc->pre.r, dc->pre.n, w, h);
if(dc->sel >= 0){
sely = Fontsz + dc->sel * Fontsz;
if(dc->sel >= 0 && dc->sel < n){
sely = (npre + dc->sel) * Fontsz;
fill(img + sely * w, Fontsz * w, Colsel);
}
for(i = 0, y = Fontsz; i < n; i++, y += Fontsz){
for(i = 0, y = npre * Fontsz; i < n; i++, y += Fontsz){
s = &dc->kouho[first+i];
putfont(img, w, h, 0, y, '1' + i + Asciitofull);
drawstr(img, 2*Fontsz, y, s->r, s->n, w, h);
@@ -124,13 +126,18 @@ winhide(void)
static void
winshow(Drawcmd *dc)
{
int px, py, w, h, i, n, maxw;
int npre, px, py, w, h, i, n, maxw;
u32int vals[4];
xcb_query_pointer_reply_t *ptr;
xcb_query_pointer_cookie_t cookie;
n = min(max(dc->nkouho, 0), Maxdisp);
npre = dc->pre.n != 0;
if(n == 0 && npre == 0){
winhide();
return;
}
cookie = xcb_query_pointer(conn, scr->root);
n = dc->nkouho;
maxw = dc->pre.n;
for(i = 0; i < n; i++)
maxw = max(maxw, dc->kouho[i].n);
@@ -140,7 +147,7 @@ winshow(Drawcmd *dc)
px = ptr->root_x + 10;
py = ptr->root_y + 10;
free(ptr);
vals[3] = h = (n + 1) * Fontsz;
vals[3] = h = (n + npre) * Fontsz;
vals[2] = w = (maxw + 3) * Fontsz;
vals[1] = py = max(0, min(py, scr->height_in_pixels - h));
vals[0] = px = max(0, min(px, scr->width_in_pixels - w));