1
0
Fork 0

Easier contraction typing (#289)

* removed isPunctiationPartOfWords hack and created a new contraction input method that allows typing just anything, instead of predefined list coming from the dictionary

* updated the common compound words and contractions in Bulgarian, Dutch, English and French

* removed some non-sense and rarely used English words

* fixed crashing when trying to find words with apostrophes in the database

* fixed a crash when trying to capitalize single character strings

* improved dictionary validation at build time: spaces are now disallowed
This commit is contained in:
Dimo Karaivanov 2023-06-20 09:29:48 +03:00 committed by GitHub
parent cf766334d6
commit 241a4125b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 389 additions and 35214 deletions

View file

@ -54,11 +54,9 @@ To support a new language one needs to:
- `locale` contains the language and the country codes (e.g. "en-US", "es-AR", "it-IT"). Refer to the list of [supported locales in Java](https://www.oracle.com/java/technologies/javase/jdk8-jre8-suported-locales.html#util-text).
- `dictionaryFile` is the name of the dictionary in `assets/` folder.
- `characterMap` contains the letters and punctuation marks associated with each key.
- Set `isPunctuationPartOfWords` to `true`, if the dictionary contains words with apostrophes or dashes, such as: `it's`, `you'll`, `a'tje` or `п'ят`. This will allow using 1-key for typing them (they will appear as suggestions). `false` will enable faster typing when apostrophes or other punctuation are not part of the words (no such words will be suggested).
- `abcString` _(optional)_. A custom string to display in ABC mode. By default, the first three letters on 2-key are used (e.g. "ABC" or "АБВ"). Set this if the first letters of the alphabet are _not_ on 2-key, like in Hebrew, or if a different string makes more sense.
- `hasUpperCase` _(optional)_ set to `false` when the language has no upper- and lowercase letters. For example: Arabic, Hebrew, East Asian
languages, and so on. The default is `true`.
- `name` _(optional)_ defaults to the native name of the language (e.g. "English", "Deutsch", "Українська"). Useful to set when the default is ambiguous. For example, both Portuguese in Portugal and Brazil will default to "Português", so assigning "Português brasileiro" would make it clear it's the language used in Brazil.
- `hasUpperCase` _(optional)_ set to `false` when the language has no upper- and lowercase letters. For example: Arabic, Hebrew, East Asian languages, and so on. The default is `true`.
- `name` _(optional)_ is automatically generated and equals the native name of the language (e.g. "English", "Deutsch", "Українська"). However, sometimes, the automatically selected name may be ambiguous. For example, both Portuguese in Portugal and Brazil will default to "Português", so assigning "Português brasileiro" would make it clear it's the language used in Brazil.
- Finally, add the new language to the list in `LanguageCollection.java`. You only need to add it in one place, in the constructor. Please, be nice and maintain the alphabetical order.

View file

@ -490,6 +490,7 @@
пия 170
плу
плю
по-
под 204
пое
пои
@ -2246,6 +2247,7 @@
наем 167
нает 137
наех
най-
назе
нали 184
нане 90

Can't render this file because it is too large.

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,26 @@
c'
ç'
d'
j'
l'
m'
n'
s'
t'
y'
qu'
-ce
-ci
-il
-la
-toi
-elle
-nous
-vous
jusqu'
lorsqu'
puisqu'
quoiqu'
abaissa 59
abaissable
abaissables

Can't render this file because it is too large.

View file

@ -1,14 +1,19 @@
's-Graveland 94
's-Gravendeel 95
's-Gravenhaags
's-Gravenhage 129
's-Gravenhagenaar
's-Gravenmoer
's-Gravenzande 103
's-Gravenzander
's-Gravenzands
's-Hertogenbosch 129
't 180
Gravendeel 95
Gravenhaags
Gravenhage 129
Gravenhagenaar
Gravenmoer
Gravenzande 103
Gravenzander
Gravenzands
Hertogenbosch 129
'k
's
't
's-
m'n
d'r
da's
A-attest
A-attesten
A-biljet
@ -23,7 +28,6 @@ A-omroep
A-omroepen
A-selectie 92
A-status 90
A.D.
AAA 109
AAW 96
AAW-voorziening
@ -1928,7 +1932,6 @@ ayses
aytekin
ayten
aytens
az.
azad
azads
azarkan
@ -5025,7 +5028,6 @@ chilenen 91
chili 110
chimène
chimènes
chin.
china 163
china's 104
chinees 119
@ -5719,7 +5721,6 @@ d-day 98
d-sluiting
d-trein 80
d-treinen
d.v.
ddt 99
deet 99
dna 144
@ -10520,8 +10521,6 @@ gül 85
güls
h-bom
h-bommen
h.k.h.
h.m.
hrm 122
htm 111
html 127
@ -15930,7 +15929,6 @@ l-profiel
l-profielen
l-vormig 86
l-vormige 104
l.b.
lan 99
lgpl
lto 75
@ -19174,7 +19172,6 @@ mexx'
meyer 123
meyerode
meys 75
mgr.
mia 90
mia's 72
mica 97
@ -19962,12 +19959,6 @@ mälarmeer
mélanie 73
mélanies
münchen 135
n.b.
n.h.
n.n.
n.o.
n.v.
n.w.
nam 121
nap 95
nasa 125
@ -20249,7 +20240,6 @@ neals
nebahat 81
nebahats
nebbeling
ned.
Neder-Betuwe 90
Neder-Californië
Neder-Congo
@ -21093,7 +21083,6 @@ nyssa
nyssa's
nürnberg 102
o-benen 78
o.l.v.
ocmw 132
ocmw's 108
OCMW-raad 88
@ -21525,7 +21514,6 @@ opdorp 81
opel 132
opels 84
opendocument
openoffice.org
opentaal 113
opgeldenaken
opgenoort
@ -23029,7 +23017,6 @@ quitoos
quitoër
quix 72
qureshi
r.i.p.
ram 115
rdw 124
ret 117
@ -26750,8 +26737,6 @@ srananmans
sranantongo 88
srebrenica 117
sri 78
st.-eustatius
st.-maarten
staaks
staals
staarink
@ -26847,7 +26832,6 @@ stavast 80
stavele 76
stavenga
stavleu
stct.
steef 85
steefs
steegers
@ -29997,8 +29981,6 @@ vänermeer
vättermeer
véronique 99
véroniques
w.
w.v.str.
wa 115
WA-verzekering 93
WAM-sticker
@ -31611,8 +31593,6 @@ z-verpleegkundige
z-verpleegkundigen
z-vormig
z-vormige
z.h.
z.k.h.
zaaijer 82
zaan 89
zaandam 132
@ -32158,13 +32138,6 @@ a-priorisch 79
a-priorische
a-prioristisch 79
a-prioristische 81
a.d.h.v.
a.g.v.
a.h.w.
a.j.b.
a.m.
a.s.
a.u.b.
aagt 84
aagtappel 80
aagtappels 79
@ -35328,7 +35301,6 @@ aanvuring 85
aanvuurde 84
aanvuurden 82
aanvuurt 83
aanw.
aanwaai 80
aanwaaide 80
aanwaaiden 78
@ -38653,7 +38625,6 @@ afasiepatiënten
afat
afatisch 80
afatische 84
afb.
afbad 77
afbak 85
afbakbrood
@ -38907,7 +38878,6 @@ afbuigmagneet 81
afbuigt 94
afcentiemen
afchecken
afd.
afdaal 84
afdaalde 95
afdaalden 92
@ -43068,7 +43038,6 @@ afwrijft 85
afwrijven 92
afwrijvingen 77
afwringen 82
afz.
afzaag 84
afzaagde 82
afzaagden 84
@ -45222,7 +45191,6 @@ amuzikaal
amygdala 99
amylose 72
amyotrofie 80
an.
anaal 121
anabaptisme 81
anabaptist 81
@ -47864,7 +47832,6 @@ aronskelken 83
aroom 82
arpeggio 89
arpeggio's 81
arr.
arrangeer 82
arrangeerde 93
arrangeerden 82
@ -50164,9 +50131,6 @@ aïssen 73
b's
b'tje
b'tjes
b.d.
b.g.g.
b.v.d.
baad 88
baadde 96
baadden 89
@ -53282,7 +53246,6 @@ bazuintje 82
bazuintjes 81
bbp 121
bbp-groei 81
bc.
beaam 90
beaamd 98
beaamde 104
@ -61835,7 +61798,6 @@ betrouwde 84
betrouwden 81
betrouwen 99
betrouwt 83
bett.
betuig 99
betuigd 111
betuigde 106
@ -64941,7 +64903,6 @@ bijkrijgt 86
bijkwam 98
bijkwamen 91
bijl 129
bijl.
bijladen 88
bijlag 87
bijlage 157
@ -65325,7 +65286,6 @@ bijtten 79
bijtwond 77
bijtwonden 82
bijtwondje
bijv.
bijvak 95
bijvakken 90
bijvakprogramma 80
@ -68420,7 +68380,6 @@ bluts 87
blutsen 89
blutst 81
blutste 76
blz.
blèrde 82
blèrden 81
blèren 97
@ -76207,10 +76166,8 @@ buy-outs
buzzer 85
buzzers 75
bv'tjes
bv.
bvba 119
bvba's
bw.
bye 108
byebye
byes
@ -76249,9 +76206,6 @@ c'tjes
c-pion
c-sleutel
c-sleutels
c.q.
c.s.
ca.
caban 82
cabans 83
cabaret 123
@ -85268,10 +85222,6 @@ d's 84
d'tje
d'tjes
d-pion
d.d.
d.i.
d.m.v.
d.w.z.
db 132
daad 149
daadkracht 122
@ -86782,7 +86732,6 @@ daze
dazen 97
dc 143
dc's
dd.
de 255
de-escalatie 77
de-escalaties
@ -89593,7 +89542,6 @@ dezulken 101
deïsme 89
deïst 86
deïsten 86
dhr.
dia 123
dia's 119
dia-avond
@ -92158,7 +92106,6 @@ dittografieën 77
ditzelfde 118
diuretica 93
diureticum 93
div.
divalifeest
divalioptocht
divalioptochten
@ -95906,9 +95853,7 @@ dozijnen 97
dozijntje 82
dozijntjes 82
dpi 111
dr.
dra 115
dra.
draad 153
draadachtig 83
draadachtige 89
@ -97578,8 +97523,6 @@ droste-effect
drostendiensten 82
drostschap 80
drozen 79
drs.
drs.-titel
drug 123
drugbaron
drugbeleid 93
@ -98141,7 +98084,6 @@ druïdetempel
dryade 92
dryaden 84
drycleaning
ds.
dtp 80
dtp-prik
dtp-prikken
@ -99536,12 +99478,6 @@ e-pion
e-snaar 78
e-zine 98
e-zines 77
e.a.
e.d.
e.e.a.
e.o.
e.v.
e.v.a.
earlgreythee
earnings
eau-de-colognefles 80
@ -103004,7 +102940,6 @@ enveloppenfinanciering
enveloppetje
environment 97
environments 72
enz.
enzoverder 93
enzovoort 144
enzovoorts 133
@ -103963,7 +103898,6 @@ etappezege 94
etappezeges 76
etatistisch
etatistische
etc.
eten 181
etend 97
etende 101
@ -104600,7 +104534,6 @@ evolutionistisch 83
evolutionistische 82
evoqueerde
evoqueren 80
evt.
ex 149
ex-kamerleden
ex-kamerlid
@ -104956,7 +104889,6 @@ excitatie 92
excitaties
exciteerde
exciteren 83
excl.
exclamatie 79
exclamaties 81
exclave 91
@ -105784,7 +105716,6 @@ f-sleutel 81
f-sleutels 76
fa 123
fa's
fa.
faal 98
faalangst 117
faalangsten
@ -106188,7 +106119,6 @@ falsifieert 82
falsifiëren 84
falsifiëring 78
falsifiëringen
fam.
fameus 98
fameust 77
fameuze 122
@ -107751,7 +107681,6 @@ fietszoektocht 75
fietszoektochten
fifties 93
fiftyfifty 85
fig.
figaro 83
figaro's
figgelen 78
@ -110574,7 +110503,6 @@ foxtrotte 80
foxtrotten 80
foyer 117
foyers 93
fr.
fraai 148
fraaie 152
fraaier 107
@ -111851,7 +111779,6 @@ g'tjes
g-sleutel 81
g-sleutels
g-spot 76
g.g.d.
ga 183
gaaf 141
gaafheid 102
@ -113444,7 +113371,6 @@ geavanceerder 101
geavanceerdere 92
geavondmaald 77
geavontuurd 82
geb.
gebaad 98
gebaald 81
gebaand 103
@ -119363,7 +119289,6 @@ geluwd 97
geluwde 83
gelyncht 97
gelynchte 82
gem.
gemaaid 122
gemaaide 100
gemaakt 193
@ -123739,7 +123664,6 @@ gesynthetiseerd 97
gesynthetiseerde 86
gesystematiseerd 87
gesystematiseerde 85
get.
getaakt 81
getaald 82
getaand 89
@ -144284,19 +144208,6 @@ i-bankieren
i-grec 81
i-grecs
i-mode 83
i.c.
i.c.m.
i.e.
i.h.a.
i.h.b.
i.m.
i.o.
i.o.v.
i.p.v.
i.s.m.
i.t.t.
i.v.m.
i.z.g.st.
ipod 130
ipods 95
ia 133
@ -145935,7 +145846,6 @@ inciviek 81
incivieke
incivieken
incivisme 80
incl.
inclinatie 90
inclinatiekompas 74
inclinaties 82
@ -147169,7 +147079,6 @@ infuuspompen 78
infuuspompjes
infuusvloeistof
infuusvloeistoffen
ing.
ingaan 146
ingaand 97
ingaande 116
@ -152012,7 +151921,6 @@ ionosferisch
ionosferische
ippon 94
ippons
ir.
irenisch 84
irenische 81
iridium 90
@ -153492,7 +153400,6 @@ jezuïetenstijl 74
jezuïtisch 78
jezuïtische 83
jezuïtisme 77
jhr.
jicht 114
jichtaanval 85
jichtaanvallen 83
@ -153528,8 +153435,6 @@ jip-en-janneketaal
jippie 89
jiujitsu 90
jive 89
jkvr.
jl.
jobaanbieding
jobaanbiedingen 79
jobaanbod
@ -154017,7 +153922,6 @@ joyriders
joyriding 88
joystick 108
joysticks 90
jr.
jubee 82
jubel 97
jubelde 96
@ -154414,7 +154318,6 @@ juxtapositie 87
k's 74
k'tje
k'tjes
k.k.
khz 114
kj 94
kw 131
@ -178201,7 +178104,6 @@ librettoschrijver
librije 86
librijen 78
librium 81
lic.
licentiaat 109
licentiaatsdiploma
licentiaatsdiploma's
@ -183132,16 +183034,6 @@ m'etjes
m's
m-bankierde
m-bankieren
m.
m.a.w.
m.b.t.
m.b.v.
m.i.
m.i.v.
m.m.
m.m.v.
m.n.
m.u.v.
m/s
ma's 92
maag 150
@ -186676,7 +186568,6 @@ mavoleerlingen
mavoniveau
mavoscholen
mavoschool
max.
maxi 111
maximaal 166
maximale 158
@ -190491,7 +190382,6 @@ meurt 77
meute 118
meuten 84
meutes 86
mevr.
mevrouw 162
mevrouwde 78
mevrouwen 93
@ -191969,7 +191859,6 @@ mimosa 91
mimosa's
min-maxcontract
min-maxcontracten
min.
minacht 94
minachten 98
minachtend 106
@ -193069,9 +192958,7 @@ mixtures
mixtuur 83
mkb 119
ml 146
mld.
mlk-school
mln.
mm 167
mnemotechniek 78
mnemotechnisch 83
@ -195711,7 +195598,6 @@ mozetta's
mozzarella 116
mp 98
mp's
mr.
ms 120
mts 88
mu 111
@ -196751,7 +196637,6 @@ muzikantesk 78
muzikanteske
muzisch 90
muzische 101
mw.
mycelium 97
mycologen 85
mycologie 84
@ -196829,9 +196714,6 @@ n'en
n'etje 75
n'etjes
n's 72
n.a.v.
n.o.t.k.
n.v.t.
na 119
na-aapt
na-apen 93
@ -201545,8 +201427,6 @@ niëlleerde 74
niëlleren 78
njet 91
njonja 80
nl.
nl.openoffice.org
nm 121
no-claim 97
no-claimkorting 92
@ -201561,7 +201441,6 @@ no-nonsense 111
no-nonsensebeleid
no-nonsensepolitiek
no-showstudent
no.
noachitisch
noachitische
nobele 120
@ -202435,8 +202314,6 @@ novum 100
nozem 91
nozempje
nozems 92
nr.
nrs.
nu 138
nuance 125
nuanceer 83
@ -202657,14 +202534,6 @@ nymfomanie 82
o's 85
o'tje
o'tjes
o.a.
o.b.v.
o.i.
o.i.d.
o.m.
o.t.t.
o.v.t.
o.v.v.
oase 126
oasen 90
oases 100
@ -208408,7 +208277,6 @@ onfrisser 75
onfrist 78
onfunctioneel
onfunctionele
ong.
ongaar
ongaarne 99
ongans 88
@ -221699,12 +221567,6 @@ oölieten 81
p's 78
p'tje
p'tjes
p.
p.a.
p.m.
p.o.
p.p.
p.w.
ph 129
ph's
ph-neutraal
@ -222284,7 +222146,6 @@ pafte 84
paften 80
pafzak 76
pafzakken 75
pag.
pagaai 85
pagaaide 81
pagaaiden 77
@ -230065,7 +229926,6 @@ plissérok
plissérokken
plissés 84
plistoceen
plm.
ploeg 158
ploegachtervolging
ploegarts 72
@ -230503,7 +230363,6 @@ pluvier 80
pluvieren 81
pluviometer 84
pluviometers 82
plv.
pneumatiek 92
pneumatisch 106
pneumatische 117
@ -236150,7 +236009,6 @@ proeverij 118
proeverijen 106
proevers 94
prof 145
prof.
profaan 93
profaanst
profafdeling
@ -238598,8 +238456,6 @@ pythons 90
q's
q'tje
q'tjes
q.e.d.
q.q.
qat 94
qua 162
quad 110
@ -238768,7 +238624,6 @@ r'en
r'etje
r'etjes
r's
r.-k.
ra 122
ra's 88
raad 168
@ -241970,7 +241825,6 @@ recyclingbedrijf 86
recyclingbedrijven 73
recyclingproces
red 141
red.
redacteur 138
redacteuren 122
redacteurs 105
@ -245250,7 +245104,6 @@ resort 130
resorts 112
resource 107
resources 118
resp.
respect 158
respectabel 105
respectabele 115
@ -251073,8 +250926,6 @@ s'en 72
s'je
s'jes
s's
s.j.
s.v.p.
sa 121
saai 142
saaie 133
@ -269364,7 +269215,6 @@ squasht 80
squashte
squaw 82
squaws 81
sr.
sta 156
sta-in-de-weg 94
sta-in-de-wegs 80
@ -279562,16 +279412,6 @@ t'tje
t'tjes
t-groep
t-groepen
t.a.v.
t.b.v.
t.g.v.
t.h.t.
t.h.v.
t.n.v.
t.o.v.
t.w.
t.w.v.
t.z.t.
taai 124
taaie 121
taaien 87
@ -298781,16 +298621,6 @@ uzi's 81
v's
v'tje
v'tjes
v.
v.chr.
v.d.
v.h.
v.l.n.r.
v.r.n.l.
v.t.t.
v.v.
v.v.t.
v.w.b.
va 138
va-banque 75
va-et-vient 82
@ -304041,7 +303871,6 @@ verfwerken
verfwinkel 89
verfwinkels 82
verg 104
verg.
verga 109
vergaan 132
vergaand 108
@ -312173,7 +312002,6 @@ vezelvlies
vezelvliezen
vezelwortels 77
vezen 81
vgl.
via 193
viaduct 126
viaducten 111
@ -315274,8 +315102,6 @@ vmbo-leerling
vmbo-leerlingen 91
vmbo-scholen 84
vmbo-school 80
vnl.
vnw.
vocaal 114
vocabulaire 112
vocabulaires 87
@ -319604,7 +319430,6 @@ voorwoord 127
voorwoorden 87
voorworp 79
voorworpen 77
voorz.
voorzaal 86
voorzag 130
voorzagen 111
@ -321752,7 +321577,6 @@ vruchtzak 79
vruchtzakje 74
vruchtzakken
vruchtzetting 94
vs.
vuig 89
vuige 100
vuiger 81
@ -322255,8 +322079,6 @@ véél 132
w's
w'tje
w'tjes
w.o.
w.v.t.t.k.
waad 88
waadbaar 78
waadbare 80
@ -333505,7 +333327,6 @@ wurmen 108
wurmpje 83
wurmpjes 81
wurmt 92
ww.
www 174
wyandotte
wybertje 81
@ -333633,11 +333454,6 @@ z'n 171
z's
z'tje
z'tjes
z.g.
z.g.a.n.
z.i.
z.o.z.
z.s.m.
zaad 146
zaadbakje 81
zaadbakjes 81
@ -336652,7 +336468,6 @@ zeverden 80
zeverlap 78
zeverlappen 79
zevert 84
zgn.
zich 210
zicht 162
zichtas 77

Can't render this file because it is too large.

View file

@ -86,29 +86,6 @@ def getReleaseVersion = { ->
return "${getVersionName()} (${getCurrentGitHash()})"
}
def isPunctuationInWordsAllowed (String dictionaryFile) {
boolean isAllowed = false
file("${project.projectDir}/src/io/github/sspanak/tt9/languages/definitions").listFiles().each { file ->
boolean isTheDefinitionFile = false
file.eachLine {line ->
if (line.contains(dictionaryFile)) {
isTheDefinitionFile = true
}
}
if (isTheDefinitionFile) {
file.eachLine {line ->
if (line.matches(".+?isPunctuationPartOfWords\\s*=\\s*true.+?")) {
isAllowed = true
}
}
}
}
return isAllowed
}
task validateDictionaries {
inputs.dir fileTree(dir:'assets', excludes:['dict.properties'])
outputs.file "${project.buildDir}/dict.validation.txt"
@ -116,7 +93,6 @@ task validateDictionaries {
doLast {
final String CSV_DELIMITER = ' ' // TAB
final GEOGRAPHICAL_NAME = ~"[A-Z]\\w+-[^\\n]+"
final PUNCTUATION_CHARS = ~".*?\\p{Punct}(?<!-).*?"
final MAX_ERRORS = 50
String errors = ""
@ -131,7 +107,6 @@ task validateDictionaries {
println "Validating dictionary: " + file.name
def isPunctuationAllowed = isPunctuationInWordsAllowed(file.name)
def uniqueWords = [:]
int lineNumber = 0
@ -151,6 +126,13 @@ task validateDictionaries {
return
}
if (line.contains(" ")) {
isFileValid = false
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found space on line " + lineNumber + ". Make sure each word is on a new line. Phrases are not allowed.\n"
return
}
String[] parts = line.split(CSV_DELIMITER, 2)
String word = parts[0]
String frequency = parts.length > 1 ? parts[1] : ""
@ -179,12 +161,6 @@ task validateDictionaries {
errors += "Dictionary '" + file.name + "' is invalid. Found a single letter: '" + word + "' on line " + lineNumber + ". Only uppercase single letters are allowed. The rest of the alphabet will be added automatically.\n"
}
if (!isPunctuationAllowed && word.matches(PUNCTUATION_CHARS)) {
isFileValid = false
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found a punctuation mark in word: '" + word + "' on line " + lineNumber + ". Remove all punctuation characters when the language definition disallows them or update the definition.\n"
}
String uniqueWordKey = word ==~ GEOGRAPHICAL_NAME ? word : word.toLowerCase()
if (uniqueWords[uniqueWordKey] != null && uniqueWords[uniqueWordKey] == true) {
isFileValid = false
@ -235,21 +211,12 @@ android {
defaultConfig {
minSdkVersion 19
//noinspection ExpiredTargetSdkVersion
targetSdk 30
versionCode getVersionCode()
versionName getVersionName()
}
// http://stackoverflow.com/a/19130098
// signingConfigs {
// release {
// storeFile file(System.getenv("KEYSTORE"))
// storePassword System.getenv("KEYSTORE_PASS")
// keyAlias System.getenv("KEY_ALIAS")
// keyPassword System.getenv("KEY_ALIAS_PASS")
// }
// }
//
buildTypes {
debug { data ->
data.buildConfigField 'String', 'VERSION_FULL', "\"${getDebugVersion()}\""

View file

@ -0,0 +1,43 @@
package io.github.sspanak.tt9;
import java.util.regex.Pattern;
public class TextTools {
private static final Pattern containsOtherThan1 = Pattern.compile("[02-9]");
private static final Pattern previousIsLetter = Pattern.compile("\\p{L}$");
private static final Pattern nextIsPunctuation = Pattern.compile("^\\p{Punct}");
private static final Pattern nextToWord = Pattern.compile("\\b$");
private static final Pattern startOfSentence = Pattern.compile("(?<!\\.)(^|[.?!¿¡])\\s*$");
public static boolean containsOtherThan1(String str) {
return str != null && containsOtherThan1.matcher(str).find();
}
public static boolean isNextToWord(String str) {
return str != null && nextToWord.matcher(str).find();
}
public static boolean isStartOfSentence(String str) {
return str != null && startOfSentence.matcher(str).find();
}
public static boolean nextIsPunctuation(String str) {
return str != null && nextIsPunctuation.matcher(str).find();
}
public static boolean previousIsLetter(String str) {
return str != null && previousIsLetter.matcher(str).find();
}
public static boolean startsWithWhitespace(String str) {
return str != null && !str.isEmpty() && (str.charAt(0) == ' ' || str.charAt(0) == '\n' || str.charAt(0) == '\t');
}
public static boolean startsWithNumber(String str) {
return str != null && !str.isEmpty() && (str.charAt(0) >= '0' && str.charAt(0) <= '9');
}
public static String removeNonLetters(String str) {
return str != null ? str.replaceAll("\\P{L}", "") : null;
}
}

View file

@ -191,24 +191,13 @@ public class DictionaryDb {
// In case the user has changed the text case, there would be no match.
// Try again with the lowercase equivalent.
String lowercaseWord = "";
if (affectedRows == 0) {
lowercaseWord = word.toLowerCase(language.getLocale());
String lowercaseWord = word.toLowerCase(language.getLocale());
affectedRows = getInstance().wordsDao().incrementFrequency(language.getId(), lowercaseWord, sequence);
Logger.d("incrementWordFrequency", "Attempting to increment frequency for lowercase variant: " + lowercaseWord);
}
// Some languages permit appending the punctuation to the end of the words, like so: "try,".
// But there are no such words in the dictionary, so try without the punctuation mark.
if (affectedRows == 0 && language.isPunctuationPartOfWords() && sequence.endsWith("1")) {
String truncatedWord = lowercaseWord.substring(0, word.length() - 1);
String truncatedSequence = sequence.substring(0, sequence.length() - 1);
affectedRows = getInstance().wordsDao().incrementFrequency(language.getId(), truncatedWord, truncatedSequence);
Logger.d("incrementWordFrequency", "Attempting to increment frequency with stripped punctuation: " + truncatedWord);
}
Logger.d("incrementWordFrequency", "Affected rows: " + affectedRows);
} catch (Exception e) {
Logger.e(

View file

@ -0,0 +1,39 @@
package io.github.sspanak.tt9.db.migrations;
import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.languages.definitions.Dutch;
import io.github.sspanak.tt9.languages.definitions.English;
public class DB11 {
public static final Migration MIGRATION = new Migration(10, 11) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
final String enWords = "'I''d','I''m','d''annunzio','I''ll','I''ve','prud''hon','an''t','bo''s''n','bo''s''ns','bo''sun','bo''suns','bos''n','bos''ns','br''er','ca''canny','could''ve','d''arezzo','d''estaing','e''en','e''er','fo''c''s''le','fo''c''s''les','fo''c''sle','fo''c''sles','ha''penny','he''d','he''ll','how''d','how''re','howe''er','it''d','it''ll','might''ve','must''ve','n''importe','ne''er','nor''easter','nor''wester','o''er','rec''d','sec''y','she''d','she''ll','should''ve','sou''wester','ta''en','that''d','that''ll','they''d','they''ll','they''re','they''ve','we''d','we''ll','we''re','we''ve','whate''er','whatsoe''er','whene''er','where''er','who''d','who''ll','who''re','who''ve','why''d','would''ve','you''d','you''ll','you''re','you''ve','Ch''in','L''Amour','L''Enfant','L''Oreal','L''Ouverture','T''ang','Xi''an'";
final String nlWords = "'''s-Graveland','''s-Gravendeel','''s-Gravenhaags','''s-Gravenhage','''s-Gravenhagenaar','''s-Gravenmoer','''s-Gravenzande','''s-Gravenzander','''s-Gravenzands','''s-Hertogenbosch','''t','A.D.','az.','chin.','d.v.','h.k.h.','h.m.','l.b.','mgr.','n.b.','n.h.','n.n.','n.o.','n.v.','n.w.','ned.','o.l.v.','openoffice.org','r.i.p.','st.-eustatius','st.-maarten','stct.','w.','w.v.str.','z.h.','z.k.h.','a.d.h.v.','a.g.v.','a.h.w.','a.j.b.','a.m.','a.s.','a.u.b.','aanw.','afb.','afd.','afz.','an.','arr.','b.d.','b.g.g.','b.v.d.','bc.','bett.','bijl.','bijv.','blz.','bv.','bw.','c.q.','c.s.','ca.','d.d.','d.i.','d.m.v.','d.w.z.','dd.','dhr.','div.','dr.','dra.','drs.','drs.-titel','ds.','e.a.','e.d.','e.e.a.','e.o.','e.v.','e.v.a.','enz.','etc.','evt.','excl.','fa.','fam.','fig.','fr.','g.g.d.','geb.','gem.','get.','i.c.','i.c.m.','i.e.','i.h.a.','i.h.b.','i.m.','i.o.','i.o.v.','i.p.v.','i.s.m.','i.t.t.','i.v.m.','i.z.g.st.','incl.','ing.','ir.','jhr.','jkvr.','jl.','jr.','k.k.','lic.','m.','m.a.w.','m.b.t.','m.b.v.','m.i.','m.i.v.','m.m.','m.m.v.','m.n.','m.u.v.','max.','mevr.','min.','mld.','mln.','mr.','mw.','n.a.v.','n.o.t.k.','n.v.t.','nl.','nl.openoffice.org','no.','nr.','nrs.','o.a.','o.b.v.','o.i.','o.i.d.','o.m.','o.t.t.','o.v.t.','o.v.v.','ong.','p.','p.a.','p.m.','p.o.','p.p.','p.w.','pag.','plm.','plv.','prof.','q.e.d.','q.q.','r.-k.','red.','resp.','s.j.','s.v.p.','sr.','t.a.v.','t.b.v.','t.g.v.','t.h.t.','t.h.v.','t.n.v.','t.o.v.','t.w.','t.w.v.','t.z.t.','v.','v.chr.','v.d.','v.h.','v.l.n.r.','v.r.n.l.','v.t.t.','v.v.','v.v.t.','v.w.b.','verg.','vgl.','vnl.','vnw.','voorz.','vs.','w.o.','w.v.t.t.k.','ww.','z.g.','z.g.a.n.','z.i.','z.o.z.','z.s.m.','zgn.'";
try {
database.beginTransaction();
database.execSQL(getDeleteEnglishSwordsQuery());
database.execSQL(getDeleteWordsQuery(new English().getId(), enWords));
database.execSQL(getDeleteWordsQuery(new Dutch().getId(), nlWords));
database.setTransactionSuccessful();
} catch (Exception e) {
Logger.e("Migrate to DB11", "Migration failed. " + e.getMessage());
} finally {
database.endTransaction();
}
}
};
private static String getDeleteEnglishSwordsQuery() {
return "DELETE FROM words WHERE lang=" + new English().getId() + " AND word LIKE '%''s'";
}
private static String getDeleteWordsQuery(int langId, String wordList) {
return "DELETE FROM words WHERE lang=" + langId + " AND word IN(" + wordList + ")";
}
}

View file

@ -8,12 +8,13 @@ import androidx.room.RoomDatabase;
import androidx.sqlite.db.SimpleSQLiteQuery;
import io.github.sspanak.tt9.db.migrations.DB10;
import io.github.sspanak.tt9.db.migrations.DB11;
import io.github.sspanak.tt9.db.migrations.DB6;
import io.github.sspanak.tt9.db.migrations.DB7;
import io.github.sspanak.tt9.db.migrations.DB8;
import io.github.sspanak.tt9.db.migrations.DB9;
@Database(version = 10, entities = Word.class, exportSchema = false)
@Database(version = 11, entities = Word.class, exportSchema = false)
public abstract class TT9Room extends RoomDatabase {
public abstract WordsDao wordsDao();
@ -25,7 +26,8 @@ public abstract class TT9Room extends RoomDatabase {
new DB7().getMigration(context),
DB8.MIGRATION,
DB9.MIGRATION,
DB10.MIGRATION
DB10.MIGRATION,
DB11.MIGRATION
)
.build();
}
@ -41,7 +43,7 @@ public abstract class TT9Room extends RoomDatabase {
" LIMIT " + limit;
if (word != null) {
sql = sql.replace("WHERE 1", "WHERE 1 AND word LIKE '" + word + "%'");
sql = sql.replace("WHERE 1", "WHERE 1 AND word LIKE '" + word.replace("'", "''") + "%'");
}
return new SimpleSQLiteQuery(sql);

View file

@ -18,7 +18,7 @@ public interface WordsDao {
@Query("SELECT COUNT(id) FROM words WHERE :langId < 0 OR lang = :langId")
int count(int langId);
@Query("DELETE FROM words WHERE LANG IN(:langIds)")
@Query("DELETE FROM words WHERE lang IN(:langIds)")
void deleteByLanguage(ArrayList<Integer> langIds);
@Query("SELECT COUNT(id) FROM words WHERE lang = :langId AND word = :word")

View file

@ -262,18 +262,16 @@ public class TraditionalT9 extends KeyPadHandler {
cancelAutoAccept();
forceShowWindowIfHidden();
String currentWord = getComposingText();
// Automatically accept the current word, when the next one is a space or punctuation,
// Automatically accept the previous word, when the next one is a space or punctuation,
// instead of requiring "OK" before that.
if (mInputMode.shouldAcceptCurrentSuggestion(key, hold, repeat > 0)) {
// First pass, analyze the incoming key press and decide whether it could be the start of
// a new word.
if (mInputMode.shouldAcceptPreviousSuggestion(key)) {
autoCorrectSpace(acceptIncompleteSuggestion(), false, key);
currentWord = "";
}
// Auto-adjust the text case before each word, if the InputMode supports it.
// We don't do it too often, because it is somewhat resource-intensive.
if (currentWord.length() == 0) {
if (getComposingText().isEmpty()) {
mInputMode.determineNextWordTextCase(textField.isThereText(), textField.getTextBeforeCursor());
}
@ -543,11 +541,11 @@ public class TraditionalT9 extends KeyPadHandler {
}
private void commitCurrentSuggestion(boolean entireSuggestion) {
if (!isSuggestionViewHidden() && currentInputConnection != null) {
if (!isSuggestionViewHidden()) {
if (entireSuggestion) {
textField.setComposingText(suggestionBar.getCurrentSuggestion());
}
currentInputConnection.finishComposingText();
textField.finishComposingText();
}
setSuggestions(null);
@ -556,11 +554,8 @@ public class TraditionalT9 extends KeyPadHandler {
private void clearSuggestions() {
setSuggestions(null);
if (currentInputConnection != null) {
textField.setComposingText("");
currentInputConnection.finishComposingText();
}
textField.finishComposingText();
}
@ -570,10 +565,22 @@ public class TraditionalT9 extends KeyPadHandler {
private void handleSuggestions() {
// Automatically accept the previous word, without requiring OK. This is similar to what
// Second pass, analyze the available suggestions and decide if combining them with the
// last key press makes up a compound word like: (it)'s, (I)'ve, l'(oiseau), or it is
// just the end of a sentence, like: "word." or "another?"
if (mInputMode.shouldAcceptPreviousSuggestion()) {
String lastComposingText = getComposingText(mInputMode.getSequenceLength() - 1);
commitCurrentSuggestion(false);
mInputMode.onAcceptSuggestion(lastComposingText, true);
autoCorrectSpace(lastComposingText, false, -1);
mInputMode.determineNextWordTextCase(textField.isThereText(), textField.getTextBeforeCursor());
}
// key code "suggestions" take priority over words
if (mInputMode.getKeyCode() > 0) {
sendDownUpKeyEvents(mInputMode.getKeyCode());
mInputMode.onAcceptSuggestion("");
mInputMode.reset();
return;
}
@ -605,16 +612,27 @@ public class TraditionalT9 extends KeyPadHandler {
}
private String getComposingText() {
private String getComposingText(int maxLength) {
if (maxLength == 0) {
return "";
}
maxLength = maxLength > 0 ? Math.min(maxLength, mInputMode.getSequenceLength()) : mInputMode.getSequenceLength();
String text = suggestionBar.getCurrentSuggestion();
if (text.length() > 0 && text.length() > mInputMode.getSequenceLength()) {
text = text.substring(0, mInputMode.getSequenceLength());
if (text.length() > 0 && text.length() > maxLength) {
text = text.substring(0, maxLength);
}
return text;
}
private String getComposingText() {
return getComposingText(-1);
}
private void refreshComposingText() {
textField.setComposingText(getComposingText());
}
@ -729,11 +747,11 @@ public class TraditionalT9 extends KeyPadHandler {
private void showAddWord() {
if (currentInputConnection == null) {
if (shouldBeOff()) {
return;
}
currentInputConnection.finishComposingText();
textField.finishComposingText();
clearSuggestions();
UI.showAddWordDialog(this, mLanguage.getId(), textField.getSurroundingWord());

View file

@ -56,7 +56,8 @@ abstract public class InputMode {
abstract public boolean onOtherKey(int key);
// Suggestions
public void onAcceptSuggestion(@NonNull String suggestion) {}
public void onAcceptSuggestion(@NonNull String word) { onAcceptSuggestion(word, false); }
public void onAcceptSuggestion(@NonNull String word, boolean preserveWordList) {}
/**
* loadSuggestions
@ -97,7 +98,8 @@ abstract public class InputMode {
}
// Interaction with the IME. Return "true" if it should perform the respective action.
public boolean shouldAcceptCurrentSuggestion(int key, boolean hold, boolean repeat) { return false; }
public boolean shouldAcceptPreviousSuggestion() { return false; }
public boolean shouldAcceptPreviousSuggestion(int nextKey) { return false; }
public boolean shouldAddAutoSpace(InputType inputType, TextField textField, boolean isWordAcceptedManually, int nextKey) { return false; }
public boolean shouldDeletePrecedingSpace(InputType inputType) { return false; }
public boolean shouldSelectNextSuggestion() { return false; }

View file

@ -70,7 +70,7 @@ public class ModeABC extends InputMode {
@Override final public boolean isABC() { return true; }
@Override public int getSequenceLength() { return 1; }
@Override public boolean shouldAcceptCurrentSuggestion(int key, boolean hold, boolean repeat) { return hold || !repeat; }
@Override public boolean shouldAcceptPreviousSuggestion() { return autoAcceptTimeout == 0 || !shouldSelectNextLetter; }
@Override public boolean shouldTrackUpDown() { return true; }
@Override public boolean shouldTrackLeftRight() { return true; }
@Override public boolean shouldSelectNextSuggestion() {

View file

@ -2,7 +2,10 @@ package io.github.sspanak.tt9.ime.modes;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.TextTools;
import io.github.sspanak.tt9.db.DictionaryDb;
import io.github.sspanak.tt9.ime.helpers.InputType;
import io.github.sspanak.tt9.ime.helpers.TextField;
@ -19,7 +22,6 @@ public class ModePredictive extends InputMode {
private String digitSequence = "";
private String lastAcceptedWord = "";
private String lastAcceptedSequence = "";
// stem filter
private boolean isStemFuzzy = false;
@ -123,6 +125,31 @@ public class ModePredictive extends InputMode {
}
/**
* clearLastAcceptedWord
* Removes the last accepted word from the suggestions list and the "digitSequence"
* or stops silently, when there is nothing to do.
*/
private void clearLastAcceptedWord() {
if (
lastAcceptedWord.isEmpty()
|| suggestions.isEmpty()
|| !suggestions.get(0).toLowerCase(language.getLocale()).startsWith(lastAcceptedWord.toLowerCase(language.getLocale()))
) {
return;
}
int lastAcceptedWordLength = lastAcceptedWord.length();
digitSequence = digitSequence.length() > lastAcceptedWordLength ? digitSequence.substring(lastAcceptedWordLength) : "";
ArrayList<String> lastSuggestions = new ArrayList<>(suggestions);
suggestions.clear();
for (String s : lastSuggestions) {
suggestions.add(s.length() >= lastAcceptedWordLength ? s.substring(lastAcceptedWordLength) : "");
}
}
/**
* clearWordStem
* Do not filter the suggestions by the word set using "setWordStem()", use only the digit sequence.
@ -156,15 +183,18 @@ public class ModePredictive extends InputMode {
* Note that you need to manually get the suggestions again to obtain a filtered list.
*/
@Override
public boolean setWordStem(String wordStem, boolean exact) {
if (language == null || wordStem == null || wordStem.length() < 1) {
public boolean setWordStem(String newStem, boolean exact) {
String sanitizedStem = TextTools.removeNonLetters(newStem);
if (language == null || sanitizedStem == null || sanitizedStem.length() < 1) {
return false;
}
try {
digitSequence = language.getDigitSequenceForWord(wordStem);
// digitSequence = "the raw input", so that everything the user typed is preserved visually
// stem = "the sanitized input", because filtering by anything that is not a letter makes no sense
digitSequence = language.getDigitSequenceForWord(newStem);
stem = sanitizedStem.toLowerCase(language.getLocale());
isStemFuzzy = !exact;
stem = digitSequence.startsWith("0") || digitSequence.startsWith("1") ? "" : wordStem.toLowerCase(language.getLocale());
Logger.d("tt9/setWordStem", "Stem is now: " + stem + (isStemFuzzy ? " (fuzzy)" : ""));
return true;
@ -172,7 +202,7 @@ public class ModePredictive extends InputMode {
isStemFuzzy = false;
stem = "";
Logger.w("tt9/setWordStem", "Ignoring invalid stem: " + wordStem + ". " + e.getMessage());
Logger.w("tt9/setWordStem", "Ignoring invalid stem: " + newStem + ". " + e.getMessage());
return false;
}
}
@ -239,19 +269,26 @@ public class ModePredictive extends InputMode {
/**
* onAcceptSuggestion
* Bring this word up in the suggestions list next time.
* Bring this word up in the suggestions list next time and if necessary preserves the suggestion list
* with "currentWord" cleaned from them.
*/
@Override
public void onAcceptSuggestion(@NonNull String currentWord) {
public void onAcceptSuggestion(@NonNull String currentWord, boolean preserveWords) {
lastAcceptedWord = currentWord;
lastAcceptedSequence = digitSequence;
reset();
if (currentWord.length() == 0) {
if (preserveWords) {
clearLastAcceptedWord();
} else {
reset();
}
stem = "";
if (currentWord.isEmpty()) {
Logger.i("acceptCurrentSuggestion", "Current word is empty. Nothing to accept.");
return;
}
// increment the frequency of the given word
try {
String sequence = language.getDigitSequenceForWord(currentWord);
@ -284,22 +321,34 @@ public class ModePredictive extends InputMode {
/**
* shouldAcceptCurrentSuggestion
* shouldAcceptPreviousSuggestion
* In this mode, In addition to confirming the suggestion in the input field,
* we also increase its' priority. This function determines whether we want to do all this or not.
*/
@Override
public boolean shouldAcceptCurrentSuggestion(int key, boolean hold, boolean repeat) {
public boolean shouldAcceptPreviousSuggestion(int nextKey) {
return
hold
// Quickly accept suggestions using "space" instead of pressing "ok" then "space"
|| (key == 0 && !repeat)
// Punctuation is considered "a word", so that we can increase the priority as needed
// Also, it must break the current word.
|| (!language.isPunctuationPartOfWords() && key == 1 && digitSequence.length() > 0 && !digitSequence.endsWith("1"))
// On the other hand, letters also "break" punctuation.
|| (!language.isPunctuationPartOfWords() && key != 1 && digitSequence.endsWith("1"))
|| (digitSequence.endsWith("0") && key != 0);
!digitSequence.isEmpty() && (
(nextKey == 0 && digitSequence.charAt(digitSequence.length() - 1) != '0')
|| (nextKey != 0 && digitSequence.charAt(digitSequence.length() - 1) == '0')
);
}
/**
* shouldAcceptPreviousSuggestion
* Variant for post suggestion load analysis.
*/
@Override
public boolean shouldAcceptPreviousSuggestion() {
return
(autoAcceptTimeout == 0 && !digitSequence.startsWith("0"))
|| (
!digitSequence.isEmpty()
&& !predictions.areThereDbWords()
&& digitSequence.contains("1")
&& TextTools.containsOtherThan1(digitSequence)
);
}
@ -307,11 +356,10 @@ public class ModePredictive extends InputMode {
public boolean shouldAddAutoSpace(InputType inputType, TextField textField, boolean isWordAcceptedManually, int nextKey) {
return autoSpace
.setLastWord(lastAcceptedWord)
.setLastSequence(lastAcceptedSequence)
.setLastSequence()
.setInputType(inputType)
.setTextField(textField)
.shouldAddAutoSpace(isWordAcceptedManually, nextKey);
}
@ -319,7 +367,6 @@ public class ModePredictive extends InputMode {
public boolean shouldDeletePrecedingSpace(InputType inputType) {
return autoSpace
.setLastWord(lastAcceptedWord)
.setLastSequence(lastAcceptedSequence)
.setInputType(inputType)
.setTextField(null)
.shouldDeletePrecedingSpace();

View file

@ -1,22 +1,16 @@
package io.github.sspanak.tt9.ime.modes.helpers;
import java.util.regex.Pattern;
import io.github.sspanak.tt9.TextTools;
import io.github.sspanak.tt9.ime.helpers.InputType;
import io.github.sspanak.tt9.ime.helpers.TextField;
import io.github.sspanak.tt9.preferences.SettingsStore;
public class AutoSpace {
private final Pattern isNumber = Pattern.compile("\\s*\\d+\\s*");
private final Pattern nextIsLetter = Pattern.compile("^\\p{L}+");
private final Pattern nextIsPunctuation = Pattern.compile("^\\p{Punct}");
private final SettingsStore settings;
private InputType inputType;
private TextField textField;
private String lastWord;
private String lastSequence;
public AutoSpace(SettingsStore settingsStore) {
settings = settingsStore;
@ -37,8 +31,7 @@ public class AutoSpace {
return this;
}
public AutoSpace setLastSequence(String lastSequence) {
this.lastSequence = lastSequence;
public AutoSpace setLastSequence() {
return this;
}
@ -57,12 +50,11 @@ public class AutoSpace {
return
settings.getAutoSpace()
&& !inputType.isSpecialized()
&& !nextChars.startsWith(" ")
&& !isNumber.matcher(previousChars).find()
&& !nextIsPunctuation.matcher(nextChars).find()
&& nextKey != 0
&& !TextTools.startsWithWhitespace(nextChars)
&& (
shouldAddAfterPunctuation(previousChars, nextKey)
|| shouldAddAfterWord(isWordAcceptedManually, nextChars)
shouldAddAfterWord(isWordAcceptedManually, previousChars, nextChars, nextKey)
|| shouldAddAfterPunctuation(previousChars, nextChars, nextKey)
);
}
@ -73,24 +65,23 @@ public class AutoSpace {
* The rules are similar to the ones in the standard Android keyboard (with some exceptions,
* because we are not using a QWERTY keyboard here).
*/
private boolean shouldAddAfterPunctuation(String previousChars, int nextKey) {
return
// no space after whitespace or special characters
!previousChars.endsWith(" ") && !previousChars.endsWith("\n") && !previousChars.endsWith("\t") // previous whitespace
&& !lastSequence.equals("0") // previous previous math/special char
&& nextKey != 0 // composing (upcoming) whitespace or special character
private boolean shouldAddAfterPunctuation(String previousChars, String nextChars, int nextKey) {
char previousChar = previousChars.isEmpty() ? 0 : previousChars.charAt(previousChars.length() - 1);
// add space after the these
return
nextKey != 1
&& !TextTools.nextIsPunctuation(nextChars)
&& !TextTools.startsWithNumber(nextChars)
&& (
previousChars.endsWith(".")
|| previousChars.endsWith(",")
|| previousChars.endsWith(";")
|| previousChars.endsWith(":")
|| previousChars.endsWith("!")
|| previousChars.endsWith("?")
|| previousChars.endsWith(")")
|| previousChars.endsWith("]")
|| previousChars.endsWith("%")
previousChar == '.'
|| previousChar == ','
|| previousChar == ';'
|| previousChar == ':'
|| previousChar == '!'
|| previousChar == '?'
|| previousChar == ')'
|| previousChar == ']'
|| previousChar == '%'
|| previousChars.endsWith(" -")
|| previousChars.endsWith(" /")
);
@ -98,15 +89,15 @@ public class AutoSpace {
/**
* shouldAddAfterPunctuation
* shouldAddAfterWord
* Similar to "shouldAddAfterPunctuation()", but determines whether to add a space after words.
*/
private boolean shouldAddAfterWord(boolean isWordAcceptedManually, String nextChars) {
private boolean shouldAddAfterWord(boolean isWordAcceptedManually, String previousChars, String nextChars, int nextKey) {
return
// Do not add space when auto-accepting words, because it feels very confusing when typing.
isWordAcceptedManually
// Right before another word
&& !nextIsLetter.matcher(nextChars).find();
isWordAcceptedManually // Do not add space when auto-accepting words, because it feels very confusing when typing.
&& nextKey != 1
&& nextChars.isEmpty()
&& TextTools.previousIsLetter(previousChars);
}

View file

@ -1,14 +1,11 @@
package io.github.sspanak.tt9.ime.modes.helpers;
import java.util.regex.Pattern;
import io.github.sspanak.tt9.TextTools;
import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.preferences.SettingsStore;
public class AutoTextCase {
private final Pattern nextToWordRegex = Pattern.compile("\\b$");
private final Pattern startOfSentenceRegex = Pattern.compile("(?<!\\.)(^|[.?!¿¡])\\s*$");
private final SettingsStore settings;
@ -68,11 +65,11 @@ public class AutoTextCase {
}
// start of sentence, excluding after "..."
if (startOfSentenceRegex.matcher(textBeforeCursor).find()) {
if (TextTools.isStartOfSentence(textBeforeCursor)) {
return InputMode.CASE_CAPITALIZE;
}
if (nextToWordRegex.matcher(textBeforeCursor).find()) {
if (TextTools.isNextToWord(textBeforeCursor)) {
return InputMode.CASE_LOWER;
}

View file

@ -23,6 +23,7 @@ public class Predictions {
private Runnable onWordsChanged = () -> {};
// data
private boolean areThereDbWords = false;
private final ArrayList<String> words = new ArrayList<>();
// punctuation/emoji
@ -82,6 +83,10 @@ public class Predictions {
return words;
}
public boolean areThereDbWords() {
return areThereDbWords;
}
/**
* suggestStem
@ -89,7 +94,7 @@ public class Predictions {
* the user has pressed X keys (otherwise, it makes no sense to add it).
*/
private void suggestStem() {
if (stem.length() > 0 && stem.length() == digitSequence.length()) {
if (!stem.isEmpty() && stem.length() == digitSequence.length()) {
words.add(stem);
}
}
@ -114,7 +119,7 @@ public class Predictions {
* sequence or loads the static ones.
*/
public void load() {
if (digitSequence == null || digitSequence.length() == 0) {
if (digitSequence == null || digitSequence.isEmpty()) {
words.clear();
onWordsChanged.run();
return;
@ -124,7 +129,7 @@ public class Predictions {
onWordsChanged.run();
} else {
DictionaryDb.getWords(
this::onDbWords,
(words) -> onDbWords(words, true),
language,
digitSequence,
stem,
@ -170,6 +175,23 @@ public class Predictions {
return true;
}
private void loadWithoutLeadingPunctuation() {
DictionaryDb.getWords(
(dbWords) -> {
char firstChar = inputWord.charAt(0);
for (int i = 0; i < dbWords.size(); i++) {
dbWords.set(i, firstChar + dbWords.get(i));
}
onDbWords(dbWords, false);
},
language,
digitSequence.substring(1),
stem.length() > 1 ? stem.substring(1) : "",
settings.getSuggestionsMin(),
settings.getSuggestionsMax()
);
}
/**
* dbWordsHandler
@ -177,8 +199,18 @@ public class Predictions {
* they will be generated based on the "inputWord". After the word list is compiled, it notifies the
* external handler it is now possible to use it with "getList()".
*/
private void onDbWords (ArrayList<String> dbWords) {
if (dbWords.size() == 0 && digitSequence.length() > 0) {
private void onDbWords(ArrayList<String> dbWords, boolean isRetryAllowed) {
// only the first round matters, the second one is just for getting the letters for a given key
areThereDbWords = !dbWords.isEmpty() && isRetryAllowed;
// If there were no database words for ",a", try getting the letters only (e.g. "a", "b", "c").
// We do this to display them in the correct order.
if (dbWords.isEmpty() && isRetryAllowed && digitSequence.length() == 2 && digitSequence.charAt(0) == '1') {
loadWithoutLeadingPunctuation();
return;
}
if (dbWords.isEmpty() && !digitSequence.isEmpty()) {
emptyDbWarning.emitOnce(language);
dbWords = generatePossibleCompletions(inputWord);
}
@ -186,7 +218,7 @@ public class Predictions {
words.clear();
suggestStem();
suggestMissingWords(generatePossibleStemVariations(dbWords));
suggestMissingWords(dbWords);
suggestMissingWords(insertPunctuationCompletions(dbWords));
onWordsChanged.run();
}
@ -206,20 +238,16 @@ public class Predictions {
// Make sure the displayed word and the digit sequence, we will be generating suggestions from,
// have the same length, to prevent visual discrepancies.
baseWord = (baseWord != null && baseWord.length() > 0) ? baseWord.substring(0, Math.min(digitSequence.length() - 1, baseWord.length())) : "";
baseWord = (baseWord != null && !baseWord.isEmpty()) ? baseWord.substring(0, Math.min(digitSequence.length() - 1, baseWord.length())) : "";
// append all letters for the last digit in the sequence (the last pressed key)
int lastSequenceDigit = digitSequence.charAt(digitSequence.length() - 1) - '0';
for (String keyLetter : language.getKeyCharacters(lastSequenceDigit)) {
// let's skip numbers, because it's weird, for example:
// | weird | weire | weirf | weir2 |
if (keyLetter.charAt(0) < '0' || keyLetter.charAt(0) > '9') {
for (String keyLetter : language.getKeyCharacters(lastSequenceDigit, false)) {
generatedWords.add(baseWord + keyLetter);
}
}
// if there are no letters for this key, just append the number
if (generatedWords.size() == 0) {
if (generatedWords.isEmpty()) {
generatedWords.add(baseWord + digitSequence.charAt(digitSequence.length() - 1));
}
@ -227,6 +255,46 @@ public class Predictions {
}
/**
* insertPunctuationCompletions
* When given: "you'", for example, this also generates all other 1-key alternatives, like:
* "you.", "you?", "you!" and so on. The generated words will be inserted after the direct
* database matches and before the fuzzy matches, as if they were direct matches with low frequency.
* This is to preserve the sorting by length and frequency.
*/
private ArrayList<String> insertPunctuationCompletions(ArrayList<String> dbWords) {
if (!stem.isEmpty() || dbWords.isEmpty() || digitSequence.length() < 2 || !digitSequence.endsWith("1")) {
return dbWords;
}
ArrayList<String> complementedWords = new ArrayList<>();
int exactMatchLength = digitSequence.length();
// shortest database words (exact matches)
for (String w : dbWords) {
if (w.length() <= exactMatchLength) {
complementedWords.add(w);
}
}
// generated "exact matches"
for (String w : generatePossibleCompletions(dbWords.get(0))) {
if (!dbWords.contains(w) && !dbWords.contains(w.toLowerCase(language.getLocale()))) {
complementedWords.add(w);
}
}
// longer database words (fuzzy matches)
for (String w : dbWords) {
if (w.length() > exactMatchLength) {
complementedWords.add(w);
}
}
return complementedWords;
}
/**
* generatePossibleStemVariations
* Similar to generatePossibleCompletions(), but uses the current filter as a base word. This is
@ -242,7 +310,7 @@ public class Predictions {
*/
private ArrayList<String> generatePossibleStemVariations(ArrayList<String> dbWords) {
ArrayList<String> variations = new ArrayList<>();
if (stem.length() == 0) {
if (stem.isEmpty()) {
return variations;
}

View file

@ -18,7 +18,6 @@ public class Language {
// settings
protected boolean hasUpperCase = true;
protected boolean isPunctuationPartOfWords; // see the getter for more info
final public int getId() {
if (id == 0) {
@ -60,24 +59,6 @@ public class Language {
return abcString;
}
/**
* isPunctuationPartOfWords
* This plays a role in Predictive mode only.
*
* Return "true", if you need to use the 1-key for typing words, such as:
* "it's" (English), "a'tje" (Dutch), "п'ят" (Ukrainian).
*
* Return "false" also:
* - hide words like the above from the suggestions.
* - 1-key would commit the current word, then display the punctuation list.
* For example, pressing 1-key after "it" would accept "it" as a separate word,
* then display only: | , | . | ! | ? | ...
*
* "false" is recommended when apostrophes or other punctuation are not part of the words,
* because it would allow faster typing.
*/
final public boolean isPunctuationPartOfWords() { return isPunctuationPartOfWords; }
public boolean hasUpperCase() {
return hasUpperCase;
@ -124,7 +105,21 @@ public class Language {
}
public String capitalize(String word) {
return word != null ? word.substring(0, 1).toUpperCase(locale) + word.substring(1).toLowerCase(locale) : null;
if (word == null) {
return null;
}
String capitalizedWord = "";
if (!word.isEmpty()) {
capitalizedWord += word.substring(0, 1).toUpperCase(locale);
}
if (word.length() > 1) {
capitalizedWord += word.substring(1).toLowerCase(locale);
}
return capitalizedWord;
}
public boolean isMixedCaseWord(String word) {

View file

@ -11,8 +11,6 @@ public class BrazilianPortuguese extends English {
locale = new Locale("pt","BR");
dictionaryFile = "pt-BR-utf8.csv";
isPunctuationPartOfWords = true;
characterMap.get(2).addAll(Arrays.asList("ç", "á", "â", "ã", "à"));
characterMap.get(3).addAll(Arrays.asList("é", "ê", "è"));
characterMap.get(4).add("í");

View file

@ -12,8 +12,6 @@ public class Bulgarian extends Language {
locale = new Locale("bg","BG");
dictionaryFile = "bg-utf8.csv";
isPunctuationPartOfWords = false;
characterMap = new ArrayList<>(Arrays.asList(
Characters.Special, // 0
Characters.Sentence, // 1

View file

@ -10,8 +10,6 @@ public class Dutch extends English {
locale = new Locale("nl","NL");
dictionaryFile = "nl-utf8.csv";
isPunctuationPartOfWords = true;
characterMap.get(2).addAll(Arrays.asList("à", "ä", "ç"));
characterMap.get(3).addAll(Arrays.asList("é", "è", "ê", "ë"));
characterMap.get(4).addAll(Arrays.asList("î", "ï"));

View file

@ -12,8 +12,6 @@ public class English extends Language {
locale = Locale.ENGLISH;
dictionaryFile = "en-utf8.csv";
isPunctuationPartOfWords = true;
characterMap = new ArrayList<>(Arrays.asList(
Characters.Special, // 0
Characters.Sentence, // 1

View file

@ -10,8 +10,6 @@ public class Finnish extends English {
locale = new Locale("fi","FI");
dictionaryFile = "fi-utf8.csv";
isPunctuationPartOfWords = true;
characterMap.get(2).addAll(Arrays.asList("ä", "å"));
characterMap.get(6).add("ö");
}

View file

@ -10,8 +10,6 @@ public class French extends English {
locale = Locale.FRENCH;
dictionaryFile = "fr-utf8.csv";
isPunctuationPartOfWords = false;
characterMap.get(2).addAll(Arrays.asList("à", "â", "æ", "ç"));
characterMap.get(3).addAll(Arrays.asList("é", "è", "ê", "ë"));
characterMap.get(4).addAll(Arrays.asList("î", "ï"));

View file

@ -9,8 +9,6 @@ public class German extends English {
locale = Locale.GERMAN;
dictionaryFile = "de-utf8.csv";
isPunctuationPartOfWords = false;
characterMap.get(2).add("ä");
characterMap.get(6).add("ö");
characterMap.get(7).add("ß");

View file

@ -14,7 +14,6 @@ public class Hebrew extends Language {
abcString = "אבג";
hasUpperCase = false;
isPunctuationPartOfWords = true;
characterMap = new ArrayList<>(Arrays.asList(
Characters.Special, // 0

View file

@ -10,8 +10,6 @@ public class Italian extends English {
locale = Locale.ITALIAN;
dictionaryFile = "it-utf8.csv";
isPunctuationPartOfWords = false;
characterMap.get(2).add("à");
characterMap.get(3).addAll(Arrays.asList("é", "è"));
characterMap.get(4).addAll(Arrays.asList("ì", "í", "î"));

View file

@ -10,8 +10,6 @@ public class Norwegian extends English {
locale = new Locale("nb","NO");
dictionaryFile = "nb-utf8.csv";
isPunctuationPartOfWords = false;
characterMap.get(2).addAll(Arrays.asList("æ", "å"));
characterMap.get(3).addAll(Arrays.asList("é", "è"));
characterMap.get(6).addAll(Arrays.asList("ø", "ó", "ò", "ô"));

View file

@ -10,8 +10,6 @@ public class Polish extends English {
locale = new Locale("pl","PL");
dictionaryFile = "pl-utf8.csv";
isPunctuationPartOfWords = false;
characterMap.get(2).addAll(Arrays.asList("ą", "ć"));
characterMap.get(3).add("ę");
characterMap.get(5).add("ł");

View file

@ -12,8 +12,6 @@ public class Russian extends Language {
locale = new Locale("ru","RU");
dictionaryFile = "ru-utf8.csv";
isPunctuationPartOfWords = false;
characterMap = new ArrayList<>(Arrays.asList(
Characters.Special, // 0
Characters.Sentence, // 1

View file

@ -13,8 +13,6 @@ public class Spanish extends English {
locale = new Locale("es", "ES");
dictionaryFile = "es-utf8.csv";
isPunctuationPartOfWords = false;
characterMap.set(1, new ArrayList<>(Characters.Sentence));
characterMap.get(1).addAll(Arrays.asList("¡", "¿"));

View file

@ -10,8 +10,6 @@ public class Swedish extends English {
locale = new Locale("sv","SE");
dictionaryFile = "sv-utf8.csv";
isPunctuationPartOfWords = false;
characterMap.get(2).addAll(Arrays.asList("å", "ä"));
characterMap.get(3).add("é");
characterMap.get(6).add("ö");

View file

@ -12,8 +12,6 @@ public class Ukrainian extends Language {
locale = new Locale("uk","UA");
dictionaryFile = "uk-utf8.csv";
isPunctuationPartOfWords = true;
characterMap = new ArrayList<>(Arrays.asList(
Characters.Special, // 0
Characters.Sentence, // 1

View file

@ -8,6 +8,5 @@ public class Yiddish extends Hebrew {
locale = new Locale("ji","JI");
dictionaryFile = "ji-utf8.csv";
isPunctuationPartOfWords = true;
}
}