Compare commits
2 Commits
82276bf37f
...
2ae87a3926
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ae87a3926 | |||
| 0fff17c8b8 |
2
Makefile
2
Makefile
@@ -42,5 +42,7 @@ test:
|
|||||||
|
|
||||||
verify-map:
|
verify-map:
|
||||||
python3 map/mktelex.py | cmp - map/telex.map
|
python3 map/mktelex.py | cmp - map/telex.map
|
||||||
|
python3 -B map/mkemoji | cmp - map/emoji.dict
|
||||||
|
python3 -B tests/mkemoji_test.py
|
||||||
|
|
||||||
.PHONY: all check test verify-map clean xim bench
|
.PHONY: all check test verify-map clean xim bench
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -29,9 +29,9 @@ mirrors must provide the same sibling; initialize it after cloning with
|
|||||||
|
|
||||||
The suite covers the fixed-size UTF-8 string, hash table, trie fixtures,
|
The suite covers the fixed-size UTF-8 string, hash table, trie fixtures,
|
||||||
Japanese/Hangul/Telex state transitions, dictionary candidates, engine
|
Japanese/Hangul/Telex state transitions, dictionary candidates, engine
|
||||||
selection state, and production map loading. Tests use explicit case tables,
|
selection state, IPC request fields, and production map loading. Tests use
|
||||||
boundary values, and small regressions for repaired core contracts. IPC and GUI
|
explicit case tables, boundary values, and small regressions for repaired core
|
||||||
protocol stacks are outside this suite and require separate integration checks.
|
contracts. Full IPC and GUI protocol stacks require separate integration checks.
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
@@ -66,11 +66,35 @@ Switch input modes with Ctrl + key:
|
|||||||
S Hangul
|
S Hangul
|
||||||
T English
|
T English
|
||||||
V Vietnamese (Telex)
|
V Vietnamese (Telex)
|
||||||
E Emoji
|
P Toggle preedit echo
|
||||||
P Toggle preedit/candidate popup window
|
|
||||||
|
|
||||||
Type romanized input. Select candidates with 1-9 or arrow keys.
|
Type romanized input. Select candidates with 1-9 or arrow keys.
|
||||||
Tab or Enter to commit.
|
Outside one-shot search, Tab or Enter commits.
|
||||||
|
|
||||||
|
### 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, and Esc cancels. Bare 1 through 9 select an existing
|
||||||
|
numbered row; a bare digit with no such row and 0 remain query text. Digit
|
||||||
|
aliases are kept in matching slots, so `^1`, `_2`, and `<3` produce `¹`, `₂`,
|
||||||
|
and `♥`.
|
||||||
|
|
||||||
|
`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 and ASCII
|
||||||
|
letters are case-insensitive. Results and aliases are limited to 64 Unicode
|
||||||
|
code points; results cannot contain whitespace. Run `map/mkemoji > map/emoji.dict`
|
||||||
|
after editing it; `make verify-map` checks the generated dictionary and the
|
||||||
|
Telex map.
|
||||||
|
|
||||||
|
😀 웃음 웃다 스마일 smile grin
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
|||||||
1
dat.h
1
dat.h
@@ -156,6 +156,7 @@ struct Dictres
|
|||||||
Str key;
|
Str key;
|
||||||
Str kouho[Maxkouho];
|
Str kouho[Maxkouho];
|
||||||
int nkouho;
|
int nkouho;
|
||||||
|
int lang;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Lang langs[];
|
extern Lang langs[];
|
||||||
|
|||||||
1
dict.c
1
dict.c
@@ -12,6 +12,7 @@ dictlookup(Dictreq *req, Dictres *res)
|
|||||||
|
|
||||||
res->key = req->pre;
|
res->key = req->pre;
|
||||||
res->nkouho = 0;
|
res->nkouho = 0;
|
||||||
|
res->lang = req->lang;
|
||||||
if(req->key.n == 0)
|
if(req->key.n == 0)
|
||||||
return;
|
return;
|
||||||
l = getlang(req->lang);
|
l = getlang(req->lang);
|
||||||
|
|||||||
2
ipc.c
2
ipc.c
@@ -38,7 +38,7 @@ ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, unsigned int *mod,
|
|||||||
unsigned int *key)
|
unsigned int *key)
|
||||||
{
|
{
|
||||||
*want = req[0] != 0;
|
*want = req[0] != 0;
|
||||||
*mod = req[1];
|
*mod = req[1] & Mmask;
|
||||||
*key = (unsigned int)req[2] |
|
*key = (unsigned int)req[2] |
|
||||||
((unsigned int)req[3] << 8) |
|
((unsigned int)req[3] << 8) |
|
||||||
((unsigned int)req[4] << 16) |
|
((unsigned int)req[4] << 16) |
|
||||||
|
|||||||
6
ipc.h
6
ipc.h
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Request: [want-preedit, modifiers, key byte 0, ..., key byte 3].
|
* Request: [want-preedit, modifiers, key byte 0, ..., key byte 3].
|
||||||
|
* Only Mmask modifier bits are retained.
|
||||||
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
||||||
* followed, when requested, by
|
* followed, when requested, by
|
||||||
* [preedit-length-low, preedit-length-high, preedit...].
|
* [preedit-length-low, preedit-length-high, preedit...].
|
||||||
@@ -22,13 +23,14 @@ enum
|
|||||||
Kesc = Kspec|0x1b,
|
Kesc = Kspec|0x1b,
|
||||||
Kup = Kspec|0x52,
|
Kup = Kspec|0x52,
|
||||||
Kdown = Kspec|0x54,
|
Kdown = Kspec|0x54,
|
||||||
Kshift = Kspec|0xe1,
|
Kmodfirst = Kspec|0xe1,
|
||||||
Ksuper = Kspec|0xeb,
|
Kmodlast = Kspec|0xee,
|
||||||
|
|
||||||
Mshift = 1<<0,
|
Mshift = 1<<0,
|
||||||
Mctrl = 1<<2,
|
Mctrl = 1<<2,
|
||||||
Malt = 1<<3,
|
Malt = 1<<3,
|
||||||
Msuper = 1<<6,
|
Msuper = 1<<6,
|
||||||
|
Mmask = Mshift|Mctrl|Malt|Msuper,
|
||||||
|
|
||||||
Ipcreqsz = 6,
|
Ipcreqsz = 6,
|
||||||
Ipclensz = 2,
|
Ipclensz = 2,
|
||||||
|
|||||||
226
map/emoji.dict
226
map/emoji.dict
@@ -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 ζ
|
||||||
|
~ ≈
|
||||||
|
~= ≈
|
||||||
|
따 👍
|
||||||
|
따봉 👍
|
||||||
|
미 😄 😊
|
||||||
|
미소 😄 😊
|
||||||
|
방 😊
|
||||||
|
방긋 😊
|
||||||
|
사 ❤️
|
||||||
|
사랑 ❤️
|
||||||
|
스 😀 😄
|
||||||
|
스마 😀 😄
|
||||||
|
스마일 😀 😄
|
||||||
|
엄 👍
|
||||||
|
엄지 👍
|
||||||
|
웃 😀 😄 😂
|
||||||
|
웃겨 😂
|
||||||
|
웃다 😀
|
||||||
|
웃음 😀 😄 😂
|
||||||
|
좋 👍
|
||||||
|
좋아 👍
|
||||||
|
좋아요 👍
|
||||||
|
찬 👍
|
||||||
|
찬성 👍
|
||||||
|
폭 😂
|
||||||
|
폭소 😂
|
||||||
|
하 ❤️
|
||||||
|
하트 ❤️
|
||||||
|
|||||||
183
map/emoji.map
183
map/emoji.map
@@ -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 ζ
|
|
||||||
~ ~
|
|
||||||
~= ≈
|
|
||||||
211
map/emoji.src
211
map/emoji.src
@@ -1,100 +1,111 @@
|
|||||||
!! ⚠
|
# Result first, followed by one or more TAB-separated aliases.
|
||||||
!= ≠
|
⚠ !!
|
||||||
** ★
|
★ **
|
||||||
+- ±
|
± +-
|
||||||
-> →
|
→ ->
|
||||||
.. ·
|
· ..
|
||||||
... …
|
… ...
|
||||||
./ ÷
|
÷ ./
|
||||||
:( ☹
|
☹ :(
|
||||||
:) ☺
|
☺ :)
|
||||||
<- ←
|
|
||||||
<= ≤
|
# Bare 1-9 choose prefix candidates; keep digit aliases in matching slots.
|
||||||
<> ≠
|
← <-
|
||||||
== ≡
|
≤ <=
|
||||||
=> ⇒
|
♥ <3
|
||||||
>= ≥
|
≠ <> !=
|
||||||
^( ⁽
|
≡ ==
|
||||||
^) ⁾
|
⇒ =>
|
||||||
^+ ⁺
|
≥ >=
|
||||||
^- ⁻
|
¹ ^1
|
||||||
^= ⁼
|
² ^2
|
||||||
_( ₍
|
³ ^3
|
||||||
_) ₎
|
⁴ ^4
|
||||||
_+ ₊
|
⁵ ^5
|
||||||
_- ₋
|
⁶ ^6
|
||||||
_= ₌
|
⁷ ^7
|
||||||
~= ≈
|
⁸ ^8
|
||||||
^0 ⁰
|
⁹ ^9
|
||||||
_0 ₀
|
₁ _1
|
||||||
^1 ¹
|
₂ _2
|
||||||
_1 ₁
|
₃ _3
|
||||||
^2 ²
|
₄ _4
|
||||||
_2 ₂
|
₅ _5
|
||||||
<3 ♥
|
₆ _6
|
||||||
^3 ³
|
₇ _7
|
||||||
_3 ₃
|
₈ _8
|
||||||
^4 ⁴
|
₉ _9
|
||||||
_4 ₄
|
|
||||||
^5 ⁵
|
⁽ ^(
|
||||||
_5 ₅
|
⁾ ^)
|
||||||
^6 ⁶
|
⁺ ^+
|
||||||
_6 ₆
|
⁻ ^-
|
||||||
^7 ⁷
|
⁼ ^=
|
||||||
_7 ₇
|
₍ _(
|
||||||
^8 ⁸
|
₎ _)
|
||||||
_8 ₈
|
₊ _+
|
||||||
^9 ⁹
|
₋ _-
|
||||||
_9 ₉
|
₌ _=
|
||||||
_a ₐ
|
≈ ~=
|
||||||
alpha α
|
⁰ ^0
|
||||||
beta β
|
₀ _0
|
||||||
chi χ
|
ₐ _a
|
||||||
deg °
|
α alpha
|
||||||
delta δ
|
β beta
|
||||||
De Δ
|
χ chi
|
||||||
dn ↓
|
° deg
|
||||||
_e ₑ
|
δ delta
|
||||||
eps ε
|
Δ De
|
||||||
eta η
|
↓ dn
|
||||||
fuck 凸
|
ₑ _e
|
||||||
gamma γ
|
ε eps
|
||||||
Ga Γ
|
η eta
|
||||||
^i ⁱ
|
凸 fuck
|
||||||
II ∫
|
γ gamma
|
||||||
inf ∞
|
Γ Ga
|
||||||
iota ι
|
ⁱ ^i
|
||||||
kappa κ
|
∫ II
|
||||||
lambda λ
|
∞ inf
|
||||||
La Λ
|
ι iota
|
||||||
mul ×
|
κ kappa
|
||||||
mu μ
|
λ lambda
|
||||||
^n ⁿ
|
Λ La
|
||||||
nu ν
|
× mul
|
||||||
omega ω
|
μ mu
|
||||||
Om Ω
|
ⁿ ^n
|
||||||
oo ●
|
ν nu
|
||||||
_o ₒ
|
ω omega
|
||||||
phi φ
|
Ω Om
|
||||||
Ph Φ
|
● oo
|
||||||
pi π
|
ₒ _o
|
||||||
Pi Π
|
φ phi
|
||||||
PP ∏
|
Φ Ph
|
||||||
psi ψ
|
π pi
|
||||||
Ps Ψ
|
Π Pi
|
||||||
rho ρ
|
∏ PP
|
||||||
sigma σ
|
ψ psi
|
||||||
Si Σ
|
Ψ Ps
|
||||||
sq √
|
ρ rho
|
||||||
SS ∑
|
σ sigma
|
||||||
tau τ
|
Σ Si
|
||||||
theta θ
|
√ sq
|
||||||
Th Θ
|
∑ SS
|
||||||
up ↑
|
τ tau
|
||||||
ups υ
|
θ theta
|
||||||
vv ✓
|
Θ Th
|
||||||
xi ξ
|
↑ up
|
||||||
Xi Ξ
|
υ ups
|
||||||
xx ✗
|
✓ vv
|
||||||
_x ₓ
|
ξ xi
|
||||||
zeta ζ
|
Ξ 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
|
||||||
|
|||||||
115
map/mkemoji
115
map/mkemoji
@@ -1,39 +1,80 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env python3
|
||||||
|
"""Write emoji.dict to stdout from a result-first UTF-8 TSV file."""
|
||||||
|
|
||||||
awk '
|
import sys
|
||||||
BEGIN { FS = "\t" }
|
import unicodedata
|
||||||
/^$/ { next }
|
from pathlib import Path
|
||||||
{
|
|
||||||
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
|
|
||||||
|
|
||||||
awk '
|
|
||||||
BEGIN { FS = "\t" }
|
SOURCE = Path(__file__).with_name("emoji.src")
|
||||||
/^$/ { next }
|
MAXRUNES = 64
|
||||||
{
|
|
||||||
key = $1
|
|
||||||
val = $2
|
def fold(s):
|
||||||
for(i = 1; i <= length(key); i++){
|
s = "".join(chr(ord(c) + 32) if "A" <= c <= "Z" else c for c in s)
|
||||||
p = substr(key, 1, i)
|
return unicodedata.normalize("NFC", s)
|
||||||
dict[p] = dict[p] " " val
|
|
||||||
}
|
|
||||||
}
|
def hascontrol(s):
|
||||||
END {
|
return any(unicodedata.category(c) == "Cc" for c in s)
|
||||||
for(p in dict){
|
|
||||||
sub(/^ /, "", dict[p])
|
|
||||||
print p "\t" dict[p]
|
def read(path):
|
||||||
}
|
entries = []
|
||||||
}
|
with path.open(encoding="utf-8") as src:
|
||||||
' $1 | sort -k1,1 > emoji.dict
|
for lineno, raw in enumerate(src, 1):
|
||||||
|
line = raw.rstrip("\r\n")
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
if "\t" not in line and 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 len(result) > MAXRUNES or hascontrol(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 len(alias) > MAXRUNES or hascontrol(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())
|
||||||
|
|||||||
309
strans.c
309
strans.c
@@ -3,8 +3,18 @@
|
|||||||
|
|
||||||
static Im im;
|
static Im im;
|
||||||
static int popup = 0;
|
static int popup = 0;
|
||||||
|
static int visible = 0;
|
||||||
static void dictqmap(Im*);
|
static void dictqmap(Im*);
|
||||||
|
|
||||||
|
typedef struct Search Search;
|
||||||
|
struct Search
|
||||||
|
{
|
||||||
|
int on;
|
||||||
|
Str raw;
|
||||||
|
Str text;
|
||||||
|
};
|
||||||
|
static Search search;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
backmap(Im *im)
|
backmap(Im *im)
|
||||||
{
|
{
|
||||||
@@ -16,7 +26,7 @@ Lang langs[] = {
|
|||||||
{LangJP, "hira", "kanji", transmap, backmap, dictqmap, nil, nil},
|
{LangJP, "hira", "kanji", transmap, backmap, dictqmap, nil, nil},
|
||||||
{LangJPK, "kata", "kanji", transmap, backmap, dictqmap, nil, nil},
|
{LangJPK, "kata", "kanji", transmap, backmap, dictqmap, nil, nil},
|
||||||
{LangKO, "hangul", nil, transko, backko, 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},
|
{LangVI, "telex", nil, transvi, backvi, dictqmap, nil, nil},
|
||||||
};
|
};
|
||||||
int nlang = nelem(langs);
|
int nlang = nelem(langs);
|
||||||
@@ -43,14 +53,17 @@ setkouho(Dictres *res)
|
|||||||
static void
|
static void
|
||||||
show(void)
|
show(void)
|
||||||
{
|
{
|
||||||
|
static Drawcmd old;
|
||||||
Drawcmd dc;
|
Drawcmd dc;
|
||||||
|
Str *pre;
|
||||||
int i, first, n;
|
int i, first, n;
|
||||||
|
|
||||||
if(!popup)
|
|
||||||
return;
|
|
||||||
sclear(&dc.pre);
|
sclear(&dc.pre);
|
||||||
if(!mapget(im.l->map, &im.pre, &dc.pre))
|
pre = search.on ? &search.text : &im.pre;
|
||||||
dc.pre = 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;
|
first = im.sel >= Maxdisp ? im.sel - Maxdisp + 1 : 0;
|
||||||
n = im.nkouho - first;
|
n = im.nkouho - first;
|
||||||
if(n > Maxdisp) n = Maxdisp;
|
if(n > Maxdisp) n = Maxdisp;
|
||||||
@@ -58,7 +71,13 @@ show(void)
|
|||||||
dc.sel = im.sel >= 0 ? im.sel - first : -1;
|
dc.sel = im.sel >= 0 ? im.sel - first : -1;
|
||||||
for(i = 0; i < n; i++)
|
for(i = 0; i < n; i++)
|
||||||
dc.kouho[i] = im.kouho[first + i];
|
dc.kouho[i] = im.kouho[first + i];
|
||||||
chansend(drawc, &dc);
|
if(dc.pre.n == 0 && n == 0 && !visible)
|
||||||
|
return;
|
||||||
|
visible = dc.pre.n != 0 || n != 0;
|
||||||
|
/* imthread is the sole producer; keep only the newest pending draw. */
|
||||||
|
while(channbrecv(drawc, &old) > 0)
|
||||||
|
;
|
||||||
|
channbsend(drawc, &dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -110,7 +129,7 @@ commit(Str *com)
|
|||||||
{
|
{
|
||||||
Str val;
|
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);
|
sappend(com, &val);
|
||||||
else
|
else
|
||||||
sappend(com, &im.pre);
|
sappend(com, &im.pre);
|
||||||
@@ -209,13 +228,248 @@ transmap(Im *im, Rune c)
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Rune
|
||||||
|
ctrlkey(Rune c)
|
||||||
|
{
|
||||||
|
if(c >= 'A' && c <= 'Z')
|
||||||
|
c += 'a' - 'A';
|
||||||
|
if(c >= 'a' && c <= 'z')
|
||||||
|
return c - 'a' + 1;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
ismodkey(u32int ks)
|
||||||
|
{
|
||||||
|
return ks >= Kmodfirst && ks <= Kmodlast;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(ismodkey(ks))
|
||||||
|
return 0;
|
||||||
|
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(n >= 0 && n < Maxdisp && off + n < im.nkouho && mod == 0){
|
||||||
|
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
|
static int
|
||||||
keystroke(u32int ks, u32int mod, Str *com)
|
keystroke(u32int ks, u32int mod, Str *com)
|
||||||
{
|
{
|
||||||
int n, off;
|
int n, off;
|
||||||
|
Rune c;
|
||||||
|
|
||||||
if(ks == Ksuper || ks == Kshift)
|
if(ismodkey(ks))
|
||||||
return 1;
|
return 0;
|
||||||
|
if(search.on)
|
||||||
|
return searchkey(ks, mod, com);
|
||||||
if(ks == Kdown || ks == Kup){
|
if(ks == Kdown || ks == Kup){
|
||||||
if(im.nkouho == 0)
|
if(im.nkouho == 0)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -271,14 +525,18 @@ keystroke(u32int ks, u32int mod, Str *com)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(mod & Mctrl){
|
if(mod & Mctrl){
|
||||||
reset();
|
c = ctrlkey(ks);
|
||||||
if(ks >= 'a' && ks <= 'z')
|
if(c == LangEMOJI){
|
||||||
ks -= 'a' - 1;
|
startsearch(com);
|
||||||
if(ks == 0x10){
|
|
||||||
popup = !popup;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(setlang(ks))
|
if(c == 0x10){
|
||||||
|
popup = !popup;
|
||||||
|
show();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
if(setlang(c))
|
||||||
return 1;
|
return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -305,6 +563,8 @@ init(void)
|
|||||||
{
|
{
|
||||||
memset(&im, 0, sizeof(im));
|
memset(&im, 0, sizeof(im));
|
||||||
im.l = getlang(LangEN);
|
im.l = getlang(LangEN);
|
||||||
|
memset(&search, 0, sizeof search);
|
||||||
|
visible = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -316,15 +576,25 @@ imhandlekey(Keyreq *kr)
|
|||||||
sclear(&res.preedit);
|
sclear(&res.preedit);
|
||||||
res.eaten = keystroke(kr->ks, kr->mod, &res.commit);
|
res.eaten = keystroke(kr->ks, kr->mod, &res.commit);
|
||||||
if(kr->want)
|
if(kr->want)
|
||||||
res.preedit = im.pre;
|
res.preedit = search.on ? search.text : im.pre;
|
||||||
chansend(kr->reply, &res);
|
chansend(kr->reply, &res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dictresult(Dictres *res)
|
||||||
|
{
|
||||||
|
if(search.on || res->lang != im.l->lang ||
|
||||||
|
scmp(&res->key, &im.pre) != 0)
|
||||||
|
return;
|
||||||
|
setkouho(res);
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
imthread(void*)
|
imthread(void*)
|
||||||
{
|
{
|
||||||
|
static Dictres res;
|
||||||
Keyreq kr;
|
Keyreq kr;
|
||||||
Dictres res;
|
|
||||||
Alt alts[] = {
|
Alt alts[] = {
|
||||||
{keyc, &kr, CHANRCV, nil},
|
{keyc, &kr, CHANRCV, nil},
|
||||||
{dictresc, &res, CHANRCV, nil},
|
{dictresc, &res, CHANRCV, nil},
|
||||||
@@ -339,10 +609,7 @@ imthread(void*)
|
|||||||
imhandlekey(&kr);
|
imhandlekey(&kr);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if(scmp(&res.key, &im.pre) == 0){
|
dictresult(&res);
|
||||||
setkouho(&res);
|
|
||||||
show();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ LIBS = -lthread -lbio
|
|||||||
|
|
||||||
PROG = unit_test
|
PROG = unit_test
|
||||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c
|
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c
|
||||||
TESTOBJ = $(TESTSRC:.c=.o)
|
TESTOBJ = $(TESTSRC:.c=.o)
|
||||||
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c
|
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||||
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ dictionary_candidates(struct ct *t)
|
|||||||
dictlookup(&req, &res);
|
dictlookup(&req, &res);
|
||||||
if(!CT_EQ_INT(t, 2, res.nkouho))
|
if(!CT_EQ_INT(t, 2, res.nkouho))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
CT_EQ_INT(t, LangJP, res.lang);
|
||||||
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
||||||
checkstr(t, "candidate 1", "候補1", &res.kouho[0]);
|
checkstr(t, "candidate 1", "候補1", &res.kouho[0]);
|
||||||
checkstr(t, "candidate 2", "候補2", &res.kouho[1]);
|
checkstr(t, "candidate 2", "候補2", &res.kouho[1]);
|
||||||
@@ -44,6 +45,7 @@ dictionary_candidates(struct ct *t)
|
|||||||
dictlookup(&req, &res);
|
dictlookup(&req, &res);
|
||||||
if(!CT_EQ_INT(t, Maxkouho, res.nkouho))
|
if(!CT_EQ_INT(t, Maxkouho, res.nkouho))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
CT_EQ_INT(t, LangJP, res.lang);
|
||||||
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
||||||
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
|
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
|
||||||
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
|
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
|
||||||
@@ -81,6 +83,7 @@ dictionary_misses_clear_result(struct ct *t)
|
|||||||
if(res.nkouho != 0)
|
if(res.nkouho != 0)
|
||||||
CT_ERRORF(t, "%s: want 0 candidates, got %d",
|
CT_ERRORF(t, "%s: want 0 candidates, got %d",
|
||||||
cases[i].name, res.nkouho);
|
cases[i].name, res.nkouho);
|
||||||
|
CT_EQ_INT(t, LangJP, res.lang);
|
||||||
checkstr(t, cases[i].name, cases[i].pre, &res.key);
|
checkstr(t, cases[i].name, cases[i].pre, &res.key);
|
||||||
}
|
}
|
||||||
hmapfree(lang->dict);
|
hmapfree(lang->dict);
|
||||||
|
|||||||
@@ -255,3 +255,449 @@ engine_language_switch_state(struct ct *t)
|
|||||||
}
|
}
|
||||||
vi->map = saved;
|
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(32);
|
||||||
|
setemoji(&l->dict, "a", "A B");
|
||||||
|
setemoji(&l->dict, "ㅁ", "B C");
|
||||||
|
setemoji(&l->dict, "alpha", "α");
|
||||||
|
setemoji(&l->dict, "smil", "😀 😄");
|
||||||
|
setemoji(&l->dict, "smile", "😀 😄");
|
||||||
|
setemoji(&l->dict, "웃음", "😀 😄 😂");
|
||||||
|
setemoji(&l->dict, "^", "¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁽");
|
||||||
|
setemoji(&l->dict, "^0", "⁰");
|
||||||
|
setemoji(&l->dict, "_", "₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₍");
|
||||||
|
setemoji(&l->dict, "<", "← ≤ ♥ ≠");
|
||||||
|
setemoji(&l->dict, "many",
|
||||||
|
"c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12");
|
||||||
|
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, 1);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('p', Mctrl, &com));
|
||||||
|
CT_EQ_INT(t, 0, popup);
|
||||||
|
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);
|
||||||
|
show();
|
||||||
|
CT_CHECK(t, draindraw(&dc) > 0);
|
||||||
|
CT_EQ_INT(t, 0, dc.pre.n);
|
||||||
|
CT_EQ_INT(t, 0, dc.nkouho);
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
CT_CHECK(t, keystroke('a', 0, &com));
|
||||||
|
checkstr(t, "raw query wins", "a", &search.text);
|
||||||
|
CT_EQ_INT(t, 3, im.nkouho);
|
||||||
|
checkstr(t, "raw candidate", "A", &im.kouho[0]);
|
||||||
|
checkstr(t, "deduplicated candidate", "B", &im.kouho[1]);
|
||||||
|
checkstr(t, "local candidate", "C", &im.kouho[2]);
|
||||||
|
CT_CHECK(t, draindraw(&dc) > 0);
|
||||||
|
checkstr(t, "prefix popup query", "a", &dc.pre);
|
||||||
|
CT_EQ_INT(t, 3, dc.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &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', 0, &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, "SMILE", &com)){
|
||||||
|
checkstr(t, "folded English alias", "smile", &search.text);
|
||||||
|
CT_EQ_INT(t, 2, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
}
|
||||||
|
|
||||||
|
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_digit_aliases(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
char *name;
|
||||||
|
Rune prefix;
|
||||||
|
Rune digit;
|
||||||
|
char *want;
|
||||||
|
} cases[] = {
|
||||||
|
{ "^1", '^', '1', "¹" },
|
||||||
|
{ "_2", '_', '2', "₂" },
|
||||||
|
{ "<3", '<', '3', "♥" },
|
||||||
|
};
|
||||||
|
Emojifix f;
|
||||||
|
Str com;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
emojibegin(&f, 0);
|
||||||
|
for(i = 0; i < nelem(cases); i++){
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
CT_CHECK(t, keystroke(cases[i].prefix, Mshift, &com));
|
||||||
|
CT_CHECK(t, im.nkouho >= cases[i].digit - '0');
|
||||||
|
CT_CHECK(t, keystroke(cases[i].digit, 0, &com));
|
||||||
|
checkstr(t, cases[i].name, cases[i].want, &com);
|
||||||
|
CT_CHECK(t, !search.on);
|
||||||
|
CT_EQ_INT(t, LangKO, im.l->lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
CT_CHECK(t, keystroke('^', Mshift, &com));
|
||||||
|
CT_CHECK(t, keystroke('0', 0, &com));
|
||||||
|
checkstr(t, "zero query", "^0", &search.text);
|
||||||
|
checkstr(t, "zero does not commit", "", &com);
|
||||||
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "zero alias", "⁰", &com);
|
||||||
|
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
if(typekeys(t, "smile", &com)){
|
||||||
|
CT_CHECK(t, keystroke('9', 0, &com));
|
||||||
|
checkstr(t, "missing number query", "smile9", &search.text);
|
||||||
|
checkstr(t, "missing number commit", "", &com);
|
||||||
|
CT_CHECK(t, search.on);
|
||||||
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "missing number text", "smile9", &com);
|
||||||
|
}
|
||||||
|
emojiend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_emoji_navigation(struct ct *t)
|
||||||
|
{
|
||||||
|
Drawcmd dc = {0};
|
||||||
|
Emojifix f;
|
||||||
|
Str com;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
emojibegin(&f, 0);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
if(typekeys(t, "many", &com)){
|
||||||
|
CT_EQ_INT(t, 12, im.nkouho);
|
||||||
|
for(i = 0; i < Maxdisp+1; i++)
|
||||||
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
|
CT_EQ_INT(t, Maxdisp, im.sel);
|
||||||
|
CT_CHECK(t, draindraw(&dc) > 0);
|
||||||
|
checkstr(t, "scrolled first row", "c2", &dc.kouho[0]);
|
||||||
|
checkstr(t, "scrolled ninth row", "c10", &dc.kouho[8]);
|
||||||
|
CT_EQ_INT(t, 8, dc.sel);
|
||||||
|
CT_CHECK(t, keystroke('1', 0, &com));
|
||||||
|
checkstr(t, "scrolled row one", "c2", &com);
|
||||||
|
}
|
||||||
|
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
if(typekeys(t, "many", &com)){
|
||||||
|
for(i = 0; i < 12; i++)
|
||||||
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
|
CT_EQ_INT(t, 11, im.sel);
|
||||||
|
CT_CHECK(t, keystroke('9', 0, &com));
|
||||||
|
checkstr(t, "scrolled row nine", "c12", &com);
|
||||||
|
}
|
||||||
|
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
if(typekeys(t, "smile", &com)){
|
||||||
|
CT_CHECK(t, keystroke(Ktab, 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
|
CT_EQ_INT(t, 1, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Kup, 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Ktab, Mshift, &com));
|
||||||
|
CT_EQ_INT(t, 1, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "navigated candidate", "😄", &com);
|
||||||
|
}
|
||||||
|
emojiend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_emoji_preedit_languages(struct ct *t)
|
||||||
|
{
|
||||||
|
Emojifix f;
|
||||||
|
Str com, shown;
|
||||||
|
int p;
|
||||||
|
|
||||||
|
emojibegin(&f, 0);
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "ka", &com)){
|
||||||
|
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
|
||||||
|
checkstr(t, "modifier preserves Japanese", "ka", &im.pre);
|
||||||
|
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
|
||||||
|
checkstr(t, "committed Japanese preedit", "か", &com);
|
||||||
|
CT_CHECK(t, search.on);
|
||||||
|
CT_EQ_INT(t, LangJP, im.l->lang);
|
||||||
|
CT_CHECK(t, keystroke('a', 0, &com));
|
||||||
|
p = popup;
|
||||||
|
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
|
||||||
|
CT_CHECK(t, search.on);
|
||||||
|
checkstr(t, "modifier preserves search", "a", &search.text);
|
||||||
|
CT_CHECK(t, keystroke('P', Mctrl|Mshift, &com));
|
||||||
|
CT_EQ_INT(t, !p, popup);
|
||||||
|
CT_CHECK(t, search.on);
|
||||||
|
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
|
||||||
|
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
|
||||||
|
CT_CHECK(t, !search.on);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "na", &com)){
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "Japanese resumed", "な", &shown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangKO);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "rk", &com)){
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
checkstr(t, "committed Korean preedit", "가", &com);
|
||||||
|
if(typekeys(t, "smile", &com)){
|
||||||
|
CT_CHECK(t, keystroke('1', 0, &com));
|
||||||
|
checkstr(t, "Korean selection", "가😀", &com);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "rk", &com))
|
||||||
|
checkstr(t, "Korean resumed", "가", &im.pre);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = &testvi;
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "as", &com)){
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
checkstr(t, "committed Telex preedit", "á", &com);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
CT_CHECK(t, !search.on);
|
||||||
|
CT_EQ_PTR(t, &testvi, im.l);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "as", &com)){
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "Telex resumed", "á", &shown);
|
||||||
|
checkstr(t, "Telex raw resumed", "as", &im.raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emojiend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_emoji_start_and_unknown(struct ct *t)
|
||||||
|
{
|
||||||
|
Emojifix f;
|
||||||
|
Str com;
|
||||||
|
char want[Maxrunes+1];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
if(typekeys(t, "xyz", &com)){
|
||||||
|
CT_CHECK(t, !keystroke(Kspec|0x51, 0, &com));
|
||||||
|
checkstr(t, "special commits shown query", "xyz", &com);
|
||||||
|
CT_CHECK(t, !search.on);
|
||||||
|
CT_EQ_INT(t, LangKO, im.l->lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
for(i = 0; i < Maxrunes; i++)
|
||||||
|
CT_CHECK(t, keystroke('q', 0, &com));
|
||||||
|
CT_EQ_INT(t, Maxrunes, search.raw.n);
|
||||||
|
CT_CHECK(t, search.on);
|
||||||
|
CT_CHECK(t, !keystroke('z', 0, &com));
|
||||||
|
memset(want, 'q', Maxrunes);
|
||||||
|
want[Maxrunes] = '\0';
|
||||||
|
checkstr(t, "bounded query commit", want, &com);
|
||||||
|
CT_CHECK(t, !search.on);
|
||||||
|
emojiend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_emoji_dictionary_identity(struct ct *t)
|
||||||
|
{
|
||||||
|
Dictres res;
|
||||||
|
Emojifix f;
|
||||||
|
|
||||||
|
emojibegin(&f, 0);
|
||||||
|
im.pre = mkstr("same");
|
||||||
|
im.kouho[0] = mkstr("old");
|
||||||
|
im.nkouho = 1;
|
||||||
|
im.sel = 0;
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
res.key = im.pre;
|
||||||
|
res.kouho[0] = mkstr("wrong");
|
||||||
|
res.nkouho = 1;
|
||||||
|
res.lang = LangJP;
|
||||||
|
dictresult(&res);
|
||||||
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
|
checkstr(t, "wrong language ignored", "old", &im.kouho[0]);
|
||||||
|
|
||||||
|
res.lang = LangKO;
|
||||||
|
res.key = mkstr("other");
|
||||||
|
dictresult(&res);
|
||||||
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
|
checkstr(t, "wrong preedit ignored", "old", &im.kouho[0]);
|
||||||
|
|
||||||
|
res.key = im.pre;
|
||||||
|
search.on = 1;
|
||||||
|
dictresult(&res);
|
||||||
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
|
checkstr(t, "search response ignored", "old", &im.kouho[0]);
|
||||||
|
|
||||||
|
search.on = 0;
|
||||||
|
res.kouho[0] = mkstr("right");
|
||||||
|
dictresult(&res);
|
||||||
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
|
CT_EQ_INT(t, -1, im.sel);
|
||||||
|
checkstr(t, "matching response accepted", "right", &im.kouho[0]);
|
||||||
|
emojiend(&f);
|
||||||
|
}
|
||||||
|
|||||||
15
tests/ipc_test.c
Normal file
15
tests/ipc_test.c
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
ipc_masks_modifiers(struct ct *t)
|
||||||
|
{
|
||||||
|
uchar buf[Ipcreqsz];
|
||||||
|
u32int key, mod;
|
||||||
|
int want;
|
||||||
|
|
||||||
|
ipcpackreq(buf, 1, Mctrl|Malt|(1<<1)|(1<<4)|(1<<7), 0x1f642);
|
||||||
|
ipcunpackreq(buf, &want, &mod, &key);
|
||||||
|
CT_EQ_INT(t, 1, want);
|
||||||
|
CT_EQ_UINT(t, Mctrl|Malt, mod);
|
||||||
|
CT_EQ_UINT(t, 0x1f642, key);
|
||||||
|
}
|
||||||
85
tests/mkemoji_test.py
Normal file
85
tests/mkemoji_test.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
MKEMOJI = ROOT / "map" / "mkemoji"
|
||||||
|
|
||||||
|
|
||||||
|
def generate(source=None):
|
||||||
|
args = [sys.executable, "-B", str(MKEMOJI)]
|
||||||
|
if source is not None:
|
||||||
|
args.append(str(source))
|
||||||
|
return subprocess.run(args, capture_output=True, text=True, check=False)
|
||||||
|
|
||||||
|
|
||||||
|
def table(output):
|
||||||
|
return dict(line.split("\t", 1) for line in output.splitlines())
|
||||||
|
|
||||||
|
|
||||||
|
class MkemojiTest(unittest.TestCase):
|
||||||
|
def source(self, text):
|
||||||
|
tmp = tempfile.TemporaryDirectory()
|
||||||
|
path = Path(tmp.name) / "emoji.src"
|
||||||
|
path.write_text(text, encoding="utf-8")
|
||||||
|
self.addCleanup(tmp.cleanup)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def test_production_digit_slots(self):
|
||||||
|
result = generate()
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
data = table(result.stdout)
|
||||||
|
self.assertEqual(data["^"].split()[:9], list("¹²³⁴⁵⁶⁷⁸⁹"))
|
||||||
|
self.assertEqual(data["_"].split()[:9], list("₁₂₃₄₅₆₇₈₉"))
|
||||||
|
self.assertEqual(data["<"].split()[2], "♥")
|
||||||
|
self.assertEqual(data["^1"], "¹")
|
||||||
|
self.assertEqual(data["_2"], "₂")
|
||||||
|
self.assertEqual(data["<3"], "♥")
|
||||||
|
|
||||||
|
def test_fold_normalize_and_exact_first(self):
|
||||||
|
source = self.source(
|
||||||
|
"β\tALPHABET\n"
|
||||||
|
"α\talpha\n"
|
||||||
|
"e\u0301\tE\u0301\n"
|
||||||
|
"#\thash\n"
|
||||||
|
)
|
||||||
|
first = generate(source)
|
||||||
|
second = generate(source)
|
||||||
|
self.assertEqual(first.returncode, 0, first.stderr)
|
||||||
|
self.assertEqual(first.stdout, second.stdout)
|
||||||
|
data = table(first.stdout)
|
||||||
|
self.assertEqual(data["alpha"].split(), ["α", "β"])
|
||||||
|
self.assertEqual(data["al"].split(), ["β", "α"])
|
||||||
|
self.assertEqual(data["é"], "é")
|
||||||
|
self.assertEqual(data["hash"], "#")
|
||||||
|
|
||||||
|
def test_rejects_malformed_and_oversized_rows(self):
|
||||||
|
bad = [
|
||||||
|
"x\n",
|
||||||
|
"bad result\ta\n",
|
||||||
|
"x\t\n",
|
||||||
|
"x\t a\n",
|
||||||
|
"\0\ta\n",
|
||||||
|
"x\ta\0b\n",
|
||||||
|
"x" * 65 + "\ta\n",
|
||||||
|
"x\t" + "a" * 65 + "\n",
|
||||||
|
]
|
||||||
|
for text in bad:
|
||||||
|
with self.subTest(text=repr(text)):
|
||||||
|
result = generate(self.source(text))
|
||||||
|
self.assertEqual(result.returncode, 1)
|
||||||
|
|
||||||
|
def test_accepts_runtime_boundary(self):
|
||||||
|
source = self.source("x" * 64 + "\t" + "a" * 64 + "\n")
|
||||||
|
result = generate(source)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertIn("a" * 64 + "\t" + "x" * 64, result.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -33,7 +33,15 @@ void engine_selects_visible_candidate(struct ct*);
|
|||||||
void engine_commit_contract(struct ct*);
|
void engine_commit_contract(struct ct*);
|
||||||
void engine_language_switch_state(struct ct*);
|
void engine_language_switch_state(struct ct*);
|
||||||
void engine_telex_history_bound(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_digit_aliases(struct ct*);
|
||||||
|
void engine_emoji_navigation(struct ct*);
|
||||||
|
void engine_emoji_preedit_languages(struct ct*);
|
||||||
|
void engine_emoji_start_and_unknown(struct ct*);
|
||||||
|
void engine_emoji_dictionary_identity(struct ct*);
|
||||||
void dictionary_candidates(struct ct*);
|
void dictionary_candidates(struct ct*);
|
||||||
void dictionary_misses_clear_result(struct ct*);
|
void dictionary_misses_clear_result(struct ct*);
|
||||||
|
void ipc_masks_modifiers(struct ct*);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -80,8 +80,16 @@ static const struct ct_test tests[] = {
|
|||||||
{ "engine/commit-contract", engine_commit_contract },
|
{ "engine/commit-contract", engine_commit_contract },
|
||||||
{ "engine/language-switch-state", engine_language_switch_state },
|
{ "engine/language-switch-state", engine_language_switch_state },
|
||||||
{ "engine/telex-history-bound", engine_telex_history_bound },
|
{ "engine/telex-history-bound", engine_telex_history_bound },
|
||||||
|
{ "engine/emoji-single-candidate", engine_emoji_single_candidate },
|
||||||
|
{ "engine/emoji-queries", engine_emoji_queries },
|
||||||
|
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
||||||
|
{ "engine/emoji-navigation", engine_emoji_navigation },
|
||||||
|
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
||||||
|
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
||||||
|
{ "engine/emoji-dictionary-identity", engine_emoji_dictionary_identity },
|
||||||
{ "dict/candidates", dictionary_candidates },
|
{ "dict/candidates", dictionary_candidates },
|
||||||
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
||||||
|
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
23
win.c
23
win.c
@@ -90,16 +90,18 @@ fill(u32int *buf, int n, u32int color)
|
|||||||
static void
|
static void
|
||||||
drawkouho(Drawcmd *dc, int first, int n, int w, int h)
|
drawkouho(Drawcmd *dc, int first, int n, int w, int h)
|
||||||
{
|
{
|
||||||
int sely, y, i;
|
int npre, sely, y, i;
|
||||||
Str *s;
|
Str *s;
|
||||||
|
|
||||||
fill(img, w * h, Colbg);
|
fill(img, w * h, Colbg);
|
||||||
drawstr(img, 0, 0, dc->pre.r, dc->pre.n, w, h);
|
npre = dc->pre.n != 0;
|
||||||
if(dc->sel >= 0){
|
if(npre)
|
||||||
sely = Fontsz + dc->sel * Fontsz;
|
drawstr(img, 0, 0, dc->pre.r, dc->pre.n, w, h);
|
||||||
|
if(dc->sel >= 0 && dc->sel < n){
|
||||||
|
sely = (npre + dc->sel) * Fontsz;
|
||||||
fill(img + sely * w, Fontsz * w, Colsel);
|
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];
|
s = &dc->kouho[first+i];
|
||||||
putfont(img, w, h, 0, y, '1' + i + Asciitofull);
|
putfont(img, w, h, 0, y, '1' + i + Asciitofull);
|
||||||
drawstr(img, 2*Fontsz, y, s->r, s->n, w, h);
|
drawstr(img, 2*Fontsz, y, s->r, s->n, w, h);
|
||||||
@@ -124,13 +126,18 @@ winhide(void)
|
|||||||
static void
|
static void
|
||||||
winshow(Drawcmd *dc)
|
winshow(Drawcmd *dc)
|
||||||
{
|
{
|
||||||
int px, py, w, h, i, n, maxw;
|
int npre, px, py, w, h, i, n, maxw;
|
||||||
u32int vals[4];
|
u32int vals[4];
|
||||||
xcb_query_pointer_reply_t *ptr;
|
xcb_query_pointer_reply_t *ptr;
|
||||||
xcb_query_pointer_cookie_t cookie;
|
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);
|
cookie = xcb_query_pointer(conn, scr->root);
|
||||||
n = dc->nkouho;
|
|
||||||
maxw = dc->pre.n;
|
maxw = dc->pre.n;
|
||||||
for(i = 0; i < n; i++)
|
for(i = 0; i < n; i++)
|
||||||
maxw = max(maxw, dc->kouho[i].n);
|
maxw = max(maxw, dc->kouho[i].n);
|
||||||
@@ -140,7 +147,7 @@ winshow(Drawcmd *dc)
|
|||||||
px = ptr->root_x + 10;
|
px = ptr->root_x + 10;
|
||||||
py = ptr->root_y + 10;
|
py = ptr->root_y + 10;
|
||||||
free(ptr);
|
free(ptr);
|
||||||
vals[3] = h = (n + 1) * Fontsz;
|
vals[3] = h = (n + npre) * Fontsz;
|
||||||
vals[2] = w = (maxw + 3) * Fontsz;
|
vals[2] = w = (maxw + 3) * Fontsz;
|
||||||
vals[1] = py = max(0, min(py, scr->height_in_pixels - h));
|
vals[1] = py = max(0, min(py, scr->height_in_pixels - h));
|
||||||
vals[0] = px = max(0, min(px, scr->width_in_pixels - w));
|
vals[0] = px = max(0, min(px, scr->width_in_pixels - w));
|
||||||
|
|||||||
Reference in New Issue
Block a user