Compare commits
3 Commits
feat/use-n
...
feat/flest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f41385ae75 | ||
|
|
5c85be61d9 | ||
|
|
639beb9e64 |
@@ -55,10 +55,6 @@ android {
|
||||
)
|
||||
}
|
||||
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "dev.patrickgold.florisboard"
|
||||
minSdk = projectMinSdk.toInt()
|
||||
@@ -193,7 +189,6 @@ dependencies {
|
||||
implementation(libs.androidx.material.icons)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.profileinstaller)
|
||||
implementation(libs.androidx.appcompat)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
implementation(libs.androidx.room.runtime)
|
||||
implementation(libs.cache4k)
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 3,
|
||||
"identityHash": "145ca5bf4bff8e98f71ebc70ab3b495b",
|
||||
"identityHash": "282a1b421e498fd0e21c055b6a4315e0",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "clipboard_history",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, `isSensitive` INTEGER NOT NULL DEFAULT 0, `isRemoteDevice` INTEGER NOT NULL DEFAULT 0)",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, `isSensitive` INTEGER NOT NULL, `isRemoteDevice` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
@@ -54,15 +54,13 @@
|
||||
"fieldPath": "isSensitive",
|
||||
"columnName": "isSensitive",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRemoteDevice",
|
||||
"columnName": "isRemoteDevice",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
@@ -88,7 +86,7 @@
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '145ca5bf4bff8e98f71ebc70ab3b495b')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '282a1b421e498fd0e21c055b6a4315e0')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "1dd181d116dcb4530fb5b33451ea9ab5",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "clipboard_history",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, `is_sensitive` INTEGER NOT NULL DEFAULT 0, `is_remote_device` INTEGER NOT NULL DEFAULT 0)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "text",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uri",
|
||||
"columnName": "uri",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "creationTimestampMs",
|
||||
"columnName": "creationTimestampMs",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPinned",
|
||||
"columnName": "isPinned",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mimeTypes",
|
||||
"columnName": "mimeTypes",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isSensitive",
|
||||
"columnName": "is_sensitive",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRemoteDevice",
|
||||
"columnName": "is_remote_device",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_clipboard_history__id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_clipboard_history__id` ON `${TABLE_NAME}` (`_id`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dd181d116dcb4530fb5b33451ea9ab5')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -76,16 +76,6 @@
|
||||
<meta-data android:name="android.view.textservice.scs" android:resource="@xml/spellchecker"/>
|
||||
</service>
|
||||
|
||||
<!-- Service for Locale handling -->
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<!-- Main App Activity -->
|
||||
<activity
|
||||
android:name="dev.patrickgold.florisboard.app.FlorisAppActivity"
|
||||
|
||||
@@ -47,8 +47,10 @@ import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeMode
|
||||
import dev.patrickgold.florisboard.ime.theme.extCoreTheme
|
||||
import org.florisboard.lib.android.isOrientationPortrait
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
import dev.patrickgold.florisboard.lib.observeAsTransformingState
|
||||
import org.florisboard.lib.snygg.SnyggLevel
|
||||
import dev.patrickgold.florisboard.lib.util.VersionName
|
||||
import dev.patrickgold.jetpref.datastore.JetPref
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceMigrationEntry
|
||||
@@ -57,8 +59,6 @@ import dev.patrickgold.jetpref.datastore.model.PreferenceType
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.florisboard.lib.android.isOrientationPortrait
|
||||
import org.florisboard.lib.snygg.SnyggLevel
|
||||
|
||||
fun florisPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
|
||||
|
||||
@@ -118,14 +118,6 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "clipboard__clean_up_after",
|
||||
default = 20,
|
||||
)
|
||||
val autoCleanSensitive = boolean(
|
||||
key = "clipboard__auto_clean_sensitive",
|
||||
default = false,
|
||||
)
|
||||
val autoCleanSensitiveAfter = int(
|
||||
key = "clipboard__auto_clean_sensitive_after",
|
||||
default = 20,
|
||||
)
|
||||
val limitHistorySize = boolean(
|
||||
key = "clipboard__limit_history_size",
|
||||
default = true,
|
||||
|
||||
@@ -20,9 +20,8 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.displayCutoutPadding
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
@@ -38,7 +37,6 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.navigation.NavController
|
||||
@@ -74,7 +72,7 @@ val LocalNavController = staticCompositionLocalOf<NavController> {
|
||||
error("LocalNavController not initialized")
|
||||
}
|
||||
|
||||
class FlorisAppActivity : AppCompatActivity() {
|
||||
class FlorisAppActivity : ComponentActivity() {
|
||||
private val prefs by florisPreferenceModel()
|
||||
private val cacheManager by cacheManager()
|
||||
private var appTheme by mutableStateOf(AppTheme.AUTO)
|
||||
@@ -94,12 +92,10 @@ class FlorisAppActivity : AppCompatActivity() {
|
||||
appTheme = it
|
||||
}
|
||||
prefs.advanced.settingsLanguage.observe(this) {
|
||||
val appLocale: LocaleListCompat = if (it == "auto") {
|
||||
LocaleListCompat.getEmptyLocaleList()
|
||||
} else {
|
||||
LocaleListCompat.forLanguageTags(FlorisLocale.fromTag(it).languageTag())
|
||||
}
|
||||
AppCompatDelegate.setApplicationLocales(appLocale)
|
||||
val config = Configuration(resources.configuration)
|
||||
val locale = if (it == "auto") FlorisLocale.default() else FlorisLocale.fromTag(it)
|
||||
config.setLocale(locale.base)
|
||||
resourcesContext = createConfigurationContext(config)
|
||||
}
|
||||
if (AndroidVersion.ATMOST_API28_P) {
|
||||
prefs.advanced.showAppIcon.observe(this) {
|
||||
|
||||
@@ -17,15 +17,10 @@
|
||||
package dev.patrickgold.florisboard.app.ext
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
@@ -38,13 +33,8 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import dev.patrickgold.florisboard.R
|
||||
@@ -56,7 +46,6 @@ import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
@@ -91,66 +80,49 @@ enum class ExtensionListScreenType(
|
||||
fun ExtensionListScreen(type: ExtensionListScreenType, showUpdate: Boolean) = FlorisScreen {
|
||||
title = stringRes(type.titleResId)
|
||||
previewFieldVisible = false
|
||||
scrollable = false
|
||||
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
val extensionManager by context.extensionManager()
|
||||
val extensionIndex by type.getExtensionIndex(extensionManager).observeAsNonNullState()
|
||||
|
||||
var fabHeight by remember {
|
||||
mutableStateOf(0)
|
||||
}
|
||||
val fabHeightDp = with(LocalDensity.current) { fabHeight.toDp()+16.dp }
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
content {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.florisScrollbar(state = listState, isVertical = true),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(bottom = fabHeightDp),
|
||||
) {
|
||||
if (showUpdate) {
|
||||
item {
|
||||
UpdateBox(extensionIndex = extensionIndex)
|
||||
}
|
||||
}
|
||||
items(extensionIndex) { ext ->
|
||||
FlorisOutlinedBox(
|
||||
modifier = Modifier.defaultFlorisOutlinedBox(),
|
||||
title = ext.meta.title,
|
||||
subtitle = ext.meta.id,
|
||||
if (showUpdate) {
|
||||
UpdateBox(extensionIndex = extensionIndex)
|
||||
}
|
||||
for (ext in extensionIndex) {
|
||||
FlorisOutlinedBox(
|
||||
modifier = Modifier.defaultFlorisOutlinedBox(),
|
||||
title = ext.meta.title,
|
||||
subtitle = ext.meta.id,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
text = ext.meta.description ?: "",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 6.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
text = ext.meta.description ?: "",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
FlorisTextButton(
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.View(ext.meta.id))
|
||||
},
|
||||
icon = Icons.Outlined.Info,
|
||||
text = stringRes(id = R.string.ext__list__view_details),//stringRes(R.string.action__add),
|
||||
colors = ButtonDefaults.textButtonColors(),
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
FlorisTextButton(
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.Edit(ext.meta.id))
|
||||
},
|
||||
icon = Icons.Default.Edit,
|
||||
text = stringRes(R.string.action__edit),
|
||||
enabled = extensionManager.canDelete(ext),
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 6.dp),
|
||||
) {
|
||||
FlorisTextButton(
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.View(ext.meta.id))
|
||||
},
|
||||
icon = Icons.Outlined.Info,
|
||||
text = stringRes(id = R.string.ext__list__view_details),//stringRes(R.string.action__add),
|
||||
colors = ButtonDefaults.textButtonColors(),
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
FlorisTextButton(
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.Edit(ext.meta.id))
|
||||
},
|
||||
icon = Icons.Default.Edit,
|
||||
text = stringRes(R.string.action__edit),
|
||||
enabled = extensionManager.canDelete(ext),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,9 +142,6 @@ fun ExtensionListScreen(type: ExtensionListScreenType, showUpdate: Boolean) = Fl
|
||||
text = stringRes(id = R.string.ext__editor__title_create_any),
|
||||
)
|
||||
},
|
||||
modifier = Modifier.onGloballyPositioned {
|
||||
fabHeight = it.size.height
|
||||
},
|
||||
shape = FloatingActionButtonDefaults.extendedFabShape,
|
||||
onClick = { type.launchExtensionCreate.invoke(navController) },
|
||||
)
|
||||
|
||||
@@ -16,11 +16,6 @@
|
||||
|
||||
package dev.patrickgold.florisboard.app.settings.advanced
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Adb
|
||||
import androidx.compose.material.icons.filled.Archive
|
||||
@@ -31,7 +26,6 @@ import androidx.compose.material.icons.filled.Preview
|
||||
import androidx.compose.material.icons.filled.SettingsBackupRestore
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.AppTheme
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
@@ -40,6 +34,7 @@ import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
@@ -49,7 +44,6 @@ import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
|
||||
import dev.patrickgold.jetpref.datastore.ui.vectorResource
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
|
||||
@Composable
|
||||
fun AdvancedScreen() = FlorisScreen {
|
||||
@@ -57,10 +51,6 @@ fun AdvancedScreen() = FlorisScreen {
|
||||
previewFieldVisible = false
|
||||
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val languageSettingsLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {}
|
||||
|
||||
content {
|
||||
ListPreference(
|
||||
@@ -77,86 +67,70 @@ fun AdvancedScreen() = FlorisScreen {
|
||||
AndroidVersion.ATLEAST_API31_S
|
||||
},
|
||||
)
|
||||
if (AndroidVersion.ATLEAST_API33_T) {
|
||||
Preference(
|
||||
title = stringRes(R.string.pref__advanced__settings_language__label),
|
||||
icon = Icons.Default.Language,
|
||||
onClick = {
|
||||
languageSettingsLauncher.launch(
|
||||
Intent(
|
||||
Settings.ACTION_APP_LOCALE_SETTINGS,
|
||||
Uri.parse("package:${context.packageName}")
|
||||
ListPreference(
|
||||
prefs.advanced.settingsLanguage,
|
||||
icon = Icons.Default.Language,
|
||||
title = stringRes(R.string.pref__advanced__settings_language__label),
|
||||
entries = listPrefEntries {
|
||||
listOf(
|
||||
"auto",
|
||||
"ar",
|
||||
"bg",
|
||||
"bs",
|
||||
"ca",
|
||||
"ckb",
|
||||
"cs",
|
||||
"da",
|
||||
"de",
|
||||
"el",
|
||||
"en",
|
||||
"eo",
|
||||
"es",
|
||||
"fa",
|
||||
"fi",
|
||||
"fr",
|
||||
"hr",
|
||||
"hu",
|
||||
"in",
|
||||
"it",
|
||||
"iw",
|
||||
"ja",
|
||||
"ko-KR",
|
||||
"ku",
|
||||
"lv-LV",
|
||||
"mk",
|
||||
"nds-DE",
|
||||
"nl",
|
||||
"no",
|
||||
"pl",
|
||||
"pt",
|
||||
"pt-BR",
|
||||
"ru",
|
||||
"sk",
|
||||
"sl",
|
||||
"sr",
|
||||
"sv",
|
||||
"tr",
|
||||
"uk",
|
||||
"zgh",
|
||||
"zh-CN",
|
||||
).map { languageTag ->
|
||||
if (languageTag == "auto") {
|
||||
entry(
|
||||
key = "auto",
|
||||
label = stringRes(R.string.settings__system_default),
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
ListPreference(
|
||||
prefs.advanced.settingsLanguage,
|
||||
icon = Icons.Default.Language,
|
||||
title = stringRes(R.string.pref__advanced__settings_language__label),
|
||||
entries = listPrefEntries {
|
||||
listOf(
|
||||
"auto",
|
||||
"ar",
|
||||
"bg",
|
||||
"bs",
|
||||
"ca",
|
||||
"ckb",
|
||||
"cs",
|
||||
"da",
|
||||
"de",
|
||||
"el",
|
||||
"en",
|
||||
"eo",
|
||||
"es",
|
||||
"fa",
|
||||
"fi",
|
||||
"fr",
|
||||
"hr",
|
||||
"hu",
|
||||
"in",
|
||||
"it",
|
||||
"iw",
|
||||
"ja",
|
||||
"ko-KR",
|
||||
"ku",
|
||||
"lv-LV",
|
||||
"mk",
|
||||
"nds-DE",
|
||||
"nl",
|
||||
"no",
|
||||
"pl",
|
||||
"pt",
|
||||
"pt-BR",
|
||||
"ru",
|
||||
"sk",
|
||||
"sl",
|
||||
"sr",
|
||||
"sv",
|
||||
"tr",
|
||||
"uk",
|
||||
"zgh",
|
||||
"zh-CN",
|
||||
).map { languageTag ->
|
||||
if (languageTag == "auto") {
|
||||
entry(
|
||||
key = "auto",
|
||||
label = stringRes(R.string.settings__system_default),
|
||||
)
|
||||
} else {
|
||||
val displayLanguageNamesIn by prefs.localization.displayLanguageNamesIn.observeAsState()
|
||||
val locale = FlorisLocale.fromTag(languageTag)
|
||||
entry(locale.languageTag(), when (displayLanguageNamesIn) {
|
||||
DisplayLanguageNamesIn.SYSTEM_LOCALE -> locale.displayName()
|
||||
DisplayLanguageNamesIn.NATIVE_LOCALE -> locale.displayName(locale)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
val displayLanguageNamesIn by prefs.localization.displayLanguageNamesIn.observeAsState()
|
||||
val locale = FlorisLocale.fromTag(languageTag)
|
||||
entry(locale.languageTag(), when (displayLanguageNamesIn) {
|
||||
DisplayLanguageNamesIn.SYSTEM_LOCALE -> locale.displayName()
|
||||
DisplayLanguageNamesIn.NATIVE_LOCALE -> locale.displayName(locale)
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.advanced.showAppIcon,
|
||||
icon = Icons.Default.Preview,
|
||||
|
||||
@@ -25,7 +25,6 @@ import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
|
||||
@OptIn(ExperimentalJetPrefDatastoreUi::class)
|
||||
@Composable
|
||||
@@ -72,22 +71,6 @@ fun ClipboardScreen() = FlorisScreen {
|
||||
stepIncrement = 5,
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.cleanUpOld isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.clipboard.autoCleanSensitive,
|
||||
title = stringRes(R.string.pref__clipboard__auto_clean_sensitive__label),
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
|
||||
visibleIf = { AndroidVersion.ATLEAST_API33_T },
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.clipboard.autoCleanSensitiveAfter,
|
||||
title = stringRes(R.string.pref__clipboard__auto_clean_sensitive_after__label),
|
||||
valueLabel = { pluralsRes(R.plurals.unit__seconds__written, it, "v" to it) },
|
||||
min = 0,
|
||||
max = 300,
|
||||
stepIncrement = 10,
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.autoCleanSensitive isEqualTo true },
|
||||
visibleIf = { AndroidVersion.ATLEAST_API33_T },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.clipboard.limitHistorySize,
|
||||
title = stringRes(R.string.pref__clipboard__limit_history_size__label),
|
||||
|
||||
@@ -254,20 +254,14 @@ class ClipboardManager(
|
||||
}
|
||||
|
||||
private fun enforceExpiryDate(clipHistory: ClipboardHistory) {
|
||||
val itemsToRemove = mutableSetOf<ClipboardItem>()
|
||||
if (prefs.clipboard.cleanUpOld.get()) {
|
||||
val nonPinnedItems = clipHistory.recent + clipHistory.other
|
||||
val expiryTime = System.currentTimeMillis() - (prefs.clipboard.cleanUpAfter.get() * 60 * 1000)
|
||||
itemsToRemove.addAll(nonPinnedItems.filter { it.creationTimestampMs < expiryTime })
|
||||
}
|
||||
if (prefs.clipboard.autoCleanSensitive.get()) {
|
||||
val sensitiveData = clipHistory.all.filter { it.isSensitive }
|
||||
val expiryTime = System.currentTimeMillis() - (prefs.clipboard.autoCleanSensitiveAfter.get() * 1000)
|
||||
itemsToRemove.addAll(sensitiveData.filter { it.creationTimestampMs < expiryTime })
|
||||
}
|
||||
if (itemsToRemove.isNotEmpty()) {
|
||||
ioScope.launch {
|
||||
clipHistoryDao?.delete(itemsToRemove.toList())
|
||||
val itemsToRemove = nonPinnedItems.filter { it.creationTimestampMs < expiryTime }
|
||||
if (itemsToRemove.isNotEmpty()) {
|
||||
ioScope.launch {
|
||||
clipHistoryDao?.delete(itemsToRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.database.getStringOrNull
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.AutoMigration
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Database
|
||||
@@ -39,13 +38,11 @@ import androidx.room.Entity
|
||||
import androidx.room.Insert
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Query
|
||||
import androidx.room.RenameColumn
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverter
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.Update
|
||||
import androidx.room.migration.AutoMigrationSpec
|
||||
import dev.patrickgold.florisboard.R
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
@@ -91,10 +88,8 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
|
||||
val isPinned: Boolean,
|
||||
val mimeTypes: Array<String>,
|
||||
@EncodeDefault
|
||||
@ColumnInfo(name = "is_sensitive", defaultValue = "0")
|
||||
val isSensitive: Boolean = false,
|
||||
@EncodeDefault
|
||||
@ColumnInfo(name= "is_remote_device", defaultValue = "0")
|
||||
val isRemoteDevice: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
@@ -242,7 +237,6 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
|
||||
if (uri != other.uri) return false
|
||||
if (creationTimestampMs != other.creationTimestampMs) return false
|
||||
if (!mimeTypes.contentEquals(other.mimeTypes)) return false
|
||||
if (isSensitive != other.isSensitive) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -254,7 +248,6 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
|
||||
result = 31 * result + (uri?.hashCode() ?: 0)
|
||||
result = 31 * result + creationTimestampMs.hashCode()
|
||||
result = 31 * result + mimeTypes.contentHashCode()
|
||||
result = 31 * result + isSensitive.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -339,30 +332,11 @@ interface ClipboardHistoryDao {
|
||||
fun deleteAllUnpinned()
|
||||
}
|
||||
|
||||
@Database(
|
||||
entities = [ClipboardItem::class],
|
||||
version = 4,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 2, to = 4),
|
||||
AutoMigration(from = 3, to = 4, spec = ClipboardHistoryDatabase.MIGRATE_3_TO_4::class),
|
||||
],
|
||||
)
|
||||
@Database(entities = [ClipboardItem::class], version = 3)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class ClipboardHistoryDatabase : RoomDatabase() {
|
||||
abstract fun clipboardItemDao(): ClipboardHistoryDao
|
||||
|
||||
@RenameColumn(
|
||||
tableName = CLIPBOARD_HISTORY_TABLE,
|
||||
fromColumnName = "isSensitive",
|
||||
toColumnName = "is_sensitive",
|
||||
)
|
||||
@RenameColumn(
|
||||
tableName = CLIPBOARD_HISTORY_TABLE,
|
||||
fromColumnName = "isRemoteDevice",
|
||||
toColumnName = "is_remote_device",
|
||||
)
|
||||
class MIGRATE_3_TO_4 : AutoMigrationSpec
|
||||
|
||||
companion object {
|
||||
fun new(context: Context): ClipboardHistoryDatabase {
|
||||
return Room
|
||||
|
||||
@@ -36,10 +36,10 @@ import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
|
||||
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyboard
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import org.florisboard.lib.android.isOrientationLandscape
|
||||
import dev.patrickgold.florisboard.lib.observeAsTransformingState
|
||||
import dev.patrickgold.florisboard.lib.util.ViewUtils
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.android.isOrientationLandscape
|
||||
|
||||
private val LocalKeyboardRowBaseHeight = staticCompositionLocalOf { 65.dp }
|
||||
private val LocalSmartbarHeight = staticCompositionLocalOf { 40.dp }
|
||||
@@ -133,7 +133,7 @@ fun ProvideKeyboardRowBaseHeight(content: @Composable () -> Unit) {
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalKeyboardRowBaseHeight provides ViewUtils.px2dp(baseRowHeight).dp,
|
||||
LocalSmartbarHeight provides ViewUtils.px2dp(smartbarHeight).toInt().dp,
|
||||
LocalSmartbarHeight provides ViewUtils.px2dp(smartbarHeight).dp,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
||||
@@ -55,8 +55,7 @@ fun RowScope.OneHandedPanel(
|
||||
Column(
|
||||
modifier = modifier
|
||||
.weight(weight)
|
||||
.snyggBackground(context, oneHandedPanelStyle)
|
||||
.height(FlorisImeSizing.imeUiHeight()),
|
||||
.snyggBackground(context, oneHandedPanelStyle),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
@@ -78,7 +77,7 @@ fun RowScope.OneHandedPanel(
|
||||
inputFeedbackController.keyPress()
|
||||
prefs.keyboard.oneHandedMode.set(panelSide)
|
||||
},
|
||||
modifier = Modifier.weight(1f).fillMaxWidth(),
|
||||
modifier = Modifier.height(FlorisImeSizing.keyboardUiHeight()).fillMaxWidth()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (panelSide == OneHandedMode.START) {
|
||||
|
||||
@@ -48,13 +48,12 @@ import dev.patrickgold.florisboard.lib.toIntOffset
|
||||
@Composable
|
||||
fun rememberPopupUiController(
|
||||
key1: Any?,
|
||||
key2: Any?,
|
||||
boundsProvider: (key: Key) -> FlorisRect,
|
||||
isSuitableForBasicPopup: (key: Key) -> Boolean,
|
||||
isSuitableForExtendedPopup: (key: Key) -> Boolean,
|
||||
): PopupUiController {
|
||||
val context = LocalContext.current
|
||||
return remember(key1, key2) {
|
||||
return remember(key1) {
|
||||
PopupUiController(context, boundsProvider, isSuitableForBasicPopup, isSuitableForExtendedPopup)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ fun TextKeyboardLayout(
|
||||
modifier: Modifier = Modifier,
|
||||
evaluator: ComputingEvaluator,
|
||||
isPreview: Boolean = false,
|
||||
isSmartbarKeyboard: Boolean = false,
|
||||
): Unit = with(LocalDensity.current) {
|
||||
val prefs by florisPreferenceModel()
|
||||
val context = LocalContext.current
|
||||
@@ -124,7 +125,7 @@ fun TextKeyboardLayout(
|
||||
|
||||
val controller = remember { TextKeyboardLayoutController(context) }.also {
|
||||
it.keyboard = keyboard
|
||||
if (glideEnabled && !isPreview && keyboard.mode == KeyboardMode.CHARACTERS) {
|
||||
if (glideEnabled && !isSmartbarKeyboard && !isPreview && keyboard.mode == KeyboardMode.CHARACTERS) {
|
||||
val keys = keyboard.keys().asSequence().toList()
|
||||
glideTypingManager.setLayout(keys)
|
||||
}
|
||||
@@ -160,7 +161,13 @@ fun TextKeyboardLayout(
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.keyboardUiHeight())
|
||||
.height(
|
||||
if (isSmartbarKeyboard) {
|
||||
FlorisImeSizing.smartbarHeight
|
||||
} else {
|
||||
FlorisImeSizing.keyboardUiHeight()
|
||||
}
|
||||
)
|
||||
.onGloballyPositioned { coords ->
|
||||
controller.size = coords.size.toSize()
|
||||
}
|
||||
@@ -189,7 +196,7 @@ fun TextKeyboardLayout(
|
||||
}
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
if (glideEnabled && glideShowTrail) {
|
||||
if (glideEnabled && glideShowTrail && !isSmartbarKeyboard) {
|
||||
val targetDist = 3.0f
|
||||
val radius = 20.0f
|
||||
|
||||
@@ -220,43 +227,57 @@ fun TextKeyboardLayout(
|
||||
val keyboardRowBaseHeight = FlorisImeSizing.keyboardRowBaseHeight
|
||||
|
||||
val desiredKey = remember(
|
||||
keyboard, keyboardWidth, keyboardHeight, keyMarginH, keyMarginV,
|
||||
keyboardRowBaseHeight, evaluator
|
||||
keyboard, isSmartbarKeyboard, keyboardWidth, keyboardHeight, keyMarginH, keyMarginV,
|
||||
keyboardRowBaseHeight
|
||||
) {
|
||||
TextKey(data = TextKeyData.UNSPECIFIED).also { desiredKey ->
|
||||
desiredKey.touchBounds.apply {
|
||||
width = keyboardWidth / 10f
|
||||
height = when (keyboard.mode) {
|
||||
KeyboardMode.CHARACTERS,
|
||||
KeyboardMode.NUMERIC_ADVANCED,
|
||||
KeyboardMode.SYMBOLS,
|
||||
KeyboardMode.SYMBOLS2 -> {
|
||||
(keyboardHeight / keyboard.rowCount)
|
||||
.coerceAtMost(keyboardRowBaseHeight.toPx() * 1.12f)
|
||||
if (isSmartbarKeyboard) {
|
||||
width = keyboardWidth / 8f
|
||||
height = keyboardHeight
|
||||
} else {
|
||||
width = keyboardWidth / 10f
|
||||
height = when (keyboard.mode) {
|
||||
KeyboardMode.CHARACTERS,
|
||||
KeyboardMode.NUMERIC_ADVANCED,
|
||||
KeyboardMode.SYMBOLS,
|
||||
KeyboardMode.SYMBOLS2 -> {
|
||||
(keyboardHeight / keyboard.rowCount)
|
||||
.coerceAtMost(keyboardRowBaseHeight.toPx() * 1.12f)
|
||||
}
|
||||
else -> keyboardRowBaseHeight.toPx()
|
||||
}
|
||||
else -> keyboardRowBaseHeight.toPx()
|
||||
}
|
||||
}
|
||||
desiredKey.visibleBounds.applyFrom(desiredKey.touchBounds).deflateBy(keyMarginH, keyMarginV)
|
||||
keyboard.layout(keyboardWidth, keyboardHeight, desiredKey, true)
|
||||
keyboard.layout(keyboardWidth, keyboardHeight, desiredKey, !isSmartbarKeyboard)
|
||||
}
|
||||
}
|
||||
|
||||
val fontSizeMultiplier = prefs.keyboard.fontSizeMultiplier()
|
||||
val popupUiController = rememberPopupUiController(
|
||||
key1 = keyboard,
|
||||
key2 = desiredKey,
|
||||
boundsProvider = { key ->
|
||||
val keyPopupWidth: Float
|
||||
val keyPopupHeight: Float
|
||||
when {
|
||||
configuration.isOrientationLandscape() -> {
|
||||
keyPopupWidth = desiredKey.visibleBounds.width * 1.0f
|
||||
keyPopupHeight = desiredKey.visibleBounds.height * 3.0f
|
||||
if (isSmartbarKeyboard) {
|
||||
keyPopupWidth = key.visibleBounds.width * 1.0f
|
||||
keyPopupHeight = desiredKey.visibleBounds.height * 3.0f * 1.2f
|
||||
} else {
|
||||
keyPopupWidth = desiredKey.visibleBounds.width * 1.0f
|
||||
keyPopupHeight = desiredKey.visibleBounds.height * 3.0f
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
keyPopupWidth = desiredKey.visibleBounds.width * 1.1f
|
||||
keyPopupHeight = desiredKey.visibleBounds.height * 2.5f
|
||||
if (isSmartbarKeyboard) {
|
||||
keyPopupWidth = key.visibleBounds.width * 1.1f
|
||||
keyPopupHeight = desiredKey.visibleBounds.height * 2.5f * 1.2f
|
||||
} else {
|
||||
keyPopupWidth = desiredKey.visibleBounds.width * 1.1f
|
||||
keyPopupHeight = desiredKey.visibleBounds.height * 2.5f
|
||||
}
|
||||
}
|
||||
}
|
||||
val keyPopupDiffX = (key.visibleBounds.width - keyPopupWidth) / 2.0f
|
||||
@@ -274,7 +295,7 @@ fun TextKeyboardLayout(
|
||||
val numeric = keyboard.mode == KeyboardMode.NUMERIC ||
|
||||
keyboard.mode == KeyboardMode.PHONE || keyboard.mode == KeyboardMode.PHONE2 ||
|
||||
keyboard.mode == KeyboardMode.NUMERIC_ADVANCED && keyType == KeyType.NUMERIC
|
||||
keyCode > KeyCode.SPACE && keyCode != KeyCode.CJK_SPACE && !numeric
|
||||
keyCode > KeyCode.SPACE && keyCode != KeyCode.MULTIPLE_CODE_POINTS && keyCode != KeyCode.CJK_SPACE && !numeric
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@@ -282,7 +303,7 @@ fun TextKeyboardLayout(
|
||||
isSuitableForExtendedPopup = { key ->
|
||||
if (key is TextKey) {
|
||||
val keyCode = key.computedData.code
|
||||
keyCode > KeyCode.SPACE && keyCode != KeyCode.CJK_SPACE || ExceptionsForKeyCodes.contains(keyCode)
|
||||
keyCode > KeyCode.SPACE && keyCode != KeyCode.MULTIPLE_CODE_POINTS && keyCode != KeyCode.CJK_SPACE || ExceptionsForKeyCodes.contains(keyCode)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@@ -295,7 +316,7 @@ fun TextKeyboardLayout(
|
||||
val debugShowTouchBoundaries by prefs.devtools.showKeyTouchBoundaries.observeAsState()
|
||||
for (textKey in keyboard.keys()) {
|
||||
TextKeyButton(
|
||||
textKey, evaluator, fontSizeMultiplier,
|
||||
textKey, evaluator, fontSizeMultiplier, isSmartbarKeyboard,
|
||||
debugShowTouchBoundaries,
|
||||
)
|
||||
}
|
||||
@@ -317,12 +338,13 @@ private fun TextKeyButton(
|
||||
key: TextKey,
|
||||
evaluator: ComputingEvaluator,
|
||||
fontSizeMultiplier: Float,
|
||||
isSmartbarKey: Boolean,
|
||||
debugShowTouchBoundaries: Boolean,
|
||||
) = with(LocalDensity.current) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val keyStyle = FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.Key,
|
||||
element = if (isSmartbarKey) FlorisImeUi.SmartbarActionKey else FlorisImeUi.Key,
|
||||
code = key.computedData.code,
|
||||
mode = evaluator.state.inputShiftState.value,
|
||||
isPressed = key.isPressed && key.isEnabled,
|
||||
|
||||
@@ -207,6 +207,11 @@ class ThemeManager(context: Context) {
|
||||
val bgDrawable = Icon.createWithResource(context, bgDrawableId).apply {
|
||||
setTint(bgColor.toArgb())
|
||||
}
|
||||
val singleIconChipStyle = ViewStyle.Builder().run {
|
||||
setBackground(bgDrawable)
|
||||
setPadding(0, 0, 0, 0)
|
||||
build()
|
||||
}
|
||||
val chipStyle = ViewStyle.Builder().run {
|
||||
setBackground(bgDrawable)
|
||||
setPadding(
|
||||
@@ -244,7 +249,7 @@ class ThemeManager(context: Context) {
|
||||
build()
|
||||
}
|
||||
val suggestionStyle = InlineSuggestionUi.newStyleBuilder().run {
|
||||
setSingleIconChipStyle(chipStyle)
|
||||
setSingleIconChipStyle(singleIconChipStyle)
|
||||
setChipStyle(chipStyle)
|
||||
setStartIconStyle(iconStyle)
|
||||
setEndIconStyle(iconStyle)
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.florisboard.lib.android.AndroidVersion
|
||||
object NetworkUtils {
|
||||
private val Ipv4Regex = """(?<Ipv4>(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))""".toRegex()
|
||||
private val Ipv6Regex = """(?<Ipv6>(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])))""".toRegex()
|
||||
private val HostRegex = """(?<Host>(?:[a-zA-Z0-9][a-zA-Z0-9\-]+[a-zA-Z0-9]\.)+[a-zA-Z]{2,}|$Ipv4Regex|$Ipv6Regex)""".toRegex()
|
||||
private val HostRegex = """(?<Host>(?:[a-zA-Z\-]+\.)+[a-zA-Z]{2,}|$Ipv4Regex|$Ipv6Regex)""".toRegex()
|
||||
private val TcpIpPortRegex = """(?<TcpIpPort>6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|(?<![0-9])[0-5]?[0-9]{1,4}(?![0-9]))""".toRegex()
|
||||
private val UrlRegex = """(?<Url>(?:(?:(?:https?:\/\/)?$HostRegex)|(?:https?:\/\/[a-zA-Z]+))(?::$TcpIpPortRegex)?(?:\/[\p{L}0-9.,;?'\\\/+&%$#=~_\-]*)?)""".toRegex()
|
||||
private val EmailRegex = """(?<Email>(?:[a-z0-9!#${'$'}%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#${'$'}%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@$HostRegex)""".toRegex()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package dev.patrickgold.florisboard.lib.util
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.widget.FrameLayout
|
||||
@@ -83,7 +84,7 @@ object ViewUtils {
|
||||
* @return A float value to represent px equivalent to dp depending on device density
|
||||
*/
|
||||
fun dp2px(dp: Float): Float {
|
||||
return dp * Resources.getSystem().displayMetrics.density
|
||||
return dp * (Resources.getSystem().displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,6 +96,6 @@ object ViewUtils {
|
||||
* @return A float value to represent dp equivalent to px value
|
||||
*/
|
||||
fun px2dp(px: Float): Float {
|
||||
return (px / Resources.getSystem().displayMetrics.density)
|
||||
return px / (Resources.getSystem().displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
unqualifiedResLocale=en-US
|
||||
@@ -114,13 +114,11 @@
|
||||
<string name="pref__input_feedback__haptic_vibration_duration__label" comment="Preference title">Duración de la vibración</string>
|
||||
<string name="pref__input_feedback__haptic_vibration_strength__label" comment="Preference title">Intensidá de la vibración</string>
|
||||
<string name="settings__keyboard__title" comment="Title of Keyboard preferences screen">Tecláu</string>
|
||||
<string name="pref__keyboard__number_row__label" comment="Preference title">Filera de númberos</string>
|
||||
<string name="pref__keyboard__number_row__summary" comment="Preference summary">Amuesa la filera de númberos enriba de la distribución de tecles</string>
|
||||
<string name="pref__keyboard__space_bar_mode__label" comment="Preference title">Etiqueta de la barra d\'espaciu</string>
|
||||
<string name="pref__keyboard__font_size_multiplier__label" comment="Preference title">Multiplicador del tamañu de la fonte</string>
|
||||
<string name="pref__keyboard__height_factor__label" comment="Preference title">Altor del tecláu</string>
|
||||
<string name="pref__keyboard__group_keypress__label" comment="Preference group title">Pulsación de tecles</string>
|
||||
<string name="pref__keyboard__popup_enabled__label" comment="Preference title">Visibilidá d\'indicadores emerxentes</string>
|
||||
<string name="pref__keyboard__popup_enabled__label" comment="Preference title">Visibilidá de los indicadores emerxentes</string>
|
||||
<string name="pref__keyboard__popup_enabled__summary" comment="Preference summary">Amuesa un indicador emerxente cuando primes una tecla</string>
|
||||
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Retrasu de la pulsación de tecles llonga</string>
|
||||
<!-- Smartbar strings -->
|
||||
@@ -264,7 +262,6 @@
|
||||
<string name="ext__validation__enter_valid_number">Introduz nun númberu válidu</string>
|
||||
<string name="ext__validation__enter_positive_number">Introduz un númberu positivu (>=0)</string>
|
||||
<string name="ext__update_box__search_for_updates">Buscar anovamientos</string>
|
||||
<string name="ext__addon_management_box__addon_manager_info">Pues remanar toles xeres rellacionaes cola importación, esportación, creación, personalización y desaniciu d\'estensiones pente\'l xestor de complementos centralizáu.</string>
|
||||
<string name="ext__addon_management_box__go_to_page">Dir a «{ext_home_title}»</string>
|
||||
<string name="ext__home__visit_store">Visitar la tienda de complementos</string>
|
||||
<!-- Action strings -->
|
||||
@@ -313,7 +310,6 @@
|
||||
<string name="enum__incognito_mode__dynamic_on_off" comment="Enum value label">Des/activación automática</string>
|
||||
<string name="enum__landscape_input_ui_mode__dynamically_show" comment="Enum value label">Apaición dinámica</string>
|
||||
<string name="enum__snygg_level__advanced" comment="Enum value label">Configuración avanzada</string>
|
||||
<string name="enum__space_bar_mode__nothing" comment="Enum value label">Ensin etiqueta</string>
|
||||
<string name="enum__space_bar_mode__current_language" comment="Enum value label">Llingua actual</string>
|
||||
<string name="enum__swipe_action__redo" comment="Enum value label">Refacer</string>
|
||||
<string name="enum__swipe_action__undo" comment="Enum value label">Desfacer</string>
|
||||
|
||||
@@ -578,8 +578,6 @@
|
||||
<string name="devtools__show_input_state_overlay__summary" comment="Summary of Show input cache overlay in Devtools">Blendet Eingabe-Overlay zur Fehlersuche ein</string>
|
||||
<string name="devtools__show_spelling_overlay__label" comment="Label of Show spelling overlay in Devtools">Zeige Rechtschreibprüfungs-Overlay</string>
|
||||
<string name="devtools__show_spelling_overlay__summary" comment="Summary of Show spelling overlay in Devtools">Blendet die aktuellen Ergebnisse der Rechtschreibprüfung fürs Debugging ein</string>
|
||||
<string name="devtools__show_inline_autofill_overlay__label">Zeige Autofill-Overlay</string>
|
||||
<string name="devtools__show_inline_autofill_overlay__summary">Blendet das aktuelle Autofill-Ergebnis für das Debugging ein</string>
|
||||
<string name="devtools__show_key_touch_boundaries__label" comment="Label of Show key touch boundaries in Devtools">Tastendruck-Umrandungen einschalten</string>
|
||||
<string name="devtools__show_key_touch_boundaries__summary" comment="Summary of Show key touch boundaries in Devtools">Umrandet die gedrückte Taste in Rot</string>
|
||||
<string name="devtools__show_drag_and_drop_helpers__label" comment="Label of Show drag and drop helpers in Devtools">Zeige Verschiebe-Hilfen</string>
|
||||
|
||||
@@ -26,11 +26,10 @@
|
||||
<string name="prefs__media__emoji_suggestion_enabled__summary" comment="Preference summary">Предлагать смайлики при наборе текста</string>
|
||||
<string name="prefs__media__emoji_suggestion_type" comment="Preference title">Тип триггера</string>
|
||||
<string name="prefs__media__emoji_suggestion_update_history" comment="Preference title">Обновить историю смайликов</string>
|
||||
<string name="prefs__media__emoji_suggestion_update_history__summary" comment="Preference summary">Принятие предложенных эмодзи добавляет их в историю эмодзи</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_show_name" comment="Preference title">Показать название смайлика</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_show_name__summary" comment="Preference summary">Предложения смайликов отображают их название рядом</string>
|
||||
<string name="prefs__media__emoji_suggestion_query_min_length" comment="Preference title">Минимальная длина запроса</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_max_count" comment="Preference title">Максимальное количество предложений</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_max_count" comment="Preference title">Максимальное количество кандидатов</string>
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Смайлы и эмоции</string>
|
||||
<string name="emoji__category__people_body" comment="Emoji category name">Люди и тело</string>
|
||||
@@ -42,7 +41,6 @@
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">Символы</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">Флаги</string>
|
||||
<string name="emoji__history__empty_message" comment="Message if the emoji history is empty">Недавно использованные эмодзи не найдены. Как только вы начнете использовать эмодзи, они автоматически будут появляться здесь.</string>
|
||||
<string name="emoji__history__phone_locked_message" comment="Message to show if phone is locked">Чтобы получить доступ к истории эмодзи, сначала разблокируйте ваше устройство.</string>
|
||||
<string name="emoji__history__usage_tip" comment="Feature discoverability for actions of emoji history">Совет: Долго нажимайте на эмодзи в истории эмодзи, чтобы закрепить или удалить их!</string>
|
||||
<string name="emoji__history__removal_success_message" comment="Toast message if user has used the delete action on an emoji in the emoji history">Удаление {emoji} из истории эмодзи</string>
|
||||
<string name="emoji__history__pinned">Закреплено</string>
|
||||
@@ -56,7 +54,6 @@
|
||||
<string name="quick_action__arrow_left__tooltip">Переместить курсор влево</string>
|
||||
<string name="quick_action__arrow_right" maxLength="12">Вправо</string>
|
||||
<string name="quick_action__arrow_right__tooltip">Переместить курсор вправо</string>
|
||||
<string name="quick_action__clipboard_clear_primary_clip" maxLength="12">Очистить</string>
|
||||
<string name="quick_action__clipboard_clear_primary_clip__tooltip">Выполнить очистку основного клипа буфера обмена</string>
|
||||
<string name="quick_action__clipboard_copy" maxLength="12">Копировать</string>
|
||||
<string name="quick_action__clipboard_copy__tooltip">Выполнить копирование из буфера обмена</string>
|
||||
@@ -583,7 +580,6 @@
|
||||
<string name="devtools__show_input_state_overlay__summary" comment="Summary of Show input cache overlay in Devtools">Показывать наложением текущее состояние ввода для отладки</string>
|
||||
<string name="devtools__show_spelling_overlay__label" comment="Label of Show spelling overlay in Devtools">Показывать орфографию наложением</string>
|
||||
<string name="devtools__show_spelling_overlay__summary" comment="Summary of Show spelling overlay in Devtools">Показывать наложением текущие результаты проверки орфографии для отладки</string>
|
||||
<string name="devtools__show_inline_autofill_overlay__label">Показать встроенное окно автозаполнения</string>
|
||||
<string name="devtools__show_inline_autofill_overlay__summary">Отображает текущие результаты автозаполнения строки для отладки</string>
|
||||
<string name="devtools__show_key_touch_boundaries__label" comment="Label of Show key touch boundaries in Devtools">Показывать границы нажатия клавиш</string>
|
||||
<string name="devtools__show_key_touch_boundaries__summary" comment="Summary of Show key touch boundaries in Devtools">Обводить границы нажатия клавиш красным контуром</string>
|
||||
@@ -763,14 +759,10 @@
|
||||
<string name="enum__display_language_names_in__system_locale__description" comment="Enum value description">Подписи в приложении и интерфейсе клавиатуры указаны на языке, используемом в системе по умолчанию</string>
|
||||
<string name="enum__display_language_names_in__native_locale" comment="Enum value label">В исходном виде</string>
|
||||
<string name="enum__display_language_names_in__native_locale__description" comment="Enum value description">Подписи в приложении и интерфейсе клавиатуры приводятся на родных языках</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_prepend" comment="Enum value label">Автоматическая сортировка (добавление в начало)</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_prepend__description" comment="Enum value description">Автоматическое изменение порядка расположения эмодзи в зависимости от их использования. Новые эмодзи добавляются в начало.</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_append" comment="Enum value label">Автоматическая сортировка (добавление в конец)</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_append__description" comment="Enum value description">Автоматическое изменение порядка расположения эмодзи в зависимости от их использования. Новые эмодзи добавляются в конец.</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_prepend" comment="Enum value label">Ручная сортировка (добавление в начало)</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_prepend__description" comment="Enum value description">Не происходит автоматической перестановки эмодзи в зависимости от их использования.
|
||||
Новые эмодзи добавляются в начало.</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_append" comment="Enum value label">Ручная сортировка (добавление в конец)</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_append__description" comment="Enum value description">Не происходит автоматической перестановки эмодзи в зависимости от их использования.
|
||||
Новые эмодзи добавляются в конец.</string>
|
||||
<string name="enum__emoji_skin_tone__default" comment="Enum value label">Цвет кожи {emoji} по умолчанию</string>
|
||||
@@ -784,9 +776,7 @@
|
||||
<string name="enum__emoji_hair_style__curly_hair" comment="Enum value label">{emoji} Вьющиеся волосы</string>
|
||||
<string name="enum__emoji_hair_style__white_hair" comment="Enum value label">{emoji} Светлые волосы</string>
|
||||
<string name="enum__emoji_hair_style__bald" comment="Enum value label">{emoji} Без волос</string>
|
||||
<string name="enum__emoji_suggestion_type__leading_colon">Начальное двоеточие</string>
|
||||
<string name="enum__emoji_suggestion_type__leading_colon__description" comment="Keep the :emoji_name while translating, this is a syntax guide">Предлагайте эмодзи, используя синтаксис :emoji_name</string>
|
||||
<string name="enum__emoji_suggestion_type__inline_text">Встроенный текст</string>
|
||||
<string name="enum__emoji_suggestion_type__inline_text__description">Предлагает эмодзи, просто набрав название эмодзи в виде слова</string>
|
||||
<string name="enum__extended_actions_placement__above_candidates" comment="Enum value label">Вышестоящие предложение</string>
|
||||
<string name="enum__extended_actions_placement__above_candidates__description" comment="Enum value description">Размещает строку расширенных действий между пользовательским интерфейсом приложения и строкой предложений</string>
|
||||
@@ -911,16 +901,4 @@
|
||||
<item quantity="many">{v} элементов</item>
|
||||
<item quantity="other">{v} элементов</item>
|
||||
</plurals>
|
||||
<plurals name="unit__characters__written">
|
||||
<item quantity="one">{v} символ</item>
|
||||
<item quantity="few">{v} символа</item>
|
||||
<item quantity="many">{v} символов</item>
|
||||
<item quantity="other">{v} символов</item>
|
||||
</plurals>
|
||||
<plurals name="unit__candidates__written">
|
||||
<item quantity="one">{v} предложение</item>
|
||||
<item quantity="few">{v} предложения</item>
|
||||
<item quantity="many">{v} предложений</item>
|
||||
<item quantity="other">{v} предложений</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -595,8 +595,6 @@
|
||||
<string name="pref__clipboard__enable_clipboard_history__summary">Retain clipboard items for quick access</string>
|
||||
<string name="pref__clipboard__clean_up_old__label">Clean up old items</string>
|
||||
<string name="pref__clipboard__clean_up_after__label">Clean up old items after</string>
|
||||
<string name="pref__clipboard__auto_clean_sensitive__label">Auto clean sensitive items</string>
|
||||
<string name="pref__clipboard__auto_clean_sensitive_after__label">Auto clean sensitive items after</string>
|
||||
<string name="pref__clipboard__limit_history_size__label">Limit history size</string>
|
||||
<string name="pref__clipboard__max_history_size__label">Max history size</string>
|
||||
<string name="pref__clipboard__clear_primary_clip_deletes_last_item__label">Clear primary clip affects history</string>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
|
||||
</style>
|
||||
|
||||
<style name="FlorisAppTheme" parent="Theme.AppCompat.NoActionBar">
|
||||
<style name="FlorisAppTheme" parent="android:style/Theme.Material.NoActionBar">
|
||||
<item name="android:colorPrimary">@color/colorPrimary</item>
|
||||
<item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="android:colorAccent">@color/colorAccent</item>
|
||||
@@ -31,7 +31,7 @@
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
|
||||
</style>
|
||||
|
||||
<style name="FlorisAppTheme.Transparent" parent="Theme.AppCompat.NoActionBar">
|
||||
<style name="FlorisAppTheme.Transparent" parent="android:style/Theme.Material.NoActionBar">
|
||||
<item name="android:colorPrimary">@android:color/transparent</item>
|
||||
<item name="android:colorPrimaryDark">@android:color/transparent</item>
|
||||
<item name="android:colorAccent">@android:color/transparent</item>
|
||||
|
||||
@@ -14,7 +14,6 @@ androidx-material-icons = "1.7.3"
|
||||
androidx-navigation = "2.8.1"
|
||||
androidx-profileinstaller = "1.4.0"
|
||||
androidx-room = "2.6.1"
|
||||
appcompat = "1.7.0"
|
||||
cache4k = "0.7.0"
|
||||
kotlin = "2.0.20"
|
||||
kotlinx-coroutines = "1.8.1"
|
||||
@@ -54,7 +53,6 @@ androidx-navigation-compose = { module = "androidx.navigation:navigation-compose
|
||||
androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "androidx-profileinstaller" }
|
||||
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" }
|
||||
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
cache4k = { module = "io.github.reactivecircus.cache4k:cache4k", version.ref = "cache4k" }
|
||||
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
|
||||
|
||||
25
libnative/flest/Cargo.lock
generated
Normal file
25
libnative/flest/Cargo.lock
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "flest"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fxhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
9
libnative/flest/Cargo.toml
Normal file
9
libnative/flest/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "flest"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
fxhash = "0.2.1"
|
||||
102
libnative/flest/src/dyntrie.rs
Normal file
102
libnative/flest/src/dyntrie.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use fxhash::FxHashMap;
|
||||
|
||||
#[derive(Default)]
|
||||
struct DynTrieNode<V> where V: Default {
|
||||
children: FxHashMap<char, Box<DynTrieNode<V>>>,
|
||||
value: Option<V>,
|
||||
}
|
||||
|
||||
impl<V> DynTrieNode<V> where V: Default {
|
||||
fn for_each_recursive<'a, F>(&'a self, current_word: &mut Vec<char>, f: &mut F)
|
||||
where F: FnMut(&[char], &'a V) {
|
||||
if let Some(value) = &self.value {
|
||||
f(¤t_word, value);
|
||||
}
|
||||
for (letter, node) in &self.children {
|
||||
current_word.push(*letter);
|
||||
node.for_each_recursive(current_word, f);
|
||||
current_word.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DynTrie<V> where V: Default {
|
||||
root: DynTrieNode<V>,
|
||||
}
|
||||
|
||||
impl<V> DynTrie<V>
|
||||
where V: Default {
|
||||
pub fn find(&self, word: &[char]) -> Option<&V> {
|
||||
let mut current_node = &self.root;
|
||||
for letter in word {
|
||||
match current_node.children.get(letter) {
|
||||
Some(node) => current_node = node,
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
return current_node.value.as_ref();
|
||||
}
|
||||
|
||||
fn str_fuzzy_match_whole(str1: &[char], str2: &[char]) -> f64 {
|
||||
let len1 = str1.len();
|
||||
let len2 = str2.len();
|
||||
let max_len = std::cmp::max(len1, len2);
|
||||
let mut score: f64 = 0.0;
|
||||
let mut penalty: f64 = 0.0;
|
||||
for i in 0..max_len {
|
||||
let ch1 = str1.get(i).unwrap_or(&' ');
|
||||
let ch2 = str2.get(i).unwrap_or(&' ');
|
||||
if ch1 == ch2 {
|
||||
score += 1.0;
|
||||
} else if ch1.to_lowercase().eq(ch2.to_lowercase()) {
|
||||
score += 0.5;
|
||||
} else {
|
||||
penalty += if i == 0 { 2.0 } else { 1.0 };
|
||||
}
|
||||
}
|
||||
return f64::max(0.0, score - penalty)
|
||||
}
|
||||
|
||||
// TODO: optimization: we do not need to iterate over all
|
||||
// the trie, we can predict if the score will never be >= 0
|
||||
// and skip the whole subtree
|
||||
pub fn find_many(&self, word: &[char]) -> Vec<(Vec<char>, &V)> {
|
||||
let mut results = Vec::new();
|
||||
self.for_each(&mut |current_word, value| {
|
||||
let score = Self::str_fuzzy_match_whole(word, current_word);
|
||||
if score > 0.0 {
|
||||
results.push((current_word.to_owned(), value));
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
pub fn find_or_insert(&mut self, word: &[char], value: V) -> &mut V {
|
||||
let mut current_node = &mut self.root;
|
||||
for letter in word {
|
||||
current_node = current_node.children.entry(*letter)
|
||||
.or_insert_with(|| Box::new(DynTrieNode::default()));
|
||||
}
|
||||
if current_node.value.is_none() {
|
||||
current_node.value = Some(value);
|
||||
}
|
||||
return current_node.value.as_mut().unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn insert(&mut self, word: &[char], value: V) {
|
||||
let mut current_node = &mut self.root;
|
||||
for letter in word {
|
||||
current_node = current_node.children.entry(*letter)
|
||||
.or_insert_with(|| Box::new(DynTrieNode::default()));
|
||||
}
|
||||
current_node.value = Some(value);
|
||||
}
|
||||
|
||||
pub fn for_each<'a, F>(&'a self, f: &mut F)
|
||||
where F: FnMut(&[char], &'a V) {
|
||||
let mut current_word: Vec<char> = Vec::new();
|
||||
self.root.for_each_recursive(&mut current_word, f);
|
||||
}
|
||||
}
|
||||
4
libnative/flest/src/lib.rs
Normal file
4
libnative/flest/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
mod dyntrie;
|
||||
mod ngrammodel;
|
||||
|
||||
pub use ngrammodel::*;
|
||||
212
libnative/flest/src/ngrammodel.rs
Normal file
212
libnative/flest/src/ngrammodel.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::dyntrie::DynTrie;
|
||||
|
||||
#[derive(Default)]
|
||||
struct NgramModelNode {
|
||||
children: DynTrie<Box<NgramModelNode>>,
|
||||
time: u64,
|
||||
usage: u64,
|
||||
}
|
||||
|
||||
impl NgramModelNode {
|
||||
fn find(&self, ngram: &[&str]) -> Option<&NgramModelNode> {
|
||||
if ngram.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let token: Vec<char> = ngram[0].chars().collect();
|
||||
let child = self.children.find(&token);
|
||||
if child.is_none() {
|
||||
return None;
|
||||
}
|
||||
let child = child.unwrap();
|
||||
if ngram.len() == 1 {
|
||||
return Some(child);
|
||||
}
|
||||
return child.find(&ngram[1..]);
|
||||
}
|
||||
|
||||
fn find_many(&self, ngram: &[&str]) -> Vec<(Vec<char>, &NgramModelNode)> {
|
||||
if ngram.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let token: Vec<char> = ngram[0].chars().collect();
|
||||
let ret = self.children.find_many(&token);
|
||||
if ngram.len() == 1 {
|
||||
return ret
|
||||
.into_iter()
|
||||
.map(|node| (node.0, node.1.as_ref()))
|
||||
.collect();
|
||||
}
|
||||
let mut ret2 = Vec::new();
|
||||
for (_, child) in &ret {
|
||||
ret2.extend(child.find_many(&ngram[1..]));
|
||||
}
|
||||
return ret2;
|
||||
}
|
||||
|
||||
fn train(&mut self, ngram: &[&str], current_time: u64) {
|
||||
if ngram.is_empty() {
|
||||
panic!("ngram must not be empty");
|
||||
}
|
||||
let token: Vec<char> = ngram[0].chars().collect();
|
||||
let child = self.children.find_or_insert(&token, Box::new(NgramModelNode::default()));
|
||||
if ngram.len() == 1 {
|
||||
if current_time != 0 {
|
||||
child.time = current_time;
|
||||
}
|
||||
child.usage += 1;
|
||||
} else {
|
||||
child.train(&ngram[1..], current_time);
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_print(&self, _indent: usize) {
|
||||
// println!("{}{}{}", " ".repeat(indent), self.token, if self.time > 0 { "*" } else { "" });
|
||||
// for child in &self.children {
|
||||
// child.debug_print(indent + 1);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NgramModel {
|
||||
root: NgramModelNode,
|
||||
time: u64,
|
||||
}
|
||||
|
||||
impl NgramModel {
|
||||
#[allow(dead_code)]
|
||||
fn find(&self, ngram: &[&str]) -> Option<&NgramModelNode> {
|
||||
self.root.find(ngram)
|
||||
}
|
||||
|
||||
fn find_many(&self, ngram: &[&str]) -> Vec<(Vec<char>, &NgramModelNode)> {
|
||||
self.root.find_many(ngram)
|
||||
}
|
||||
|
||||
pub fn train_dataset(&mut self, token_list: &[&str]) {
|
||||
self.root.train(token_list, 0);
|
||||
}
|
||||
|
||||
pub fn train_input(&mut self, token_list: &[&str]) {
|
||||
self.time += 1;
|
||||
self.root.train(token_list, self.time);
|
||||
}
|
||||
|
||||
pub fn debug_print(&self) {
|
||||
self.root.debug_print(0);
|
||||
}
|
||||
|
||||
pub fn predict(&self, history: &Vec<&str>) -> Vec<(String, f64)> {
|
||||
let mut tmin = u64::MAX;
|
||||
let mut tmax = u64::MIN;
|
||||
let mut umin = u64::MAX;
|
||||
let mut umax = u64::MIN;
|
||||
let nmin = 1;
|
||||
let nmax = 3;
|
||||
let mut candidate_nodes: Vec<(Vec<char>, &NgramModelNode, f64)> = Vec::new();
|
||||
|
||||
let user_input_word = history.last().unwrap_or(&"");
|
||||
|
||||
for n in nmin..=std::cmp::min(history.len(), nmax) {
|
||||
let nweight = 1.0 - (nmax - n) as f64 * 0.1;
|
||||
let ngram = &history[history.len() - n..history.len() - 1];
|
||||
let nodes = self.find_many(ngram);
|
||||
for (_, node) in nodes {
|
||||
node.children.for_each(&mut |curr_word, child| {
|
||||
candidate_nodes.push((curr_word.to_owned(), child, nweight));
|
||||
tmin = tmin.min(child.time);
|
||||
tmax = tmax.max(child.time);
|
||||
umin = umin.min(child.usage);
|
||||
umax = umax.max(child.usage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
candidate_nodes = candidate_nodes
|
||||
.into_iter()
|
||||
.map(|(word, node, nweight)| {
|
||||
(
|
||||
word,
|
||||
node,
|
||||
nweight
|
||||
* norm_weight(node.time, tmin, tmax)
|
||||
* norm_weight(node.usage, umin, umax),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !user_input_word.is_empty() {
|
||||
let user_input_word: Vec<char> = user_input_word.chars().collect();
|
||||
let mut filtered_nodes = Vec::new();
|
||||
for (word, node, weight) in candidate_nodes {
|
||||
let score_len = std::cmp::min(
|
||||
(word.len() + user_input_word.len()) / 2,
|
||||
user_input_word.len(),
|
||||
) as f64;
|
||||
let score = str_fuzzy_match_live(&word, &user_input_word);
|
||||
if score > 0.0 {
|
||||
let new_weight = 0.95 * (score / score_len) + 0.05 * weight;
|
||||
filtered_nodes.push((word, node, new_weight));
|
||||
}
|
||||
}
|
||||
self.root.children.for_each(&mut |word, node| {
|
||||
let score_len = std::cmp::min(
|
||||
(word.len() + user_input_word.len()) / 2,
|
||||
user_input_word.len(),
|
||||
) as f64;
|
||||
let score = str_fuzzy_match_live(&word, &user_input_word);
|
||||
if score > 0.0 {
|
||||
let new_weight = 0.75 * (score / score_len) + 0.25 * 0.0;
|
||||
filtered_nodes.push((word.to_owned(), node, new_weight));
|
||||
}
|
||||
});
|
||||
candidate_nodes = filtered_nodes;
|
||||
}
|
||||
|
||||
candidate_nodes.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap());
|
||||
|
||||
let mut predictions: HashMap<String, f64> = HashMap::new();
|
||||
for (word, _, weight) in candidate_nodes {
|
||||
predictions
|
||||
.entry(word.iter().collect())
|
||||
.or_insert(weight);
|
||||
}
|
||||
|
||||
let mut predictions_vec: Vec<(String, f64)> = predictions.into_iter().collect();
|
||||
predictions_vec.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
predictions_vec.into_iter().take(8).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn norm_weight(x: u64, xmin: u64, xmax: u64) -> f64 {
|
||||
if x <= xmin {
|
||||
return 0.0;
|
||||
}
|
||||
if x >= xmax {
|
||||
return 1.0;
|
||||
}
|
||||
let xnorm = (x - xmin) as f64 / (xmax - xmin) as f64;
|
||||
return 2.0 * xnorm - xnorm.powi(2);
|
||||
}
|
||||
|
||||
fn str_fuzzy_match_live(word: &[char], current_word: &[char]) -> f64 {
|
||||
//let len1 = word.len();
|
||||
let len2 = current_word.len();
|
||||
let mut score = 0.0;
|
||||
let mut penalty: f64 = 0.0;
|
||||
for i in 0..len2 {
|
||||
let ch1 = word.get(i).unwrap_or(&' ');
|
||||
let ch2 = current_word.get(i).unwrap_or(&' ');
|
||||
if ch1 == ch2 {
|
||||
score += 1.0;
|
||||
} else if ch1.to_lowercase().eq(ch2.to_lowercase()) {
|
||||
score += 0.9;
|
||||
} else {
|
||||
penalty += if i == 0 { 2.0 } else { 1.0 };
|
||||
}
|
||||
}
|
||||
return f64::max(0.0, score - 0.125 * penalty.powi(2));
|
||||
}
|
||||
354
libnative/textutils/Cargo.lock
generated
Normal file
354
libnative/textutils/Cargo.lock
generated
Normal file
@@ -0,0 +1,354 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core_maths"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locid",
|
||||
"icu_provider_macros",
|
||||
"stable_deref_trait",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider_macros"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_segmenter"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locid",
|
||||
"icu_provider",
|
||||
"icu_segmenter_data",
|
||||
"utf8_iter",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_segmenter_data"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
|
||||
[[package]]
|
||||
name = "linkify"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textutils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"icu_segmenter",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"linkify",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
13
libnative/textutils/Cargo.toml
Normal file
13
libnative/textutils/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "textutils"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
icu_segmenter = "1.5.0"
|
||||
itertools = "0.13.0"
|
||||
lazy_static = "1.5.0"
|
||||
linkify = "0.10.0"
|
||||
regex = "1.10.5"
|
||||
20
libnative/textutils/src/filter.rs
Normal file
20
libnative/textutils/src/filter.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use lazy_static::lazy_static;
|
||||
use linkify::{self, LinkFinder};
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static! {
|
||||
static ref LINK_FINDER: LinkFinder = LinkFinder::new();
|
||||
static ref REDDIT_REGEX: Regex = Regex::new(r"\/?(r\/[a-zA-Z0-9_]{3}[a-zA-Z0-9_]{0,18}|u\/[a-zA-Z0-9_-]{3}[a-zA-Z0-9_-]{0,17})").unwrap();
|
||||
}
|
||||
|
||||
pub fn preprocess_auto(text: &str) -> String {
|
||||
let mut cleaned_text = String::new();
|
||||
let mut begin_cleaned_index = 0;
|
||||
for span in LINK_FINDER.links(text) {
|
||||
cleaned_text.push_str(&text[begin_cleaned_index..span.start()]);
|
||||
begin_cleaned_index = span.end();
|
||||
}
|
||||
cleaned_text.push_str(&text[begin_cleaned_index..]);
|
||||
cleaned_text = REDDIT_REGEX.replace_all(&cleaned_text, "").to_string();
|
||||
return cleaned_text;
|
||||
}
|
||||
52
libnative/textutils/src/lib.rs
Normal file
52
libnative/textutils/src/lib.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
mod filter;
|
||||
mod segment;
|
||||
|
||||
pub use filter::*;
|
||||
pub use segment::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use icu_segmenter::{SentenceSegmenter, WordSegmenter};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn segment_sentences_simple() {
|
||||
let text = "Hello, world! How are you? I'm fine.";
|
||||
let segmenter = SentenceSegmenter::new();
|
||||
let sentences = split_sentences(text, &segmenter);
|
||||
assert_eq!(&sentences, &["Hello, world!", "How are you?", "I'm fine."]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn segment_words_simple() {
|
||||
let text = "Hello, world! How are you? I'm fine.";
|
||||
let segmenter = WordSegmenter::new_auto();
|
||||
let words = split_words(text, &segmenter);
|
||||
assert_eq!(&words, &["Hello", "world", "How", "are", "you", "I'm", "fine"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preprocess_auto_simple() {
|
||||
let text = "Hello, world! How are you? I'm fine. https://example.com and more";
|
||||
let cleaned_text = preprocess_auto(text);
|
||||
assert_eq!(&cleaned_text, "Hello, world! How are you? I'm fine. and more");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preprocess_reddit_ids() {
|
||||
let text = "have a look at r/cats, user u/example posed a cute cat in there";
|
||||
let cleaned_text = preprocess_auto(text);
|
||||
assert_eq!(&cleaned_text, "have a look at , user posed a cute cat in there");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preprocess_url_markdown() {
|
||||
let text = "You can find an example [in the documentation](https://example.com) or on GitHub";
|
||||
let cleaned_text = preprocess_auto(text);
|
||||
assert_eq!(&cleaned_text, "You can find an example [in the documentation]() or on GitHub");
|
||||
let segmenter = WordSegmenter::new_auto();
|
||||
let words = split_words(&cleaned_text, &segmenter);
|
||||
assert_eq!(&words, &["You", "can", "find", "an", "example", "in", "the", "documentation", "or", "on", "GitHub"]);
|
||||
}
|
||||
}
|
||||
63
libnative/textutils/src/segment.rs
Normal file
63
libnative/textutils/src/segment.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use icu_segmenter::{GraphemeClusterSegmenter, SentenceSegmenter, WordSegmenter};
|
||||
use itertools::Itertools;
|
||||
|
||||
pub struct IcuSegmenterCache {
|
||||
sentence_segmenter: SentenceSegmenter,
|
||||
word_segmenter: WordSegmenter,
|
||||
grapheme_cluster_segmenter: GraphemeClusterSegmenter,
|
||||
}
|
||||
|
||||
impl IcuSegmenterCache {
|
||||
pub fn new_auto() -> Self {
|
||||
let sentence_segmenter = SentenceSegmenter::new();
|
||||
let word_segmenter = WordSegmenter::new_auto();
|
||||
let grapheme_cluster_segmenter = GraphemeClusterSegmenter::new();
|
||||
return Self {
|
||||
sentence_segmenter,
|
||||
word_segmenter,
|
||||
grapheme_cluster_segmenter,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn split_sentences<'t>(&self, text: &'t str) -> Vec<&'t str> {
|
||||
return split_sentences(text, &self.sentence_segmenter);
|
||||
}
|
||||
|
||||
pub fn split_words<'t>(&self, text: &'t str) -> Vec<&'t str> {
|
||||
return split_words(text, &self.word_segmenter);
|
||||
}
|
||||
|
||||
pub fn split_grapheme_clusters<'t>(&self, text: &'t str) -> Vec<&'t str> {
|
||||
return split_grapheme_clusters(text, &self.grapheme_cluster_segmenter);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split_sentences<'t>(text: &'t str, segmenter: &SentenceSegmenter) -> Vec<&'t str> {
|
||||
let sentences: Vec<&str> = segmenter
|
||||
.segment_str(text)
|
||||
.tuple_windows()
|
||||
.map(|(i, j)| text[i..j].trim())
|
||||
.filter(|sentence| !sentence.is_empty())
|
||||
.collect();
|
||||
return sentences;
|
||||
}
|
||||
|
||||
pub fn split_words<'t>(text: &'t str, segmenter: &WordSegmenter) -> Vec<&'t str> {
|
||||
let words: Vec<&str> = segmenter
|
||||
.segment_str(text)
|
||||
.iter_with_word_type()
|
||||
.tuple_windows()
|
||||
.filter(|(_, (_, segment_type))| segment_type.is_word_like())
|
||||
.map(|((i, _), (j, _))| &text[i..j])
|
||||
.collect();
|
||||
return words;
|
||||
}
|
||||
|
||||
pub fn split_grapheme_clusters<'t>(text: &'t str, segmenter: &GraphemeClusterSegmenter) -> Vec<&'t str> {
|
||||
let grapheme_clusters: Vec<&str> = segmenter
|
||||
.segment_str(text)
|
||||
.tuple_windows()
|
||||
.map(|(i, j)| &text[i..j])
|
||||
.collect();
|
||||
return grapheme_clusters;
|
||||
}
|
||||
509
utils/flesttools/Cargo.lock
generated
Normal file
509
utils/flesttools/Cargo.lock
generated
Normal file
@@ -0,0 +1,509 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core_maths"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "flest"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fxhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flesttools"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"flest",
|
||||
"pancurses",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"textutils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locid",
|
||||
"icu_provider_macros",
|
||||
"stable_deref_trait",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider_macros"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_segmenter"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locid",
|
||||
"icu_provider",
|
||||
"icu_segmenter_data",
|
||||
"utf8_iter",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_segmenter_data"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
|
||||
[[package]]
|
||||
name = "linkify"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "ncurses"
|
||||
version = "5.101.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e2c5d34d72657dc4b638a1c25d40aae81e4f1c699062f72f467237920752032"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pancurses"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0352975c36cbacb9ee99bfb709b9db818bed43af57751797f8633649759d13db"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"ncurses",
|
||||
"pdcurses-sys",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pdcurses-sys"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "084dd22796ff60f1225d4eb6329f33afaf4c85419d51d440ab6b8c6f4529166b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textutils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"icu_segmenter",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"linkify",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
13
utils/flesttools/Cargo.toml
Normal file
13
utils/flesttools/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "flesttools"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
flest = { path = "../../libnative/flest" }
|
||||
textutils = { path = "../../libnative/textutils" }
|
||||
pancurses = { version = "0.17.0", features = ["wide"] }
|
||||
serde = "1.0.203"
|
||||
serde_json = "1.0.120"
|
||||
148
utils/flesttools/src/main.rs
Normal file
148
utils/flesttools/src/main.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use flest::NgramModel;
|
||||
use textutils::IcuSegmenterCache;
|
||||
use pancurses::Input;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
|
||||
const TOKEN_SENTENCE_SEPARATOR: &str = "\\sep";
|
||||
|
||||
fn tokenize_text(text: &str) -> Vec<&str> {
|
||||
let segmenters = IcuSegmenterCache::new_auto();
|
||||
let sentences = segmenters.split_sentences(text);
|
||||
let mut tokens: Vec<&str> = Vec::new();
|
||||
|
||||
tokens.push(TOKEN_SENTENCE_SEPARATOR);
|
||||
for sentence in sentences {
|
||||
let words = segmenters.split_words(sentence);
|
||||
for word in words {
|
||||
tokens.push(word);
|
||||
}
|
||||
tokens.push(TOKEN_SENTENCE_SEPARATOR);
|
||||
}
|
||||
|
||||
//println!("Tokens: {:?}", tokens);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
fn train_model(text: &str, model: &mut NgramModel) {
|
||||
let text = textutils::preprocess_auto(text);
|
||||
let text = text.trim();
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
let tokens = tokenize_text(&text);
|
||||
//println!("Tokens: {:?}", tokens);
|
||||
let n_values = [2, 3, 4];
|
||||
|
||||
for &n in &n_values {
|
||||
if n > tokens.len() {
|
||||
continue;
|
||||
}
|
||||
for i in 0..tokens.len() - n + 1 {
|
||||
model.train_dataset(&tokens[i..(i + n)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn train_from_plain_text(path: &str, model: &mut NgramModel) {
|
||||
let text = fs::read_to_string(path).expect("Failed to read file");
|
||||
train_model(&text, model);
|
||||
}
|
||||
|
||||
fn train_from_reddit_comments(path: &str, model: &mut NgramModel) {
|
||||
let file = fs::File::open(path).expect("Failed to open file");
|
||||
let reader = BufReader::new(file);
|
||||
let mut line_count = 0;
|
||||
for line in reader.lines() {
|
||||
if let Ok(line) = line {
|
||||
let json: serde_json::Value = serde_json::from_str(&line).expect("Failed to parse JSON");
|
||||
|
||||
if let Some(author) = json.get("author").and_then(|it| it.as_str()) {
|
||||
if author == "AutoModerator" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(body) = json.get("body").and_then(|it| it.as_str()) {
|
||||
train_model(body, model);
|
||||
}
|
||||
}
|
||||
line_count += 1;
|
||||
if line_count > 10000 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 2 {
|
||||
eprintln!("Usage: {} <file_path>", args[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
let path = &args[1];
|
||||
let mut model = NgramModel::default();
|
||||
|
||||
if path.ends_with(".reddit.jsonl") {
|
||||
train_from_reddit_comments(path, &mut model);
|
||||
} else {
|
||||
train_from_plain_text(path, &mut model);
|
||||
}
|
||||
|
||||
let window = pancurses::initscr();
|
||||
let mut input_text = String::new();
|
||||
|
||||
pancurses::noecho();
|
||||
window.keypad(true);
|
||||
loop {
|
||||
let mut words: Vec<&str> = input_text.split_whitespace().collect();
|
||||
words.insert(0, TOKEN_SENTENCE_SEPARATOR);
|
||||
|
||||
if input_text.ends_with(' ') || words.last() == Some(&TOKEN_SENTENCE_SEPARATOR) {
|
||||
words.push("");
|
||||
}
|
||||
|
||||
let predictions = model.predict(&words);
|
||||
|
||||
window.clear();
|
||||
window.addstr("N-gram model debug frontend\n");
|
||||
window.addstr(" demo tokenizer only supports single-line sentence in input text!\n\n");
|
||||
window.addstr(format!("enter text: {}\n", input_text));
|
||||
window.addstr(format!("detected words: {:?}\n\n", words));
|
||||
window.addstr("predictions:\n");
|
||||
for (i, (word, weight)) in predictions.iter().enumerate() {
|
||||
if i == 0 && *weight > 0.9 {
|
||||
window.attron(pancurses::A_BOLD);
|
||||
}
|
||||
window.addstr(format!(" {}. {} (c={:.2})\n", i + 1, word, weight));
|
||||
if i == 0 && *weight > 0.9 {
|
||||
window.attroff(pancurses::A_BOLD);
|
||||
}
|
||||
}
|
||||
if predictions.is_empty() {
|
||||
window.addstr(" (none)\n");
|
||||
}
|
||||
window.mv(3, 12 + input_text.len() as i32);
|
||||
window.refresh();
|
||||
|
||||
match window.getch().unwrap() {
|
||||
Input::KeyF10 => {
|
||||
break
|
||||
}
|
||||
Input::KeyBackspace => {
|
||||
input_text.pop();
|
||||
}
|
||||
Input::Character('\n') => {
|
||||
train_model(&input_text, &mut model)
|
||||
}
|
||||
Input::Character(ch) => {
|
||||
input_text.push(ch)
|
||||
}
|
||||
_ => { () }
|
||||
}
|
||||
}
|
||||
|
||||
pancurses::endwin();
|
||||
}
|
||||
27
utils/setup_vscode_dev_env.sh
Executable file
27
utils/setup_vscode_dev_env.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
WORKSPACE_ROOT_DIR="$(realpath "$(dirname "$0")/..")"
|
||||
VSCODE_DIR="$WORKSPACE_ROOT_DIR/.vscode"
|
||||
VSCODE_SETTINGS_JSON_PATH="$VSCODE_DIR/settings.json"
|
||||
|
||||
if [ "$WORKSPACE_ROOT_DIR" != "$(pwd)" ]; then
|
||||
echo "Not executing this script from workspace root dir!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$VSCODE_DIR" ]; then
|
||||
mkdir "$VSCODE_DIR"
|
||||
fi
|
||||
|
||||
echo -en "{\n" > "$VSCODE_SETTINGS_JSON_PATH"
|
||||
|
||||
# <rust-analyzer>
|
||||
rust_project_paths="$(find "$WORKSPACE_ROOT_DIR" -type f -name "Cargo.toml")"
|
||||
echo -en " \"rust-analyzer.linkedProjects\": [\n" >> "$VSCODE_SETTINGS_JSON_PATH"
|
||||
for rust_project_path in $rust_project_paths; do
|
||||
echo -en " \"$rust_project_path\",\n" >> "$VSCODE_SETTINGS_JSON_PATH"
|
||||
done
|
||||
echo -en " ],\n" >> "$VSCODE_SETTINGS_JSON_PATH"
|
||||
# </rust-analyzer>
|
||||
|
||||
echo -en "}\n" >> "$VSCODE_SETTINGS_JSON_PATH"
|
||||
Reference in New Issue
Block a user