diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c8936f6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,61 @@ +name: Release +on: + push: + tags: + - '*' + +jobs: + gradle: + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + artifact_name: memosc_${{ github.ref_name }}_amd64.deb + asset_name: memosc_${{ github.ref_name }}_amd64.deb + type: Deb + dir: deb + - os: windows-latest + artifact_name: memosc-${{ github.ref_name }}.exe + asset_name: memosc-${{ github.ref_name }}.exe + type: Exe + dir: exe + - os: macos-latest + artifact_name: memosc-${{ github.ref_name }}.dmg + asset_name: memosc-${{ github.ref_name }}.dmg + type: Dmg + dir: dmg + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: oracle + java-version: 19 + cache: 'gradle' + - uses: ConorMacBride/install-package@v1 + with: + apt: fakeroot + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - uses: SebRollen/toml-action@v1.0.2 + id: read_toml + with: + file: 'gradle/libs.versions.toml' + field: 'versions.packageVersion' + + - name: Execute Gradle build + + run: ./gradlew packageRelease${{ matrix.type }} --no-daemon + - name: Upload binaries to release + uses: svenstaro/upload-release-action@v2 + env: + FILE_NAME: ${{steps.read_toml.outputs.value}} + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: composeApp/build/compose/binaries/main-release/${{ matrix.dir }}/${{ matrix.artifact_name }} + asset_name: ${{ matrix.asset_name }} +# tag: ${{ github.ref }} + overwrite: true +#composeApp/build/compose/binaries/main/dmg/memosc-1.0.0.dmg diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a17bbf2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,48 @@ +name: test +on: + push: + branches: + - '*' + +jobs: + gradle: + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + artifact_name: memosc_${{ github.ref_name }}_amd64.deb + asset_name: memosc_${{ github.ref_name }}_amd64.deb + type: Deb + dir: deb + - os: windows-latest + artifact_name: memosc-${{ github.ref_name }}.exe + asset_name: memosc-${{ github.ref_name }}.exe + type: Exe + dir: exe + - os: macos-latest + artifact_name: memosc-${{ github.ref_name }}.dmg + asset_name: memosc-${{ github.ref_name }}.dmg + type: Dmg + dir: dmg + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: oracle + java-version: 19 + cache: 'gradle' + - uses: ConorMacBride/install-package@v1 + with: + apt: fakeroot + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - uses: SebRollen/toml-action@v1.0.2 + id: read_toml + with: + file: 'gradle/libs.versions.toml' + field: 'versions.packageVersion' + - name: Execute Gradle build + run: ./gradlew packageRelease${{ matrix.type }} --no-daemon \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..769535c --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +*.iml +.gradle +**/build/ +xcuserdata +!src/**/build/ +local.properties +.idea +.DS_Store +captures +.externalNativeBuild +.cxx +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +**/xcshareddata/WorkspaceSettings.xcsettings +/composeApp/settings_preferences.preferences_pb +/.env +*.db \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b821351 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 springeye + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..ef5ec7b --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +This is a Kotlin Multiplatform project targeting Android, iOS, Desktop. + +* `/composeApp` is for code that will be shared across your Compose Multiplatform applications. + It contains several subfolders: + - `commonMain` is for code that’s common for all targets. + - Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. + For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, + `iosMain` would be the right folder for such calls. + +* `/iosApp` contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, + you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project. + + +Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)… \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100755 index 0000000..52cf9fa --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + // this is necessary to avoid the plugins to be loaded multiple times + // in each subproject's classloader + alias(libs.plugins.androidApplication) apply false + alias(libs.plugins.androidLibrary) apply false + alias(libs.plugins.jetbrainsCompose) apply false + alias(libs.plugins.kotlinMultiplatform) apply false +} \ No newline at end of file diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts new file mode 100755 index 0000000..f0714e2 --- /dev/null +++ b/composeApp/build.gradle.kts @@ -0,0 +1,227 @@ +import org.jetbrains.compose.ExperimentalComposeLibrary +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsCompose) + kotlin("plugin.serialization") version "1.9.21" + id("com.google.devtools.ksp") version "1.9.21-1.0.16" + id("de.jensklingenberg.ktorfit") version "1.11.0" + id("app.cash.sqldelight") version "2.0.1" + +} +sqldelight { + databases { + create("AppDatabase") { + packageName.set("com.github.springeye.memosc.db.model") + } + } +} +val ktorfitVersion = "1.11.0" +val sqllinVersion = "1.2.3" +kotlin { + + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + } + } + } + + jvm("desktop") +// +// listOf( +// iosX64(), +// iosArm64(), +// iosSimulatorArm64() +// ).forEach { iosTarget -> +// iosTarget.binaries.framework { +// baseName = "ComposeApp" +// isStatic = true +// } +// } + + sourceSets { + val desktopMain by getting +// iosMain.dependencies { +//// implementation(libs.ktor.client.darwin) +// implementation("app.cash.paging:paging-runtime-uikit:3.3.0-alpha02-0.4.0") +// } + androidMain.dependencies { + implementation(libs.compose.ui.tooling.preview.android) + implementation(libs.androidx.activity.compose) + implementation(libs.ktor.client.okhttp) + implementation(libs.kotlinx.coroutines.android) + api(libs.androidx.startup) + implementation(libs.android.driver) + implementation(libs.koin.android) + // Jetpack WorkManager + implementation(libs.koin.androidx.workmanager) + // Navigation Graph + implementation(libs.koin.androidx.navigation) + implementation(libs.koin.androidx.compose) +// implementation(libs.voyager.bottomSheetNavigator) +// implementation(libs.voyager.tabNavigator) + } + desktopMain.dependencies { + implementation(compose.desktop.currentOs) + implementation(libs.kotlinx.coroutines.swing) + implementation(libs.ktor.client.cio) + implementation(libs.jetbrains.ui.tooling.preview.desktop) + implementation(libs.androidx.ui.graphics.desktop) + implementation(libs.sqlite.driver) + + } + + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) +// implementation(compose.material) + implementation(compose.material3) + implementation(compose.materialIconsExtended) + implementation(compose.ui) + @OptIn(ExperimentalComposeLibrary::class) + implementation(compose.components.resources) + implementation(libs.ktor.client.core) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.serialization.json) + implementation(libs.okio) + implementation(libs.stately.common) + + // Multiplatform + + // Navigator + implementation(libs.voyager.navigator) + + // Screen Model + implementation(libs.voyager.screenModel) + + // BottomSheetNavigator + implementation(libs.voyager.bottomSheetNavigator) + + // TabNavigator + implementation(libs.voyager.tabNavigator) + + // Transitions + implementation(libs.voyager.transitions) + + implementation(libs.voyager.koin) + implementation(libs.koin.core) + implementation(libs.koin.compose) + + + implementation(libs.kotlin.stdlib) + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.datetime) + + + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.ktor.client.logging) + implementation(libs.napier) + + + implementation(libs.napier) + + implementation(libs.androidx.datastore.preferences.core) +// api(libs.retrofit) +// api(libs.retrofit.converter.gson) +// implementation(libs.okhttp) +// api(libs.okhttp.interceptor.logging) + + implementation(libs.ktorfit.lib) + + implementation(libs.multiplatform.markdown.renderer) + + implementation(libs.kamel.image) + implementation(libs.mpfilepicker) + implementation(libs.highlights) + implementation(libs.napier.v271) + implementation(libs.paging.compose.common) + implementation(libs.androidx.paging3.extensions) + implementation("app.cash.sqldelight:coroutines-extensions:2.0.1") + } + + } +} + +android { + namespace = "com.github.springeye.memosc" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDirs("src/androidMain/res") + sourceSets["main"].resources.srcDirs("src/commonMain/resources") + + defaultConfig { + applicationId = "com.github.springeye.memosc" + minSdk = libs.versions.android.minSdk.get().toInt() + targetSdk = libs.versions.android.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + dependencies { + debugImplementation(libs.compose.ui.tooling) + + } +} + +compose.desktop { + application { + mainClass = "com.github.springeye.memosc.MainKt" + buildTypes.release { + proguard { + isEnabled=false + configurationFiles.from("rules.pro") + } + } + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi,TargetFormat.Exe, TargetFormat.Deb) + packageName = "memosc" + packageVersion = libs.versions.packageVersion.get() + + linux{ + iconFile=rootProject.file("launcher/logo.png") + } + macOS{ + iconFile=rootProject.file("launcher/logo.png") + } + windows{ + shortcut=true + menuGroup=packageName + iconFile=rootProject.file("launcher/logo.png") + } + } + } +} +ksp{ +} +dependencies { + + add("kspCommonMainMetadata", "de.jensklingenberg.ktorfit:ktorfit-ksp:$ktorfitVersion") +// add("kspIosX64","de.jensklingenberg.ktorfit:ktorfit-ksp:$ktorfitVersion") +// add("kspIosArm64","de.jensklingenberg.ktorfit:ktorfit-ksp:$ktorfitVersion") +// add("kspIosSimulatorArm64","de.jensklingenberg.ktorfit:ktorfit-ksp:$ktorfitVersion") + add("kspDesktop","de.jensklingenberg.ktorfit:ktorfit-ksp:$ktorfitVersion") + add("kspAndroid","de.jensklingenberg.ktorfit:ktorfit-ksp:$ktorfitVersion") +// add("kspLinuxArm64","de.jensklingenberg.ktorfit:ktorfit-ksp:$ktorfitVersion") +// add("kspLinuxX64","de.jensklingenberg.ktorfit:ktorfit-ksp:$ktorfitVersion") + +} \ No newline at end of file diff --git a/composeApp/rules.pro b/composeApp/rules.pro new file mode 100644 index 0000000..6151ca9 --- /dev/null +++ b/composeApp/rules.pro @@ -0,0 +1,13 @@ +-dontwarn +-keep class kotlinx.coroutines.** {*;} +-keep class org.jetbrains.skia.** {*;} +-keep class org.jetbrains.skiko.** {*;} +-keepclassmembernames class kotlinx.** { + volatile ; +} +-keep public class MainKt { + public void main(); +} + +-keep class api.** {*;} +-keep class model.** {*;} \ No newline at end of file diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml new file mode 100755 index 0000000..7eb43d8 --- /dev/null +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/ContextProvider.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/ContextProvider.kt new file mode 100644 index 0000000..78fbdf8 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/ContextProvider.kt @@ -0,0 +1,18 @@ +package com.github.springeye.memosc + +import android.content.Context +import androidx.startup.Initializer + +internal lateinit var applicationContext: Context + private set + +data object ContextProviderInitializer + +class ContextProvider: Initializer { + override fun create(context: Context): ContextProviderInitializer { + applicationContext = context.applicationContext + return ContextProviderInitializer + } + + override fun dependencies(): List>> = emptyList() +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/DataStorePreferences.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/DataStorePreferences.android.kt new file mode 100644 index 0000000..32df58d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/DataStorePreferences.android.kt @@ -0,0 +1,21 @@ +package com.github.springeye.memosc + +import androidx.datastore.core.DataMigration +import androidx.datastore.core.DataStore +import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler +import androidx.datastore.preferences.core.Preferences +import kotlinx.coroutines.CoroutineScope +import java.io.File + +actual fun dataStorePreferences( + corruptionHandler: ReplaceFileCorruptionHandler?, + coroutineScope: CoroutineScope, + migrations: List> +): DataStore = createDataStoreWithDefaults( + corruptionHandler = corruptionHandler, + migrations = migrations, + coroutineScope = coroutineScope, + path = { + File(applicationContext.filesDir, "datastore/$SETTINGS_PREFERENCES.preferences_pb").path + } +) \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/MainActivity.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/MainActivity.kt new file mode 100755 index 0000000..e146b40 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/MainActivity.kt @@ -0,0 +1,85 @@ +package com.github.springeye.memosc + + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.RowScope +import androidx.compose.material.BottomNavigation +import androidx.compose.material.BottomNavigationItem +import androidx.compose.material.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.tooling.preview.Preview +import cafe.adriel.voyager.navigator.tab.CurrentTab +import cafe.adriel.voyager.navigator.tab.LocalTabNavigator +import cafe.adriel.voyager.navigator.tab.Tab +import cafe.adriel.voyager.navigator.tab.TabNavigator +import com.github.springeye.memosc.core.Base64ImageFetcher +import com.github.springeye.memosc.di.appModule +import com.github.springeye.memosc.di.homeModule +import io.kamel.core.config.KamelConfig +import io.kamel.core.config.httpFetcher +import io.kamel.core.config.takeFrom +import io.kamel.image.config.Default +import io.kamel.image.config.LocalKamelConfig +import io.ktor.client.plugins.cookies.HttpCookies +import org.koin.compose.KoinApplication +import org.koin.compose.getKoin +import com.github.springeye.memosc.tab.DaysReviewTab +import com.github.springeye.memosc.tab.ExploreTab +import com.github.springeye.memosc.tab.HomeTab +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + KoinApplication(application = { + modules(appModule, homeModule) + }) { + val koin= getKoin() + val kamelConfig= KamelConfig { + takeFrom(KamelConfig.Default) + fetcher(Base64ImageFetcher()) + httpFetcher { + install(HttpCookies){ + this.storage=koin.get() + } + } + } + CompositionLocalProvider(LocalKamelConfig provides kamelConfig) { + TabNavigator(HomeTab) { + Scaffold( + content = {padding-> + println(padding) + CurrentTab() + }, + bottomBar = { + BottomNavigation { + TabNavigationItem(HomeTab) + TabNavigationItem(ExploreTab) + TabNavigationItem(DaysReviewTab) + } + } + ) + } + } + } + } + } +} +@Composable +private fun RowScope.TabNavigationItem(tab: Tab) { + val tabNavigator = LocalTabNavigator.current + + BottomNavigationItem( + selected = tabNavigator.current == tab, + onClick = { tabNavigator.current = tab }, + icon = { Icon(painter = tab.options.icon!!, contentDescription = tab.options.title) } + ) +} +@Preview +@Composable +fun AppAndroidPreview() { +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/MemoscApplication.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/MemoscApplication.kt new file mode 100644 index 0000000..e20cef5 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/MemoscApplication.kt @@ -0,0 +1,17 @@ +package com.github.springeye.memosc + +import android.app.Application + +class MemoscApplication: Application() { + override fun onCreate() { + super.onCreate() +// startKoin { +// // Log Koin into Android logger +// androidLogger() +// // Reference Android context +// androidContext(this@MemoscApplication) +// // Load modules +// modules(appModule, homeModule) +// } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/Platform.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/Platform.android.kt new file mode 100644 index 0000000..14d62e6 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/Platform.android.kt @@ -0,0 +1,10 @@ +package com.github.springeye.memosc + +import Platform +import android.os.Build + +class AndroidPlatform : Platform { + override val name: String = "Android ${Build.VERSION.SDK_INT}" +} + +actual fun getPlatform(): Platform = AndroidPlatform() \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/PopupNotification.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/PopupNotification.android.kt new file mode 100644 index 0000000..729aecc --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/PopupNotification.android.kt @@ -0,0 +1,11 @@ +package com.github.springeye.memosc + +import PopupNotification +import ToastState +import androidx.compose.runtime.MutableState + + + +actual fun createPopupNotification(state: MutableState): PopupNotification { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/components/Card.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/components/Card.kt new file mode 100644 index 0000000..cc7a6ba --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/components/Card.kt @@ -0,0 +1,57 @@ +package com.github.springeye.memosc.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Preview +@Composable +fun PreviewCard() { + Scaffold { + Column(modifier = Modifier.padding(it).fillMaxSize().background(Color.Gray)) { + CardItem(radius = 10.dp, + hoverColor = Color.Blue, + + ) { + Text("asdfasfas") + } + } + } +} +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun CardItem( + radius: Dp =15.dp, + color: Color=Color.White, + hoverColor: Color=color, + borderColor: Color=color, + hoverBorderColor: Color=color, + borderWidth:Dp=0.dp, + paddingValues: PaddingValues= PaddingValues(10.dp), + modifier:Modifier = Modifier, content: @Composable() (BoxScope.() -> Unit)) { + + var background = Modifier.clip(RoundedCornerShape(radius)) + .background(color) + + Box(modifier = background + .then(modifier) + .border(borderWidth,borderColor,shape = RoundedCornerShape(radius)) + .padding(paddingValues), + content = content) +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/components/Heatmap.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/components/Heatmap.android.kt new file mode 100644 index 0000000..6c4fa17 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/components/Heatmap.android.kt @@ -0,0 +1,33 @@ +package com.github.springeye.memosc.components + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.Constraints +import java.time.LocalDate +import java.time.temporal.ChronoUnit +import java.time.temporal.TemporalAdjusters +import java.time.temporal.WeekFields +import java.util.Locale +import kotlin.math.ceil + +actual fun countHeatmap(constraints: Constraints): Int { + val cellSize = ceil(constraints.maxHeight.toDouble() / 7).toInt() + if (cellSize <= 0) { + return 0 + } + val columns = constraints.maxWidth / cellSize + val fullCells = columns * 7 + + val firstDayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek + val firstDayOfThisWeek = LocalDate.now().with(TemporalAdjusters.previousOrSame(firstDayOfWeek)) + val lastColumn = ChronoUnit.DAYS.between(firstDayOfThisWeek, LocalDate.now()).toInt() + 1 + if (lastColumn % 7 == 0) { + return fullCells + } + return fullCells - 7 + lastColumn +} + +@Composable +actual fun aa() { + Text("我是andriod") +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/components/HeatmapStat.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/components/HeatmapStat.android.kt new file mode 100644 index 0000000..53ab670 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/components/HeatmapStat.android.kt @@ -0,0 +1,8 @@ +package com.github.springeye.memosc.components + +import androidx.compose.runtime.Composable +import com.github.springeye.memosc.model.DailyUsageStat + +@Composable +actual fun HeatmapStat2(day: DailyUsageStat) { +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/core/IFile.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/core/IFile.android.kt new file mode 100644 index 0000000..ad0a55e --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/core/IFile.android.kt @@ -0,0 +1,8 @@ +package com.github.springeye.memosc.core + +import core.IFile + + +actual fun createIFile(path: String): IFile { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/core/Instant.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/core/Instant.android.kt new file mode 100644 index 0000000..eeda1fe --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/core/Instant.android.kt @@ -0,0 +1,22 @@ +import kotlinx.datetime.Instant +import java.text.SimpleDateFormat +import java.util.Date + +actual fun Instant.formatDate( + pattern: String, + defValue: String +): String { + return try { + SimpleDateFormat(pattern).format(Date(this.toEpochMilliseconds())) + } catch (e: Exception) { + defValue + } +} + +actual fun String.parseDate(pattern: String, defValue: Long): Long { + return try { + SimpleDateFormat(pattern).parse(this).time + } catch (e: Exception) { + defValue + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/core/Instant1.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/core/Instant1.android.kt new file mode 100644 index 0000000..7432096 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/core/Instant1.android.kt @@ -0,0 +1,14 @@ +package com.github.springeye.memosc.core + +import kotlinx.datetime.Instant + +actual fun Instant.formatDate( + pattern: String, + defValue: String +): String { + TODO("Not yet implemented") +} + +actual fun String.parseDate(pattern: String, defValue: Long): Long { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/db/AppDatabase.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/db/AppDatabase.android.kt new file mode 100644 index 0000000..c799c5f --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/db/AppDatabase.android.kt @@ -0,0 +1,7 @@ +package com.github.springeye.memosc.db + +import com.github.springeye.memosc.db.model.AppDatabase + +actual fun createAppDatabase(): AppDatabase { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/db/MemoDao.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/db/MemoDao.android.kt new file mode 100644 index 0000000..6ccc150 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/db/MemoDao.android.kt @@ -0,0 +1,53 @@ +package com.github.springeye.memosc.db + +import com.github.springeye.memosc.db.model.AppDatabase +import com.github.springeye.memosc.db.model.MemoQueries +import com.github.springeye.memosc.model.Memo + +actual fun createMemoDao(database: AppDatabase): MemoDao { + return AndroidMemoDao(database) +} +internal class AndroidMemoDao(private val database: AppDatabase): MemoDao { + + override suspend fun findAll(): List { + return database.memoQueries.selectAll().executeAsList().map { + Memo.fromDbMemo(it) + } + } + + override suspend fun clear() { + TODO("Not yet implemented") + } + + override suspend fun findById(memoId: Long): Memo? { + TODO("Not yet implemented") + } + + override suspend fun insertAll(vararg memos: Memo, clear: Boolean) { + database.memoQueries.transaction { + if(clear){ + database.memoQueries.deleteAll() + } + for (memo in memos) { + val item= with(memo) { + com.github.springeye.memosc.db.model.Memo( + id, + createdTs, + creatorId, + creatorName, + content, + pinned, + rowStatus, + visibility, + updatedTs + ) + } + database.memoQueries.insertMemo(item) + } + } + } + + override fun memoQueries(): MemoQueries { + return database.memoQueries + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/db/RemoteKeyDao.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/db/RemoteKeyDao.android.kt new file mode 100644 index 0000000..1c9cabc --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/db/RemoteKeyDao.android.kt @@ -0,0 +1,5 @@ +package com.github.springeye.memosc.db + +actual fun createRemoteKeyDao(): RemoteKeyDao { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/di/CacheCookiesStorage.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/di/CacheCookiesStorage.android.kt new file mode 100644 index 0000000..5bebc21 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/di/CacheCookiesStorage.android.kt @@ -0,0 +1,60 @@ +package com.github.springeye.memosc.di + +import AppPreferences +import di.fillDefaults +import di.fromString +import di.matches +import di.toSaveString +import io.ktor.client.plugins.cookies.CookiesStorage +import io.ktor.http.Cookie +import io.ktor.http.Url +import io.ktor.util.date.getTimeMillis +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.util.concurrent.atomic.AtomicLong +import kotlin.math.min + + +actual class PersistentCookiesStorage actual constructor(val store: AppPreferences) : + CookiesStorage { + override suspend fun addCookie(requestUrl: Url, cookie: Cookie) { + val items = getCookiesFromStorage() + items.removeAll { it.name == cookie.name && it.matches(requestUrl) } + items.add(cookie.fillDefaults(requestUrl)) + store.setString("cookies", Json.encodeToString(items.map { it.toSaveString() }.toList())); + } + + private suspend fun getCookiesFromStorage(): MutableList { + val old = store.getString("cookies") ?: "[]" + val items = Json.decodeFromString>(old) + val cookies = mutableListOf() + return cookies.apply { + addAll(items.map { fromString(it) }) + }; + } + private val oldestCookie: AtomicLong = AtomicLong(0L) + override suspend fun get(requestUrl: Url): List { + val now = getTimeMillis() + if (now >= oldestCookie.get()) cleanup(now) + return getCookiesFromStorage().filter { + it.matches(requestUrl) + } + } + private suspend fun cleanup(timestamp: Long) { + val cookies = getCookiesFromStorage() + cookies.removeAll { cookie -> + val expires = cookie.expires?.timestamp ?: return@removeAll false + expires < timestamp + } + + val newOldest = cookies.fold(Long.MAX_VALUE) { acc, cookie -> + cookie.expires?.timestamp?.let { min(acc, it) } ?: acc + } + + oldestCookie.set(newOldest) + store.setString("cookies", Json.encodeToString(cookies.map { it.toSaveString() }.toList())); + } + override fun close() { + } +} + diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/logger.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/logger.android.kt new file mode 100644 index 0000000..aa9db7d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/logger.android.kt @@ -0,0 +1,8 @@ +package com.github.springeye.memosc + +import io.github.aakira.napier.DebugAntilog +import io.github.aakira.napier.Napier + +actual fun initLogger() { + Napier.base(DebugAntilog()) +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/model/DailyUsageStat.android.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/model/DailyUsageStat.android.kt new file mode 100644 index 0000000..bee1286 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/model/DailyUsageStat.android.kt @@ -0,0 +1,27 @@ +package com.github.springeye.memosc.model + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.toKotlinLocalDate +import java.time.DayOfWeek +import java.time.format.TextStyle +import java.time.temporal.WeekFields +import java.util.Locale + +actual fun initialMatrix(): List { + val now = java.time.LocalDate.now() + return (1..now.lengthOfYear()).map { day -> + DailyUsageStat(date = now.minusDays(day - 1L).toKotlinLocalDate()) + }.reversed() +} + +actual val weekDays: List + get(){ + val day = WeekFields.of(Locale.getDefault()).firstDayOfWeek + return DayOfWeek.values().mapIndexed { index, _ -> + day.plus(index.toLong()).getDisplayName(TextStyle.SHORT, Locale.getDefault()) + } + } + +actual fun calculateMatrix(memos: Map>): List { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/ArchivedTab.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/ArchivedTab.kt new file mode 100644 index 0000000..780222d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/ArchivedTab.kt @@ -0,0 +1,33 @@ +package com.github.springeye.memosc.tab + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Archive +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import cafe.adriel.voyager.navigator.tab.Tab +import cafe.adriel.voyager.navigator.tab.TabOptions + +object ArchivedTab: Tab { + @Composable + override fun Content() { + Text("home2") + } + + override val options: TabOptions + @Composable + get() { + val title = "已归档" + val icon = rememberVectorPainter(Icons.Outlined.Archive) + + return remember { + TabOptions( + index = 5u, + title = title, + icon = icon + ) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/DaysReviewTab.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/DaysReviewTab.kt new file mode 100644 index 0000000..d2a50eb --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/DaysReviewTab.kt @@ -0,0 +1,35 @@ +package com.github.springeye.memosc.tab + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.CalendarToday +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import cafe.adriel.voyager.navigator.tab.Tab +import cafe.adriel.voyager.navigator.tab.TabOptions +import org.jetbrains.compose.resources.ExperimentalResourceApi + +@OptIn(ExperimentalResourceApi::class) +object DaysReviewTab: Tab { + @Composable + override fun Content() { + Text("home2") + } + + override val options: TabOptions + @Composable + get() { + val title = "每次回顾" + val icon = rememberVectorPainter(Icons.Outlined.CalendarToday) + + return remember { + TabOptions( + index = 1u, + title = title, + icon = icon + ) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/ExploreTab.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/ExploreTab.kt new file mode 100644 index 0000000..637a8bf --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/ExploreTab.kt @@ -0,0 +1,63 @@ +package com.github.springeye.memosc.tab + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.outlined.Explore +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import app.cash.paging.LoadStateLoading +import app.cash.paging.compose.collectAsLazyPagingItems +import cafe.adriel.voyager.koin.getScreenModel +import cafe.adriel.voyager.navigator.tab.Tab +import cafe.adriel.voyager.navigator.tab.TabOptions +import ui.home.MemoModel + +object ExploreTab:Tab{ + @OptIn(ExperimentalMaterialApi::class) + @Composable + override fun Content() { + val model = getScreenModel() + val lazyPagingItems = model.pager.flow.collectAsLazyPagingItems() + val refreshing = lazyPagingItems.loadState.refresh == LoadStateLoading + val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = { + lazyPagingItems.refresh() + }) + Box(modifier = Modifier.fillMaxWidth().fillMaxHeight() + .background(Color.Red) + .pullRefresh(pullRefreshState)) { + Text("探索") + PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter)) + } + + } + + override val options: TabOptions + @Composable + get() { + val title = "探索" + val icon = rememberVectorPainter(Icons.Outlined.Explore) + + return remember { + TabOptions( + index = 3u, + title = title, + icon = icon + ) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/HomeTab.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/HomeTab.kt new file mode 100644 index 0000000..84b8ecf --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/HomeTab.kt @@ -0,0 +1,646 @@ +package com.github.springeye.memosc.tab + +import AppModel +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.DropdownMenu +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Attachment +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.ExpandLess +import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.Photo +import androidx.compose.material.icons.filled.Send +import androidx.compose.material.icons.filled.Tag +import androidx.compose.material.icons.outlined.Attachment +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.AssistChip +import androidx.compose.material3.AssistChipDefaults +import androidx.compose.material3.Button +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.input.pointer.PointerIcon +import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import app.cash.paging.LoadStateLoading +import app.cash.paging.compose.collectAsLazyPagingItems +import cafe.adriel.voyager.koin.getScreenModel +import cafe.adriel.voyager.navigator.tab.Tab +import cafe.adriel.voyager.navigator.tab.TabOptions +import com.darkrockstudios.libraries.mpfilepicker.FilePicker +import com.mikepenz.markdown.compose.LocalMarkdownColors +import com.mikepenz.markdown.compose.LocalMarkdownTypography +import com.mikepenz.markdown.compose.Markdown +import com.mikepenz.markdown.compose.components.MarkdownComponent +import com.mikepenz.markdown.compose.components.markdownComponents +import com.mikepenz.markdown.model.markdownColor +import com.wakaztahir.codeeditor.prettify.PrettifyParser +import com.wakaztahir.codeeditor.theme.CodeThemeType +import com.wakaztahir.codeeditor.utils.parseCodeAsAnnotatedString +import com.github.springeye.memosc.components.CardItem +import components.ITextField +import dev.snipme.highlights.Highlights +import dev.snipme.highlights.model.BoldHighlight +import dev.snipme.highlights.model.ColorHighlight +import dev.snipme.highlights.model.SyntaxLanguage +import dev.snipme.highlights.model.SyntaxThemes +import io.kamel.image.KamelImage +import io.kamel.image.asyncPainterResource +import kotlinx.coroutines.launch +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toJavaLocalDateTime +import kotlinx.datetime.toLocalDateTime +import model.Memo +import model.Resource +import org.intellij.markdown.ast.ASTNode +import ui.home.MemoModel +import java.time.format.DateTimeFormatter +import kotlin.math.ceil +import kotlin.math.min + +object HomeTab : Tab { + @OptIn(ExperimentalMaterialApi::class) + @Composable + override fun Content() { + + val model = getScreenModel() + val lazyPagingItems = model.pager.flow.collectAsLazyPagingItems() + val memos = model.memos + val refreshing = lazyPagingItems.loadState.refresh == LoadStateLoading + val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = { + lazyPagingItems.refresh() + }) + Column(modifier = Modifier.fillMaxWidth().pullRefresh(pullRefreshState)) { + PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.CenterHorizontally)) + Box(Modifier.padding(top = 20.dp)) { + EditMemo() + } + LazyColumn(modifier = Modifier.fillMaxWidth()) { + items(lazyPagingItems.itemCount) { index -> + val it = lazyPagingItems[index]!! + Box(Modifier.padding(top = 10.dp)) { + CardItem( + radius = 10.dp, + modifier = Modifier.fillMaxWidth(), + borderColor = Color.White, + borderWidth = 1.dp, + hoverBorderColor = Color(0xccCCCCCC), + ) { + Column { + val dateTime = + Instant.fromEpochSeconds(it.createdTs) + .toLocalDateTime( + TimeZone.currentSystemDefault() + ).toJavaLocalDateTime() + .format(DateTimeFormatter.ofPattern("YYYY-MM-DD HH:mm:ss")) + Row(modifier = Modifier) { + Text( + dateTime, + style = TextStyle.Default.copy( + color = Color( + 156, + 163, + 175 + ) + ) + ) + Spacer(Modifier.weight(1F)) + ItemEditMenu(it) + } + + val components = markdownComponents( + codeBlock = codeBlockComponent, + codeFence = codeFenceBlockComponent + ) + + Markdown( + it.content, modifier = Modifier.fillMaxWidth(), + components = components, + colors = markdownColor(codeText = Color.Black), + // typography = markdownTypography(code = MaterialTheme.typography.body2.copy(fontFamily = FontFamily.Monospace, color = Color.Black)) + ) + MemoResourceContent(memo = it); + } + } + } + } + } + + } + + + } + + private val codeBlockComponent: MarkdownComponent = { + MarkdownCodeBlock(it.content, it.node) + } + private val codeFenceBlockComponent: MarkdownComponent = { + MarkdownCodeFenceBlock(it.content, it.node) + } + + @Composable + internal fun MarkdownCodeFenceBlock( + content: String, + node: ASTNode + ) { + // CODE_FENCE_START, FENCE_LANG, {content}, CODE_FENCE_END + if (node.children.size >= 3) { + + val start = node.children[2].startOffset + val end = node.children[node.children.size - 2].endOffset + val langStart = node.children[1].startOffset + val langEnd = node.children[1].endOffset + val lang = content.substring(langStart, langEnd) + MarkdownCode(content.subSequence(start, end).toString().replaceIndent(), lang) + } else { + // invalid code block, skipping + } + } + + @Composable + internal fun MarkdownCodeBlock( + content: String, + node: ASTNode + ) { + val start = node.children[0].startOffset + val end = node.children[node.children.size - 1].endOffset + MarkdownCode(content.subSequence(start, end).toString().replaceIndent(), "txt") + } + + @Composable + private fun MarkdownCode( + code: String, + lang: String, + style: TextStyle = LocalMarkdownTypography.current.code + ) { + val color = LocalMarkdownColors.current.codeText + var language = lang + language = language.replace("^golang$".toRegex(), "go") +// language=language.replace("^java$".toRegex(),"kotlin") + val backgroundCodeColor = LocalMarkdownColors.current.codeBackground + Surface( + color = backgroundCodeColor, + shape = RoundedCornerShape(8.dp), + modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp) + ) { + + val syn = SyntaxLanguage.getByName(language) + if (syn != null) { + val highlights = Highlights.Builder() + .code(code) + .theme(SyntaxThemes.darcula()) + .language(syn) + .build() + .getHighlights() + Text( + text = buildAnnotatedString { + append(code) + highlights + .filterIsInstance() + .forEach { + addStyle( + SpanStyle(color = Color(it.rgb).copy(alpha = 1f)), + start = it.location.start, + end = it.location.end, + ) + } + + highlights + .filterIsInstance() + .forEach { + addStyle( + SpanStyle(fontWeight = FontWeight.Bold), + start = it.location.start, + end = it.location.end, + ) + } + }, + color = color, + modifier = Modifier.horizontalScroll(rememberScrollState()).padding(8.dp), + style = style + ) + } else { + + val parser = remember { PrettifyParser() } + var themeState by remember { mutableStateOf(CodeThemeType.Monokai) } + val theme = remember(themeState) { themeState.theme } + val parsedCode: AnnotatedString = remember { + if (!parser.isSupport(language)) { + AnnotatedString(code) + } else { + parseCodeAsAnnotatedString( + parser = parser, + theme = theme, + lang = language, + code = code + ) + } + } + + Text( + parsedCode, + color = color, + modifier = Modifier.horizontalScroll(rememberScrollState()).padding(8.dp), + style = style + ) + } + + } + } + + override val options: TabOptions + @Composable + get() { + val title = "主页" + val icon = rememberVectorPainter(Icons.Outlined.Home) + + return remember { + TabOptions( + index = 0u, + title = title, + icon = icon + ) + } + } + + @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) + @Preview + @Composable + fun EditMemo() { + val model = getScreenModel() + val settings by getScreenModel().state.collectAsState() + CardItem( + radius = 10.dp, + borderColor = Color(0xccCCCCCC), + borderWidth = 1.dp, + paddingValues = PaddingValues(start = 10.dp, end = 10.dp, bottom = 10.dp), + hoverBorderColor = Color(0xccCCCCCC), + ) { + Column(Modifier.fillMaxWidth().wrapContentHeight()) { + ITextField( + model.content, + onValueChange = { + model.content = it + }, + minLines = 2, + maxLines = 10, + placeholder = { + Text( + "任何想法", + style = TextStyle.Default.copy(color = Color(156, 163, 175)) + ) + }, + modifier = Modifier + .fillMaxWidth(), // Here I have decreased the height + shape = RoundedCornerShape(0.dp), + colors = TextFieldDefaults.colors( + focusedContainerColor = Color.White, + unfocusedContainerColor = Color.White, + disabledContainerColor = Color.White, + disabledIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + + ), + + ) + var showFilePicker by remember { mutableStateOf(false) } + Row { + Icon(Icons.Default.Tag, "") + Icon(Icons.Default.Photo, "", Modifier.padding(start = 10.dp).clickable { + showFilePicker = true + }) + Icon(Icons.Default.Attachment, "", Modifier.padding(start = 10.dp)) + } + Row { + + + val fileType = listOf("jpg", "png") + FilePicker(show = showFilePicker, fileExtensions = fileType) { file -> + showFilePicker = false +// println(file?.path) + file?.path?.let { model.upload(it) } + // do something with the file + } + } + Divider(Modifier.padding(top = 10.dp, bottom = 10.dp)) + + FlowRow() { + model.resources.forEach { item -> + AnimatedContent(targetState = item) {s-> + ResourceItem(s, settings.host) + } + } + } + Row(Modifier.padding(vertical = 10.dp)) { + VisibilityButton() + Spacer(Modifier.weight(1f)) + SubmitButton(model.content.isNotEmpty()) + } + + } + } + } + + @Composable + fun ResourceItem(item: Resource, host: String) { + val model = getScreenModel() + + val scope = rememberCoroutineScope() + Row( + modifier = Modifier + .wrapContentWidth() + .padding(end = 5.dp) + .background(MaterialTheme.colorScheme.background), + verticalAlignment = Alignment.CenterVertically + ) { + KamelImage( + asyncPainterResource(item.uri(host)), + "", + Modifier.size(20.dp) + + ) + Text( + item.filename, + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(start = 5.dp) + ) + Icon(Icons.Default.Close, "", modifier = Modifier.clickable { + scope.launch { + model.removeResource(item) + + } + + }) + } + } + + @Composable + fun SubmitButton(enable: Boolean) { + val model = getScreenModel() + Button( + enabled = enable, + onClick = { + model.submit() + }, + contentPadding = PaddingValues(horizontal = 10.dp), + shape = RoundedCornerShape(5.dp), + modifier = Modifier + .defaultMinSize(minWidth = 1.dp, minHeight = 1.dp) + .height(30.dp) + .pointerHoverIcon(PointerIcon.Hand) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text("保存") + Icon( + Icons.Filled.Send, "", + tint = Color.White, + modifier = Modifier + .size(20.dp) + .padding(start = 5.dp) + ) + } + } + } + + @Composable + fun MemoResourceIcons(memo: Memo) { + val settings by getScreenModel().state.collectAsState() + memo.resourceList?.let { resourceList -> + val cols = min(3, resourceList.size) + val imageList = resourceList.filter { it.type.startsWith("image/") } + if (imageList.isNotEmpty()) { + val rows = ceil(imageList.size.toFloat() / cols).toInt() + for (rowIndex in 0 until rows) { + Row { + for (colIndex in 0 until cols) { + val index = rowIndex * cols + colIndex + if (index < imageList.size) { + Box(modifier = Modifier.fillMaxWidth(1f / (cols - colIndex))) { + val uri = imageList[index].uri(settings.host) + KamelImage( + asyncPainterResource(uri), + onLoading = { + Text("加载中") + }, + onFailure = { + it.printStackTrace() + Text("加载失败") + }, + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier + .padding(2.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(4.dp)) + ) + } + } else { + Spacer(modifier = Modifier.fillMaxWidth(1f / cols)) + } + } + } + } + } + resourceList.filterNot { it.type.startsWith("image/") }.forEach { resource -> + Attachment(resource) + } + } + } + + @Composable + fun MemoResourceContent(memo: Memo) { + val settings by getScreenModel().state.collectAsState() + memo.resourceList?.let { resourceList -> + val cols = min(3, resourceList.size) + val imageList = resourceList.filter { it.type.startsWith("image/") } + if (imageList.isNotEmpty()) { + val rows = ceil(imageList.size.toFloat() / cols).toInt() + for (rowIndex in 0 until rows) { + Row { + for (colIndex in 0 until cols) { + val index = rowIndex * cols + colIndex + if (index < imageList.size) { + Box(modifier = Modifier.fillMaxWidth(1f / (cols - colIndex))) { + val uri = imageList[index].uri(settings.host) + KamelImage( + asyncPainterResource(uri), + onLoading = { + Text("加载中") + }, + onFailure = { + it.printStackTrace() + Text("加载失败") + }, + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier + .padding(2.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(4.dp)) + ) + } + } else { + Spacer(modifier = Modifier.fillMaxWidth(1f / cols)) + } + } + } + } + } + resourceList.filterNot { it.type.startsWith("image/") }.forEach { resource -> + Attachment(resource) + } + } + } + + @Composable + fun Attachment( + resource: Resource + ) { + val uriHandler = LocalUriHandler.current + val settings by getScreenModel().state.collectAsState() + + AssistChip( + modifier = Modifier.padding(bottom = 10.dp), + onClick = { + uriHandler.openUri(resource.uri(settings.host)) + }, + label = { Text(resource.filename) }, + leadingIcon = { + androidx.compose.material3.Icon( + Icons.Outlined.Attachment, + contentDescription = "附件", + Modifier.size(AssistChipDefaults.IconSize) + ) + } + ) + } + + @Composable + fun VisibilityButton() { + var expanded by remember { mutableStateOf(false) } + Box(Modifier.clip(RoundedCornerShape(5.dp)) + .background(Color(0x99eeeeee)) + .clickable { + expanded = !expanded + }.padding(vertical = 5.dp, horizontal = 10.dp) + ) { + Row( + modifier = Modifier.wrapContentWidth() + ) { + val tint = Color(156, 163, 175) + Icon( + imageVector = Icons.Default.Lock, + contentDescription = "More", + modifier = Modifier.size(20.dp), + tint = tint + ) + Text( + "私有", + style = TextStyle.Default.copy(color = tint), + modifier = Modifier.padding(horizontal = 5.dp) + ) + Icon( + imageVector = if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore, + contentDescription = "More", + modifier = Modifier.size(20.dp), + tint = tint + + + ) + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { Text("Load") }, + onClick = { } + ) + DropdownMenuItem( + text = { Text("Save") }, + onClick = { } + ) + } + } + } + + @Composable + fun ItemEditMenu(memo: Memo) { + var expanded by remember { mutableStateOf(false) } + val model = getScreenModel() + Box() { + Icon(Icons.Default.MoreVert, "More", Modifier.clickable { + expanded = !expanded + }) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { Text("Edit") }, + onClick = { + model.setEdit(memo) + expanded = false + } + ) + DropdownMenuItem( + text = { Text("Save") }, + onClick = { } + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/InboxTab.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/InboxTab.kt new file mode 100644 index 0000000..a532b4a --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/InboxTab.kt @@ -0,0 +1,33 @@ +package com.github.springeye.memosc.tab +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Archive +import androidx.compose.material.icons.outlined.Inbox +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import cafe.adriel.voyager.navigator.tab.Tab +import cafe.adriel.voyager.navigator.tab.TabOptions + +object InboxTab:Tab{ + @Composable + override fun Content() { + Text("home2") + } + + override val options: TabOptions + @Composable + get() { + val title = "通知" + val icon = rememberVectorPainter(Icons.Outlined.Inbox) + + return remember { + TabOptions( + index = 4u, + title = title, + icon = icon + ) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/ResourcesTab.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/ResourcesTab.kt new file mode 100644 index 0000000..95adcfa --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/ResourcesTab.kt @@ -0,0 +1,87 @@ +package com.github.springeye.memosc.tab +import AppModel +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.PhotoLibrary +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.koin.getScreenModel +import cafe.adriel.voyager.navigator.tab.Tab +import cafe.adriel.voyager.navigator.tab.TabOptions +import com.github.springeye.memosc.components.CardItem +import formatDate +import io.kamel.image.KamelImage +import io.kamel.image.asyncPainterResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import ui.home.MemoModel + +object ResourcesTab : Tab { + @OptIn(ExperimentalLayoutApi::class) + @Composable + override fun Content() { + val model = getScreenModel() + val settings = getScreenModel().state.value + val group by model.resourcesGroup.collectAsState(mapOf()) + CardItem(modifier = Modifier.fillMaxSize()) { + Column { + for (entry in group) { + Row { + Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(horizontal = 15.dp).padding(top = 10.dp)) { + Text("${entry.key.formatDate("yyyy")}", style = MaterialTheme.typography.bodySmall) + Text("${entry.key.formatDate("MM")}", style = MaterialTheme.typography.titleMedium) + } + + FlowRow { + for (resource in entry.value) { + KamelImage( + asyncPainterResource(resource.uri(settings.host)), + "", + modifier = Modifier.size(100.dp) + .padding(5.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(4.dp)), + contentScale = ContentScale.FillWidth, + + ) + } + } + } + } + } + } + } + + @OptIn(ExperimentalResourceApi::class) + override val options: TabOptions + @Composable + get() { + val title = "资源库" + val icon = rememberVectorPainter(Icons.Outlined.PhotoLibrary); + return remember { + TabOptions( + index = 2u, + title = title, + icon = icon + ) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/SettingsTab.kt b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/SettingsTab.kt new file mode 100644 index 0000000..7d26009 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/github/springeye/memosc/tab/SettingsTab.kt @@ -0,0 +1,33 @@ +package com.github.springeye.memosc.tab +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Archive +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import cafe.adriel.voyager.navigator.tab.Tab +import cafe.adriel.voyager.navigator.tab.TabOptions + +object SettingsTab:Tab{ + @Composable + override fun Content() { + Text("home2") + } + + override val options: TabOptions + @Composable + get() { + val title = "设置" + val icon = rememberVectorPainter(Icons.Outlined.Settings) + + return remember { + TabOptions( + index = 6u, + title = title, + icon = icon + ) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml b/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml new file mode 100755 index 0000000..2b068d1 --- /dev/null +++ b/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml b/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml new file mode 100755 index 0000000..07d5da9 --- /dev/null +++ b/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100755 index 0000000..eca70cf --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100755 index 0000000..eca70cf --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 0000000..a571e60 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png new file mode 100755 index 0000000..61da551 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png new file mode 100755 index 0000000..c41dd28 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png new file mode 100755 index 0000000..db5080a Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png new file mode 100755 index 0000000..6dba46d Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100755 index 0000000..da31a87 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 0000000..15ac681 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100755 index 0000000..b216f2d Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100755 index 0000000..f25a419 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100755 index 0000000..e96783c Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/values/strings.xml b/composeApp/src/androidMain/res/values/strings.xml new file mode 100755 index 0000000..1dd08c3 --- /dev/null +++ b/composeApp/src/androidMain/res/values/strings.xml @@ -0,0 +1,3 @@ + + memosc + \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/AppPreferences.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/AppPreferences.kt new file mode 100644 index 0000000..8e93e1a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/AppPreferences.kt @@ -0,0 +1,75 @@ +package com.github.springeye.memosc +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map + +interface AppPreferences { + suspend fun host(): String? + suspend fun username(): String? + suspend fun password(): String? + suspend fun host(host:String) + suspend fun username(username:String) + suspend fun password(password:String) + suspend fun setString(key:String,value:String) + suspend fun getString(key:String):String? +} + +internal class AppPreferencesImpl( + private val dataStore: DataStore +) : AppPreferences { + + private companion object { + private const val PREFS_TAG_KEY = "AppPreferences" + private const val HOST = "host" + private const val USERNAME = "username" + private const val PASSWORD = "password" + } + + private val hostKey = stringPreferencesKey("$PREFS_TAG_KEY$HOST") + private val usernameKey = stringPreferencesKey("$PREFS_TAG_KEY$USERNAME") + private val passwordKey = stringPreferencesKey("$PREFS_TAG_KEY$PASSWORD") + + + override suspend fun host(): String? = dataStore.data.map { preferences -> + preferences[hostKey] + }.first() + override suspend fun host(host: String) { + dataStore.edit { preferences -> + preferences[hostKey] = host + } + } + + override suspend fun username(): String? =dataStore.data.map { preferences -> + preferences[usernameKey] + }.first() + + override suspend fun username(username: String){ + dataStore.edit { preferences -> + preferences[usernameKey] = username + } + } + + override suspend fun password(): String? =dataStore.data.map { preferences -> + preferences[passwordKey] + }.first() + + override suspend fun password(password: String) { + dataStore.edit { preferences -> + preferences[passwordKey] = password + } + } + + override suspend fun setString(key: String, value: String) { + dataStore.edit { preferences -> + preferences[stringPreferencesKey("$PREFS_TAG_KEY$key")]=value + } + } + + override suspend fun getString(key: String): String? = dataStore.data.map {preferences -> + preferences[stringPreferencesKey("$PREFS_TAG_KEY$key")] + }.firstOrNull() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/AppSettings.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/AppSettings.kt new file mode 100644 index 0000000..cbc6062 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/AppSettings.kt @@ -0,0 +1,6 @@ +package com.github.springeye.memosc +import androidx.compose.runtime.Stable +import androidx.compose.runtime.compositionLocalOf + +@Stable +data class AppSettings(val host:String) diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/CoreComponent.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/CoreComponent.kt new file mode 100644 index 0000000..1752d25 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/CoreComponent.kt @@ -0,0 +1,22 @@ +package com.github.springeye.memosc +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.plus + +interface CoreComponent : CoroutinesComponent { + val appPreferences: AppPreferences +} + +internal class CoreComponentImpl internal constructor() : CoreComponent, + CoroutinesComponent by CoroutinesComponentImpl.create() { + + private val dataStore: DataStore = dataStorePreferences( + corruptionHandler = null, + coroutineScope = applicationScope + Dispatchers.IO, + migrations = emptyList() + ) + + override val appPreferences : AppPreferences = AppPreferencesImpl(dataStore) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/CoroutinesComponent.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/CoroutinesComponent.kt new file mode 100644 index 0000000..113214a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/CoroutinesComponent.kt @@ -0,0 +1,23 @@ +package com.github.springeye.memosc +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob + +interface CoroutinesComponent { + val mainImmediateDispatcher: CoroutineDispatcher + val applicationScope: CoroutineScope +} + +internal class CoroutinesComponentImpl private constructor() : CoroutinesComponent { + + companion object { + fun create(): CoroutinesComponent = CoroutinesComponentImpl() + } + + override val mainImmediateDispatcher: CoroutineDispatcher = Dispatchers.Main.immediate + override val applicationScope: CoroutineScope + get() = CoroutineScope( + SupervisorJob() + mainImmediateDispatcher, + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/DataStorePreferences.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/DataStorePreferences.kt new file mode 100644 index 0000000..fe8c4c3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/DataStorePreferences.kt @@ -0,0 +1,34 @@ +package com.github.springeye.memosc +import androidx.datastore.core.DataMigration +import androidx.datastore.core.DataStore +import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.SupervisorJob +import okio.Path.Companion.toPath + +expect fun dataStorePreferences( + corruptionHandler: ReplaceFileCorruptionHandler?, + coroutineScope: CoroutineScope, + migrations: List>, +): DataStore + +internal const val SETTINGS_PREFERENCES = "settings_preferences" + +internal fun createDataStoreWithDefaults( + corruptionHandler: ReplaceFileCorruptionHandler? = null, + coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()), + migrations: List> = emptyList(), + path: () -> String, +) = PreferenceDataStoreFactory + .createWithPath( + corruptionHandler = corruptionHandler, + scope = coroutineScope, + migrations = migrations, + produceFile = { + path().toPath() + } + ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/Greeting.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/Greeting.kt new file mode 100755 index 0000000..b357277 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/Greeting.kt @@ -0,0 +1,8 @@ +package com.github.springeye.memosc +class Greeting { + private val platform = getPlatform() + + fun greet(): String { + return "Hello, ${platform.name}!" + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/LoadingAnimation.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/LoadingAnimation.kt new file mode 100644 index 0000000..16b851b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/LoadingAnimation.kt @@ -0,0 +1,74 @@ +package com.github.springeye.memosc + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.keyframes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay + +@Composable +fun LoadingAnimation( + modifier: Modifier = Modifier, + circleSize: Dp = 25.dp, + circleColor: Color = MaterialTheme.colorScheme.primary, + spaceBetween: Dp = 10.dp, + travelDistance: Dp = 20.dp +) { + val circles = listOf( + remember { Animatable(initialValue = 0f) }, + remember { Animatable(initialValue = 0f) }, + remember { Animatable(initialValue = 0f) } + ) + + circles.forEachIndexed { index, animatable -> + LaunchedEffect(key1 = animatable) { + delay(index * 100L) + animatable.animateTo( + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = keyframes { + durationMillis = 1200 + 0.0f at 0 with LinearOutSlowInEasing + 1.0f at 300 with LinearOutSlowInEasing + 0.0f at 600 with LinearOutSlowInEasing + 0.0f at 1200 with LinearOutSlowInEasing + }, + repeatMode = RepeatMode.Restart + ) + ) + } + } + + val circleValues = circles.map { it.value } + val distance = with(LocalDensity.current) { travelDistance.toPx() } + val lastCircle = circleValues.size - 1 + + Row(modifier = modifier) { + circleValues.forEachIndexed { index, value -> + Box(modifier = Modifier + .size(circleSize) + .graphicsLayer { translationY = -value * distance } + .background(color = circleColor, shape = CircleShape) + ) + if (index != lastCircle) Spacer(modifier = Modifier.width(spaceBetween)) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/MemosApi.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/MemosApi.kt new file mode 100644 index 0000000..e6686b8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/MemosApi.kt @@ -0,0 +1,136 @@ +package com.github.springeye.memosc +import com.github.springeye.memosc.model.Memo +import com.github.springeye.memosc.model.MemosRowStatus +import com.github.springeye.memosc.model.MemosVisibility +import com.github.springeye.memosc.model.Resource +import com.github.springeye.memosc.model.Status +import com.github.springeye.memosc.model.User +import de.jensklingenberg.ktorfit.Response +import de.jensklingenberg.ktorfit.http.* +import io.ktor.client.request.forms.MultiPartFormDataContent +import kotlinx.serialization.Serializable + +@Serializable +data class SignInInput( + val email: String, + var username: String, + val password: String, + val remember:Boolean=true +) +@Serializable +data class CreateMemoInput( + val content: String, + val visibility: MemosVisibility? = null, + val resourceIdList: List? = null +) +@Serializable +data class UpdateMemoOrganizerInput( + val pinned: Boolean +) +@Serializable +data class UpdateTagInput( + val name: String +) +@Serializable +data class DeleteTagInput( + val name: String +) +@Serializable +data class PatchMemoInput( + val id: Long, + val createdTs: Long? = null, + val rowStatus: MemosRowStatus? = null, + val content: String? = null, + val visibility: MemosVisibility? = null, + val resourceIdList: List? = null, +) + +interface MemosApi { + @POST("api/v1/auth/signin") + suspend fun signIn(@Body body: SignInInput):Response + + @POST("api/v1/auth/signout") + suspend fun logout() + + @GET("api/v1/user/me") + suspend fun me(): User + + + /** + * /api/v1/memo + * GET + * Summary + * Get a list of memos matching optional filters + * + * Parameters + * Name Located in Description Required Schema + * creatorId query Creator ID No integer + * creatorUsername query Creator username No string + * rowStatus query Row status No string + * pinned query Pinned No boolean + * tag query Search for tag. Do not append # No string + * content query Search for content No string + * limit query Limit No integer + * offset query Offset No integer + */ + @GET("api/v1/memo") + suspend fun listMemo( + @Query("creatorId") creatorId: Long? = null, + @Query("creatorUsername") creatorUsername: String? = null, + @Query("pinned") pinned: Boolean? = null, + @Query("tag") tag: String? = null, + @Query("limit") limit: Int? = null, + @Query("offset") offset: Int? = null, + @Query("rowStatus") rowStatus: MemosRowStatus? = null, + @Query("visibility") visibility: MemosVisibility? = null, + @Query("content") content: String? = null + ): List + + @POST("api/v1/memo") + suspend fun createMemo(@Body body: CreateMemoInput): Memo + + @GET("api/v1/tag") + suspend fun getTags(@Query("creatorId") creatorId: Long? = null): List + + @POST("api/v1/tag") + suspend fun updateTag(@Body body: UpdateTagInput): String + + @POST("api/v1/tag/delete") + suspend fun deleteTag(@Body body: DeleteTagInput): Unit + + @POST("api/v1/memo/{id}/organizer") + suspend fun updateMemoOrganizer(@Path("id") memoId: Long, @Body body: UpdateMemoOrganizerInput): Memo + + @PATCH("api/v1/memo/{id}") + suspend fun patchMemo(@Path("id") memoId: Long, @Body body: PatchMemoInput): Memo + + @DELETE("api/v1/memo/{id}") + suspend fun deleteMemo(@Path("id") memoId: Long): Unit + + @GET("api/v1/resource") + suspend fun getResources(): List + + @POST("api/v1/resource/blob") + suspend fun uploadResource(@Body map: MultiPartFormDataContent): Resource + + @DELETE("api/v1/resource/{id}") + suspend fun deleteResource(@Path("id") resourceId: Long): Unit + + @GET("api/v1/status") + suspend fun status(): Status + + /** + * Used to generate the heatmap + * @return Memo createdTs list + */ + @GET("api/v1/memo/stats") + suspend fun stats(@Query("creatorId") creatorId: Long?=null, @Query("creatorUsername") creatorUsername: String? = null): List + @GET("api/v1/memo/all") + suspend fun listAllMemo( + @Query("pinned") pinned: Boolean? = null, + @Query("tag") tag: String? = null, + @Query("visibility") visibility: MemosVisibility? = null, + @Query("limit") limit: Int? = null, + @Query("offset") offset: Int? = null + ): List +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/Platform.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/Platform.kt new file mode 100755 index 0000000..7fe02be --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/Platform.kt @@ -0,0 +1,6 @@ +package com.github.springeye.memosc +interface Platform { + val name: String +} + +expect fun getPlatform(): Platform \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/PopupNotification.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/PopupNotification.kt new file mode 100644 index 0000000..a6ce788 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/PopupNotification.kt @@ -0,0 +1,9 @@ +package com.github.springeye.memosc +import androidx.compose.runtime.MutableState + +interface PopupNotification { + fun showPopUpMessage(text: String) + fun showLoading(text: String="loading") + fun hideLoading() +} +expect fun createPopupNotification(state:MutableState): PopupNotification \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/Toast.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/Toast.kt new file mode 100755 index 0000000..20b3cde --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/Toast.kt @@ -0,0 +1,83 @@ +package com.github.springeye.memosc +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import kotlinx.coroutines.delay + +sealed interface ToastState { + object Hidden : ToastState + class Shown(val message: String) : ToastState + class Loading(val message: String) : ToastState + +} + +@Composable +fun Toast( + state: MutableState +) { + val value = state.value + if (value is ToastState.Shown) { + Box( + modifier = Modifier.fillMaxSize().padding(bottom = 20.dp), + contentAlignment = Alignment.BottomCenter + ) { + Surface( + modifier = Modifier.size(300.dp, 70.dp), + color = Color(23, 23, 23), + shape = RoundedCornerShape(4.dp) + ) { + Box(contentAlignment = Alignment.Center) { + Text(value.message, color = Color.White) + } + + LaunchedEffect(value.message) { + delay(3000) + state.value = ToastState.Hidden + } + } + } + } else if (value is ToastState.Loading) { + Dialog(onDismissRequest = { + state.value = ToastState.Hidden + }) { +// CircularProgressIndicator( +// modifier = Modifier.width(64.dp).wrapContentSize(Alignment.Center), +// color = MaterialTheme.colorScheme.secondary, +// ) + LoadingAnimation() + } +// Box( +// modifier = Modifier.fillMaxSize().background(Color.Gray), +// contentAlignment = Alignment.Center +// ) { +// Surface( +// modifier = Modifier.size(70.dp, 70.dp), +// color = Color(23, 23, 23), +// shape = RoundedCornerShape(4.dp) +// ) { +// Box(contentAlignment = Alignment.Center,modifier = Modifier.fillMaxSize() ) { +// CircularProgressIndicator( +// modifier = Modifier.width(64.dp).wrapContentSize(Alignment.Center), +// color = MaterialTheme.colors.secondary, +// ) +// } +// } +// } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/components/Heatmap.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/components/Heatmap.kt new file mode 100644 index 0000000..bed20fe --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/components/Heatmap.kt @@ -0,0 +1,39 @@ +package com.github.springeye.memosc.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.dp +import com.github.springeye.memosc.model.DailyUsageStat +import kotlinx.datetime.* + +@Composable +expect fun aa() + +@Composable +fun Heatmap(matrix:List) { + + BoxWithConstraints(modifier = Modifier.fillMaxSize()) { + LazyVerticalGrid( + columns = GridCells.Fixed(7), + horizontalArrangement = Arrangement.spacedBy(2.dp, alignment = Alignment.End), + verticalArrangement = Arrangement.spacedBy(2.dp), + userScrollEnabled = false, + ) { + val countHeatmap = countHeatmap(constraints) + matrix.takeLast(31).forEach { + item(key = it.date) { + HeatmapStat2(day = it) + } + } + } + } +} +expect fun countHeatmap(constraints: Constraints): Int \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/components/HeatmapStat.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/components/HeatmapStat.kt new file mode 100644 index 0000000..5508bd4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/components/HeatmapStat.kt @@ -0,0 +1,21 @@ +package com.github.springeye.memosc.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.github.springeye.memosc.model.DailyUsageStat +import kotlinx.datetime.Clock +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +@Composable +expect fun HeatmapStat2(day: DailyUsageStat) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/components/TextField.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/components/TextField.kt new file mode 100644 index 0000000..662a987 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/components/TextField.kt @@ -0,0 +1,102 @@ +package com.github.springeye.memosc.components + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.TextFieldColors +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ITextField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier, + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + label: @Composable (() -> Unit)? = null, + visualTransformation: VisualTransformation = VisualTransformation.None, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + prefix: @Composable (() -> Unit)? = null, + suffix: @Composable (() -> Unit)? = null, + supportingText: @Composable (() -> Unit)? = null, + shape: Shape = TextFieldDefaults.shape, + colors: TextFieldColors = TextFieldDefaults.colors( + disabledIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + ), + singleLine: Boolean = false, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + minLines: Int = 1, + isError: Boolean = false, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + + BasicTextField(value, + onValueChange = onValueChange, + modifier = modifier + .defaultMinSize( + minWidth = TextFieldDefaults.MinWidth / 2, + minHeight = TextFieldDefaults.MinHeight / 2 + ), + singleLine = singleLine, + maxLines = maxLines, + minLines = minLines, + enabled = enabled, + readOnly = readOnly, + textStyle = textStyle, + decorationBox = @Composable { innerTextField -> + TextFieldDefaults.DecorationBox( + value = value, + visualTransformation = visualTransformation, + innerTextField = innerTextField, + placeholder = placeholder, + label = label, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + prefix = prefix, + suffix = suffix, + supportingText = supportingText, + shape = shape, + singleLine = singleLine, + enabled = enabled, + isError = isError, + interactionSource = interactionSource, + colors = colors, + contentPadding = + if (label == null) { + TextFieldDefaults.contentPaddingWithoutLabel( + start = 8.dp, + end = 8.dp, + bottom = 8.dp, + top = 8.dp + ) + } else { + TextFieldDefaults.contentPaddingWithLabel( + start = 8.dp, + end = 8.dp, + bottom = 4.dp, + top = 4.dp + ) + }, + ) + } + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/AspectRatioReference.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/AspectRatioReference.kt new file mode 100644 index 0000000..95000e2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/AspectRatioReference.kt @@ -0,0 +1,15 @@ +package com.github.springeye.memosc.core +enum class AspectRatioReference { + + /** Child width matches parent width. Child height adjusted to keep the ratio */ + PARENT_WIDTH, + + /** Child height matches parent height. Child height adjusted to keep the ratio */ + PARENT_HEIGHT, + + /** Child fits parent smallest width or height */ + MIN_PARENT_WIDTH_PARENT_HEIGHT, + + /** Child fits parent biggest width or height */ + MAX_PARENT_WIDTH_PARENT_HEIGHT +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/AspectRatioReferenceModifier.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/AspectRatioReferenceModifier.kt new file mode 100644 index 0000000..7d29624 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/AspectRatioReferenceModifier.kt @@ -0,0 +1,130 @@ +package com.github.springeye.memosc.core + +import androidx.compose.runtime.Stable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.IntrinsicMeasurable +import androidx.compose.ui.layout.IntrinsicMeasureScope +import androidx.compose.ui.layout.LayoutModifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.platform.InspectorValueInfo +import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntSize +import kotlin.math.roundToInt + +@Stable +fun Modifier.aspectRatioReference( + ratioWidth: Float, + ratioHeight: Float, + reference: AspectRatioReference = AspectRatioReference.MIN_PARENT_WIDTH_PARENT_HEIGHT +) = then( + AspectRatioReferenceModifier( + ratioWidth = ratioWidth, + ratioHeight = ratioHeight, + reference = reference, + debugInspectorInfo { + name = "aspectRatioReference" + properties["ratioWidth"] = ratioWidth + properties["ratioHeight"] = ratioHeight + properties["reference"] = reference + } + ) +) + +private class AspectRatioReferenceModifier( + private val ratioWidth: Float, + private val ratioHeight: Float, + private val reference: AspectRatioReference, + inspectorInfo: InspectorInfo.() -> Unit +) : LayoutModifier, InspectorValueInfo(inspectorInfo) { + + init { + require(ratioWidth > 0) { "ratioWidth $ratioWidth must be > 0" } + require(ratioHeight > 0) { "ratioHeight $ratioHeight must be > 0" } + } + + private val ratio = ratioWidth / ratioHeight + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureResult { + val size = constraints.findSize() + val wrappedConstraints = if (size != IntSize.Zero) { + Constraints.fixed(size.width, size.height) + } else { + constraints + } + val placeable = measurable.measure(wrappedConstraints) + return layout(placeable.width, placeable.height) { + placeable.placeRelative(0, 0) + } + } + + override fun IntrinsicMeasureScope.minIntrinsicWidth( + measurable: IntrinsicMeasurable, + height: Int + ) = if (height != Constraints.Infinity) { + (height * ratio).roundToInt() + } else { + measurable.minIntrinsicWidth(height) + } + + override fun IntrinsicMeasureScope.maxIntrinsicWidth( + measurable: IntrinsicMeasurable, + height: Int + ) = if (height != Constraints.Infinity) { + (height * ratio).roundToInt() + } else { + measurable.maxIntrinsicWidth(height) + } + + override fun IntrinsicMeasureScope.minIntrinsicHeight( + measurable: IntrinsicMeasurable, + width: Int + ) = if (width != Constraints.Infinity) { + (width / ratio).roundToInt() + } else { + measurable.minIntrinsicHeight(width) + } + + override fun IntrinsicMeasureScope.maxIntrinsicHeight( + measurable: IntrinsicMeasurable, + width: Int + ) = if (width != Constraints.Infinity) { + (width / ratio).roundToInt() + } else { + measurable.maxIntrinsicHeight(width) + } + + private fun Constraints.findSize(): IntSize { + val matchPARENTWidth = when (reference) { + AspectRatioReference.PARENT_WIDTH -> true + AspectRatioReference.PARENT_HEIGHT -> false + AspectRatioReference.MIN_PARENT_WIDTH_PARENT_HEIGHT -> maxWidth < maxHeight + AspectRatioReference.MAX_PARENT_WIDTH_PARENT_HEIGHT -> maxWidth > maxHeight + } + return if (matchPARENTWidth) { + IntSize(maxWidth, (maxWidth * ratioHeight / ratioWidth).roundToInt()) + } else { + IntSize((maxHeight * ratioWidth / ratioHeight).roundToInt(), maxHeight) + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + val otherModifier = other as? AspectRatioReferenceModifier ?: return false + return ratio == otherModifier.ratio && reference == other.reference + } + + override fun hashCode(): Int = ratio.hashCode() * 31 + reference.hashCode() + + override fun toString(): String = "AspectRatioReferenceModifier(" + + "ratioWidth=$ratioWidth, " + + "ratioHeight=$ratioHeight, " + + "reference=$reference" + + ")" +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/Base64ImageFetcher.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/Base64ImageFetcher.kt new file mode 100644 index 0000000..5c37eaa --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/Base64ImageFetcher.kt @@ -0,0 +1,43 @@ +package com.github.springeye.memosc.core + +import io.kamel.core.DataSource +import io.kamel.core.Resource +import io.kamel.core.config.ResourceConfig +import io.kamel.core.fetcher.Fetcher +import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi +import kotlin.reflect.KClass + +data class Base64Image(val content:String?=null) +class Base64ImageFetcher : Fetcher { + + override val inputDataKClass: KClass + get() = Base64Image::class + override val source: DataSource + get() = DataSource.None + + @OptIn(ExperimentalEncodingApi::class) + override fun fetch( + data: Base64Image, + resourceConfig: ResourceConfig + ): Flow> { + return flow { + try { + val reg = "^data:image/([a-zA-Z]*);base64,([^\"]*)".toRegex(RegexOption.IGNORE_CASE) + val matchResult = + reg.matchEntire(data.content ?: "") ?: throw Exception("not match") + val (type, base64) = matchResult.destructured + val bytes = Base64.decode(base64) + emit(Resource.Success(ByteReadChannel(bytes))) + } catch (e: Exception) { + emit(Resource.Failure(e)) + } + } + } + + override val Base64Image.isSupported: Boolean + get() = "^data:image/([a-zA-Z]*);base64,([^\"]*)".toRegex(RegexOption.IGNORE_CASE).matches(content?:"") +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/IFile.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/IFile.kt new file mode 100644 index 0000000..1cba784 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/IFile.kt @@ -0,0 +1,7 @@ +package com.github.springeye.memosc.core +interface IFile{ + fun readBytes():ByteArray + val mimeType:String + val filename:String +} +expect fun createIFile(path:String):IFile \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/Instant.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/Instant.kt new file mode 100644 index 0000000..244241e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/core/Instant.kt @@ -0,0 +1,7 @@ +package com.github.springeye.memosc.core +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate + +expect fun Instant.formatDate(pattern: String, defValue: String = ""): String + +expect fun String.parseDate(pattern: String, defValue: Long = 0L): Long \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/db/AppDatabase.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/db/AppDatabase.kt new file mode 100644 index 0000000..d06e82c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/db/AppDatabase.kt @@ -0,0 +1,5 @@ +package com.github.springeye.memosc.db + +import com.github.springeye.memosc.db.model.AppDatabase + +expect fun createAppDatabase():AppDatabase \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/db/MemoQueryWhere.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/db/MemoQueryWhere.kt new file mode 100644 index 0000000..9ad1147 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/db/MemoQueryWhere.kt @@ -0,0 +1,17 @@ +package com.github.springeye.memosc.db + +import com.github.springeye.memosc.model.MemosRowStatus +import com.github.springeye.memosc.model.MemosVisibility + + +data class MemoQueryWhere( + val creatorId: Long?=null, + val creatorUsername: String?=null, + val pinned: Boolean?=null, + val tag: String?=null, + val limit: Int?=null, + val offset: Int?=null, + val rowStatus: MemosRowStatus?=null, + val visibility: MemosVisibility?=null, + val content: String?=null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/db/MemoRemoteMediator.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/db/MemoRemoteMediator.kt new file mode 100644 index 0000000..86c4754 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/db/MemoRemoteMediator.kt @@ -0,0 +1,61 @@ +package com.github.springeye.memosc.db + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import app.cash.paging.RemoteMediator +import com.github.springeye.memosc.db.model.QueryQueries + +import com.github.springeye.memosc.model.Memo +import com.github.springeye.memosc.repository.MemoRepository + + +@OptIn(ExperimentalPagingApi::class) +class MemoRemoteMediator(private val api: MemoRepository, + private val memoQueries: QueryQueries, + private val query: MemoQueryWhere = MemoQueryWhere(),): RemoteMediator() { + override suspend fun initialize(): InitializeAction { + return InitializeAction.SKIP_INITIAL_REFRESH + } + override suspend fun load(loadType: LoadType, state: PagingState): MediatorResult { + return try { +// val loadKey = when (loadType) { +// LoadType.REFRESH -> null +// +// LoadType.PREPEND -> return MediatorResult.Success( +// endOfPaginationReached = true +// ) +// LoadType.APPEND -> { +// val remoteKey = +// remoteKeyDao.remoteKeyByQuery(query?.creatorId,query?.rowStatus,query?.visibility) +// +// return MediatorResult.Success( +// endOfPaginationReached = true +// ) +// +// remoteKey +// } +// } + + + // Suspending network load via Retrofit. This doesn't need to be wrapped in a + // withContext(Dispatcher.IO) { ... } block since Retrofit's Coroutine CallAdapter + // dispatches on a worker thread. + val response = api.listMemo(filter = query) + memoQueries.transaction { + memoQueries.deleteAllMemo() + memoQueries.deleteAllResource() + for (memo in response) { + memoQueries.insertMemo(memo.toDbMemo()) + for (resource in memo.resourceList) { + memoQueries.insertResource(resource.toDBResource(memo.id)) + } + } + } + MediatorResult.Success(endOfPaginationReached = true) + } catch (e: Exception) { + MediatorResult.Error(e) + } + + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/di/PersistentCookiesStorage.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/di/PersistentCookiesStorage.kt new file mode 100644 index 0000000..e72174b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/di/PersistentCookiesStorage.kt @@ -0,0 +1,88 @@ +package com.github.springeye.memosc.di + +import com.github.springeye.memosc.AppPreferences +import io.ktor.client.plugins.cookies.CookiesStorage +import io.ktor.http.Cookie +import io.ktor.http.CookieEncoding +import io.ktor.http.Url +import io.ktor.http.hostIsIp +import io.ktor.http.isSecure +import io.ktor.util.date.GMTDate +import io.ktor.util.toLowerCasePreservingASCIIRules + +expect class PersistentCookiesStorage(store: AppPreferences):CookiesStorage +internal fun Cookie.matches(requestUrl: Url): Boolean { + val domain = domain?.toLowerCasePreservingASCIIRules()?.trimStart('.') + ?: error("Domain field should have the default value") + + val path = with(path) { + val current = path ?: error("Path field should have the default value") + if (current.endsWith('/')) current else "$path/" + } + + val host = requestUrl.host.toLowerCasePreservingASCIIRules() + val requestPath = let { + val pathInRequest = requestUrl.encodedPath + if (pathInRequest.endsWith('/')) pathInRequest else "$pathInRequest/" + } + + if (host != domain && (hostIsIp(host) || !host.endsWith(".$domain"))) { + return false + } + + if (path != "/" && + requestPath != path && + !requestPath.startsWith(path) + ) { + return false + } + + return !(secure && !requestUrl.protocol.isSecure()) +} + +internal fun Cookie.fillDefaults(requestUrl: Url): Cookie { + var result = this + + if (result.path?.startsWith("/") != true) { + result = result.copy(path = requestUrl.encodedPath) + } + + if (result.domain.isNullOrBlank()) { + result = result.copy(domain = requestUrl.host) + } + + return result +} + +fun fromString(string: String): Cookie { + val strings = string.split(":") + val name = strings[0]; + val value = strings[1]; + val encoding = strings[2]; + val maxAge = strings[3].toInt(); + val expires = strings[4].toLong(); + val domain = strings[5]; + val path = strings[6]; + val secure = strings[7].toBoolean(); + val httpOnly = strings[8].toBoolean(); + val extensions = strings[9].split(",").map { + val keyValue = it.split("#") + keyValue[0] to keyValue[1] + }.toMap() + return Cookie( + name, value, + CookieEncoding.valueOf(encoding), maxAge, + GMTDate(expires), domain, path, secure, httpOnly, extensions, + ) +} + +internal fun Cookie.toSaveString(): String { + val cookie = this + val extensions = extensions.map { + "${it.key}#${it.value}" + }.joinToString(",") + val c = + "${cookie.name}:${cookie.value}:${cookie.encoding}:${cookie.maxAge}:${cookie.expires?.timestamp}:${cookie.domain}:${cookie.path}:${cookie.secure}:${cookie.httpOnly}:$extensions" + + return c +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/di/module.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/di/module.kt new file mode 100644 index 0000000..b01baa2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/di/module.kt @@ -0,0 +1,135 @@ +package com.github.springeye.memosc.di + +import com.github.springeye.memosc.AppPreferences +import com.github.springeye.memosc.CoreComponent +import com.github.springeye.memosc.CoreComponentImpl +import com.github.springeye.memosc.MemosApi + +import com.github.springeye.memosc.db.createAppDatabase +import com.github.springeye.memosc.db.model.AppDatabase +import com.github.springeye.memosc.repository.MemoRepository +import com.github.springeye.memosc.ui.AppModel +import com.github.springeye.memosc.ui.app.AppScreenModel +import com.github.springeye.memosc.ui.home.ArchivedModel +import com.github.springeye.memosc.ui.home.MemoModel +import com.github.springeye.memosc.ui.home.NotifiModel +import com.github.springeye.memosc.ui.home.ProfileModel +import com.github.springeye.memosc.ui.login.LoginScreenModel +import de.jensklingenberg.ktorfit.Ktorfit +import de.jensklingenberg.ktorfit.converter.builtin.CallConverterFactory +import de.jensklingenberg.ktorfit.ktorfit +import io.github.aakira.napier.Napier +import io.ktor.client.HttpClient +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.cookies.CookiesStorage +import io.ktor.client.plugins.cookies.HttpCookies +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.request.header +import io.ktor.serialization.kotlinx.json.json +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import org.koin.dsl.module + + +@OptIn(ExperimentalSerializationApi::class) +val appModule = module { + + single { + CoreComponentImpl() + } + single { + get().appPreferences + } + + single { + Json { + encodeDefaults = true + isLenient = true + ignoreUnknownKeys=true + explicitNulls=false + allowSpecialFloatingPointValues = true +// namingStrategy= JsonNamingStrategy.SnakeCase + allowStructuredMapKeys = true + prettyPrint = false + useArrayPolymorphism = false + } + } + single { + PersistentCookiesStorage(get()) + } + single { + HttpClient { + defaultRequest { + + if(headers["Content-Type"].isNullOrEmpty()){ + header("Content-Type","application/json") + } + url{ + val prefer: AppPreferences =get() + val url=runBlocking { prefer.host() } + //this url should https://api.github.com + url + } + } + install(Logging) { + logger = object: Logger { + override fun log(message: String) { + Napier.v("HTTP Client", null, message) + } + } + level = LogLevel.ALL + } + install(HttpCookies){ + this.storage=get() + } + install(ContentNegotiation) { + json(json= get()) + } + } + } + single { + + ktorfit { + val sp=get() + val host = runBlocking { sp.host() ?: "" } + baseUrl(host,false) + httpClient(get()) + converterFactories( + CallConverterFactory() + ) + } + } + single { + val ktorfit=get() + ktorfit.create() + } + single{ + MemoRepository(get()) + } + single { + createAppDatabase() + } + single { + get().queryQueries + } +} +val homeModule = module { + + single { + val sp=get() + val host = runBlocking { sp.host() ?: "" } + AppModel(host) + } + single { AppScreenModel(get(), get(),get()) } + single { LoginScreenModel(get(),get(),get()) } + single { MemoModel(get(),get(),get()) } + single { NotifiModel() } + single { ProfileModel(get(),get(),get()) } + single { ArchivedModel(get(),get(),get()) } + +} + diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/logger.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/logger.kt new file mode 100644 index 0000000..73c5e04 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/logger.kt @@ -0,0 +1,21 @@ +package com.github.springeye.memosc +import io.github.aakira.napier.Napier + +expect fun initLogger() +object Logger{ + fun d(message: String, throwable: Throwable? = null, tag: String? = null){ + Napier.d(message,throwable,tag) + } + fun i(message: String, throwable: Throwable? = null, tag: String? = null){ + Napier.i(message,throwable,tag) + } + fun e(message: String, throwable: Throwable? = null, tag: String? = null){ + Napier.e(message,throwable,tag) + } + fun w(message: String, throwable: Throwable? = null, tag: String? = null){ + Napier.w(message,throwable,tag) + } + fun v(message: String, throwable: Throwable? = null, tag: String? = null){ + Napier.v(message,throwable,tag) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/DailyUsageStat.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/DailyUsageStat.kt new file mode 100644 index 0000000..69dd6d2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/DailyUsageStat.kt @@ -0,0 +1,20 @@ +package com.github.springeye.memosc.model + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.toJavaLocalDate +import java.time.format.DateTimeFormatter + +data class DailyUsageStat( + //milliseconds + val date: LocalDate, + val count: Int = 0 +) { + override fun toString(): String { + return date.toJavaLocalDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + } +} +expect fun initialMatrix(): List + + +expect val weekDays:List +expect fun calculateMatrix(memos: Map>): List \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/ErrorMessage.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/ErrorMessage.kt new file mode 100644 index 0000000..5f622d2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/ErrorMessage.kt @@ -0,0 +1,8 @@ +package com.github.springeye.memosc.model +import kotlinx.serialization.Serializable + +@Serializable +data class ErrorMessage( + val error: String? = null, + val message: String +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/Memo.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/Memo.kt new file mode 100644 index 0000000..0945ce9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/Memo.kt @@ -0,0 +1,78 @@ +package com.github.springeye.memosc.model + + +import com.github.springeye.memosc.db.model.QueryQueries +import kotlinx.serialization.Serializable + +@Serializable +enum class MemosVisibility { + + PUBLIC, + + PROTECTED, + + PRIVATE +} +@Serializable +enum class MemosRowStatus { + + NORMAL, + + ARCHIVED +} +@Serializable +data class Memo( + val id: Long, + val createdTs: Long, + val creatorId: Long, + val creatorName: String? = null, + var content: String, + var pinned: Boolean, + val rowStatus: MemosRowStatus = MemosRowStatus.NORMAL, + val updatedTs: Long, + val visibility: MemosVisibility = MemosVisibility.PRIVATE, + val resourceList: MutableList = mutableListOf() +){ + fun loadResource(memoQueries: QueryQueries) { + resourceList.clear() + resourceList.addAll(memoQueries.selectResourceByMemoId(this.id).executeAsList().map { Resource.fromDBResource(it) }) + + } + fun toDbMemo(): com.github.springeye.memosc.db.model.Memo { + return com.github.springeye.memosc.db.model.Memo( + id, + createdTs, + creatorId, + creatorName, + content, + pinned, + rowStatus, + visibility, + updatedTs + ) + } + companion object{ + fun empty(): Memo { + return Memo( + System.currentTimeMillis(), System.currentTimeMillis(), 1, "henjue", + "aaa", + false, MemosRowStatus.NORMAL,System.currentTimeMillis(), + ) + } + fun fromDbMemo(memo:com.github.springeye.memosc.db.model.Memo):Memo{ + return with(memo) { + Memo( + id, + createdTs, + creatorId, + creatorName, + content, + pinned, + rowStatus, + updatedTs, + visibility + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/Resource.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/Resource.kt new file mode 100644 index 0000000..2174af4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/Resource.kt @@ -0,0 +1,72 @@ +package com.github.springeye.memosc.model + +import io.ktor.http.URLBuilder +import io.ktor.http.path +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +data class Resource( + val id: Long, + val createdTs: Long, + val creatorId: Long, + val filename: String, + val size: Long, + val type: String, + val updatedTs: Long, + val externalLink: String?, + val publicId: String?, + @Transient + val memoId: Long=0 +) { + fun toDBResource( memoId: Long): com.github.springeye.memosc.db.model.Resource { + return com.github.springeye.memosc.db.model.Resource( + id, + createdTs, + creatorId, + filename, + size, + type, + updatedTs, + externalLink, + publicId, + memoId + ) + } + fun uri(host: String): String { + if (!externalLink.isNullOrEmpty()) { + return URLBuilder(externalLink).buildString() + } + if (!publicId.isNullOrEmpty()) { + return URLBuilder(host) + .apply { + path("o","r",id.toString(),publicId) + } + .buildString() + } + return return URLBuilder(host) + .apply { + path("o","r",id.toString(),filename) + } + .buildString() + } + companion object{ + fun fromDBResource(resource:com.github.springeye.memosc.db.model.Resource):Resource{ + return with(resource) { + Resource( + id, + createdTs, + creatorId, + filename, + size, + type, + updatedTs, + externalLink, + publicId, + memoId?:0 + ) + } + + } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/ShareContent.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/ShareContent.kt new file mode 100644 index 0000000..aecb002 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/ShareContent.kt @@ -0,0 +1,51 @@ +package com.github.springeye.memosc.model + +import kotlinx.serialization.Serializable + + +@Serializable +data class ShareContent( + val text: String = "", + val images: List = ArrayList() +) { +// companion object { +// fun parseIntent(intent: Intent): ShareContent { +// val text = intent.getStringExtra(Intent.EXTRA_TEXT) +// val images = ArrayList() +// +// when (intent.action) { +// Intent.ACTION_SEND -> { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { +// intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)?.let { +// images.add(it) +// } +// } else { +// @Suppress("DEPRECATION") +// (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { +// images.add(it) +// } +// } +// } +// +// Intent.ACTION_SEND_MULTIPLE -> { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { +// intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)?.let { +// images.addAll(it) +// } +// } else { +// @Suppress("DEPRECATION") +// intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.let { +// for (item in it) { +// if (item is Uri) { +// images.add(item) +// } +// } +// } +// } +// } +// } +// +// return ShareContent(text ?: "", images) +// } +// } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/Status.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/Status.kt new file mode 100644 index 0000000..608e7a2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/Status.kt @@ -0,0 +1,14 @@ +package com.github.springeye.memosc.model +import kotlinx.serialization.Serializable + + +@Serializable +data class Profile( + val mode: String, + val version: String +) + +@Serializable +data class Status( + val profile: Profile +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/User.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/User.kt new file mode 100644 index 0000000..8d83e59 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/model/User.kt @@ -0,0 +1,54 @@ +package com.github.springeye.memosc.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +enum class MemosRole { + HOST, + USER +} + +@Serializable +enum class MemosUserSettingKey { + @SerialName("locale") + LOCALE, + @SerialName("memo-visibility") + MEMO_VISIBILITY, + @SerialName("editorFontStyle") + EDITOR_FONT_STYLE, + UNKNOWN +} + +@Serializable +data class MemosUserSetting( + val key: MemosUserSettingKey = MemosUserSettingKey.UNKNOWN, + val value: String +) + +@Serializable +data class User( + val createdTs: Long, + val email: String?, + val id: Long, + val name: String?, + val role: MemosRole = MemosRole.USER, + val rowStatus: MemosRowStatus = MemosRowStatus.NORMAL, + val updatedTs: Long?, + val userSettingList: List? = null, + val nickname: String?, + val username: String?, + val avatarUrl: String?, +) { + val displayEmail get() = email ?: username ?: "" + val displayName get() = nickname ?: name ?: "" + + val memoVisibility: MemosVisibility + get() = userSettingList?.find { it.key == MemosUserSettingKey.MEMO_VISIBILITY }?.let { + try { + MemosVisibility.valueOf(it.value.removePrefix("\"").removeSuffix("\"")) + } catch (_: IllegalArgumentException) { + MemosVisibility.PRIVATE + } + } ?: MemosVisibility.PRIVATE +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/repository/MemoPagingSource.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/repository/MemoPagingSource.kt new file mode 100644 index 0000000..ab57366 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/repository/MemoPagingSource.kt @@ -0,0 +1,31 @@ +package com.github.springeye.memosc.repository + +import androidx.paging.PagingState +import app.cash.paging.PagingSource +import com.github.springeye.memosc.db.MemoQueryWhere +import com.github.springeye.memosc.model.Memo +import io.github.aakira.napier.Napier + +class MemoPagingSource( + val repository: MemoRepository, + val filter: MemoQueryWhere, + ): PagingSource() { + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) + } + } + + override suspend fun load(params: LoadParams): LoadResult { + val nextPage:Int = (params.key ?: 0) + val size = params.loadSize + Napier.d("page:$nextPage size:$size") + val newFilter=filter.copy(offset = nextPage * params.loadSize, limit = size) + val list = repository.listMemo(filter = newFilter) + return LoadResult.Page(list, + prevKey = if(nextPage>0) nextPage-1 else null, + nextKey = if(list.size>=size) nextPage+1 else null) + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/repository/MemoRepository.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/repository/MemoRepository.kt new file mode 100644 index 0000000..cfab311 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/repository/MemoRepository.kt @@ -0,0 +1,15 @@ +package com.github.springeye.memosc.repository + +import com.github.springeye.memosc.MemosApi +import com.github.springeye.memosc.db.MemoQueryWhere +import com.github.springeye.memosc.model.Memo +import com.github.springeye.memosc.model.MemosRowStatus +import com.github.springeye.memosc.model.MemosVisibility +import kotlinx.coroutines.delay + + +class MemoRepository(private val api: MemosApi,) { + suspend fun listMemo(filter: MemoQueryWhere=MemoQueryWhere()): List { + return api.listMemo(filter.creatorId, filter.creatorUsername, filter.pinned, filter.tag, filter.limit, filter.offset, filter.rowStatus, filter.visibility, filter.content) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/AppModel.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/AppModel.kt new file mode 100644 index 0000000..95806d0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/AppModel.kt @@ -0,0 +1,9 @@ +package com.github.springeye.memosc.ui +import cafe.adriel.voyager.core.model.StateScreenModel +import com.github.springeye.memosc.AppSettings + +class AppModel(private val host:String): StateScreenModel(AppSettings(host)) { + fun setHost(host: String){ + mutableState.value=mutableState.value.copy(host=host) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/app/AppScreenModel.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/app/AppScreenModel.kt new file mode 100644 index 0000000..ae8f42b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/app/AppScreenModel.kt @@ -0,0 +1,41 @@ +package com.github.springeye.memosc.ui.app +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import com.github.springeye.memosc.AppPreferences +import com.github.springeye.memosc.MemosApi +import io.ktor.client.HttpClient +import io.ktor.client.plugins.cookies.cookies +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class AppScreenModel(private val api: MemosApi, + private val httpClient: HttpClient, + private val prefers: AppPreferences + +) : StateScreenModel(State.Loading) { + sealed class State { + + object Loading : State() + data class Result(val isLogin:Boolean) : State() + } + + fun check(){ + + screenModelScope.launch { + val host=prefers.host() + val username=prefers.username() + val password=prefers.password() + mutableState.value=State.Loading + delay(1000) + if(host!=null){ + val cookies=httpClient.cookies(host) + if(cookies.isNotEmpty()){ + mutableState.value=State.Result(true) + return@launch + } + } + mutableState.value=State.Result(false) +// println("cookies===>${cookies}") + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/ArchivedModel.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/ArchivedModel.kt new file mode 100644 index 0000000..ffa9126 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/ArchivedModel.kt @@ -0,0 +1,85 @@ +package com.github.springeye.memosc.ui.home + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.paging.ExperimentalPagingApi +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import androidx.paging.filter +import app.cash.paging.Pager +import app.cash.paging.map +import app.cash.sqldelight.Query +import app.cash.sqldelight.coroutines.asFlow +import app.cash.sqldelight.coroutines.mapToList +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import com.github.springeye.memosc.CreateMemoInput +import com.github.springeye.memosc.MemosApi +import com.github.springeye.memosc.PatchMemoInput +import com.github.springeye.memosc.UpdateMemoOrganizerInput +import com.github.springeye.memosc.core.createIFile +import com.github.springeye.memosc.core.formatDate +import com.github.springeye.memosc.core.parseDate +import com.github.springeye.memosc.db.MemoQueryWhere +import com.github.springeye.memosc.db.model.QueryQueries +import com.github.springeye.memosc.model.DailyUsageStat +import com.github.springeye.memosc.model.Memo +import com.github.springeye.memosc.model.MemosRowStatus +import com.github.springeye.memosc.model.MemosVisibility +import com.github.springeye.memosc.model.Resource +import com.github.springeye.memosc.model.calculateMatrix +import com.github.springeye.memosc.model.initialMatrix +import com.github.springeye.memosc.repository.MemoPagingSource +import com.github.springeye.memosc.repository.MemoRepository +import io.ktor.client.request.forms.MultiPartFormDataContent +import io.ktor.client.request.forms.formData +import io.ktor.http.Headers +import io.ktor.http.HttpHeaders +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.datetime.Instant +import org.jetbrains.compose.resources.LoadState + +@OptIn(ExperimentalPagingApi::class) +class ArchivedModel( + private val api: MemosApi, + private val repository: MemoRepository, + private val memoQueries: QueryQueries, + + + ) : ScreenModel { + + + + + fun getPagerByUserId(uid:Long): Flow> { + return Pager( + config = PagingConfig(pageSize = 10), +// remoteMediator = MemoRemoteMediator(repository, memoQueries,query) + ) { + MemoPagingSource(repository,MemoQueryWhere(creatorId = uid, rowStatus = MemosRowStatus.ARCHIVED)) + + }.flow.cachedIn(screenModelScope) + } + + + suspend fun setPininned(it: Memo) { + api.updateMemoOrganizer(it.id, UpdateMemoOrganizerInput(!it.pinned)) + } + + suspend fun remove(it: Memo) { + api.deleteMemo(it.id) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/MemoModel.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/MemoModel.kt new file mode 100644 index 0000000..cf0f306 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/MemoModel.kt @@ -0,0 +1,224 @@ +package com.github.springeye.memosc.ui.home + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.paging.ExperimentalPagingApi +import androidx.paging.PagingConfig +import androidx.paging.cachedIn +import app.cash.paging.Pager +import app.cash.sqldelight.Query +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import com.github.springeye.memosc.CreateMemoInput +import com.github.springeye.memosc.MemosApi +import com.github.springeye.memosc.PatchMemoInput +import com.github.springeye.memosc.UpdateMemoOrganizerInput +import com.github.springeye.memosc.core.createIFile +import com.github.springeye.memosc.core.formatDate +import com.github.springeye.memosc.core.parseDate +import com.github.springeye.memosc.db.MemoQueryWhere +import com.github.springeye.memosc.db.model.QueryQueries +import com.github.springeye.memosc.model.DailyUsageStat +import com.github.springeye.memosc.model.Memo +import com.github.springeye.memosc.model.MemosRowStatus +import com.github.springeye.memosc.model.MemosVisibility +import com.github.springeye.memosc.model.Resource +import com.github.springeye.memosc.model.User +import com.github.springeye.memosc.model.calculateMatrix +import com.github.springeye.memosc.model.initialMatrix +import com.github.springeye.memosc.repository.MemoPagingSource +import com.github.springeye.memosc.repository.MemoRepository +import io.ktor.client.request.forms.MultiPartFormDataContent +import io.ktor.client.request.forms.formData +import io.ktor.http.Headers +import io.ktor.http.HttpHeaders +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toKotlinInstant +import kotlinx.datetime.toLocalDateTime +import java.util.Date + +@OptIn(ExperimentalPagingApi::class) +class MemoModel( + private val api: MemosApi, + private val repository: MemoRepository, + private val memoQueries: QueryQueries, + + + ) : ScreenModel { + + + private val resources = mutableStateListOf() + var resourcesGroup: Flow>> = snapshotFlow { resources.toList() } + .map { + return@map it.sortedByDescending { it.createdTs }.groupBy { + val old = Instant.fromEpochSeconds(it.createdTs).formatDate("yyyy-MM") + val time = old.parseDate("yyyy-MM") + val date = Instant.fromEpochMilliseconds(time) + date + } + } + + private val _query = MutableStateFlow(MemoQueryWhere()) + + @OptIn(ExperimentalCoroutinesApi::class) + val pager = _query.flatMapLatest { query -> + Pager( + config = PagingConfig(pageSize = 10), +// remoteMediator = MemoRemoteMediator(repository, memoQueries,query) + ) { + MemoPagingSource(repository, query) +// QueryPagingSource( +// countQuery =memoQueries.countMemos(), +// transacter =memoQueries, +// context = Dispatchers.IO, +// queryProvider = createQuery(memoQueries,query) +// ) + }.flow.cachedIn(screenModelScope) + } + var user by mutableStateOf(null) + private set + + fun fetchExtraInfo() { + screenModelScope.launch { + user = api.me() + if (user != null) { + resources.clear() + resources.addAll(api.getResources()) + + + val stats = api.stats(user!!.id).map { + Date(it * 1000).toInstant().toKotlinInstant().toLocalDateTime( + TimeZone.currentSystemDefault() + ).date + }.groupBy { it } + matrix=calculateMatrix(stats) + + } + } + + } + + var matrix by mutableStateOf>(initialMatrix()) + private set + + + var query by mutableStateOf(MemoQueryWhere()) + + + + suspend fun upload(path: String): Resource { + val multipart = formData { + val iFile = createIFile(path) + val bytes = iFile.readBytes() + val mimeType = iFile.mimeType + val filename = iFile.filename + append("file", bytes, Headers.build { + append(HttpHeaders.ContentType, mimeType) + append(HttpHeaders.ContentDisposition, "filename=${filename}") + }) + } + return api.uploadResource(MultiPartFormDataContent(multipart)) + + } + + + suspend fun submit( + editId: Long, + content: String, + resources: List, + visibility: MemosVisibility + ) { + + if (editId > 0) { + val memo = api.patchMemo( + editId, + PatchMemoInput( + editId, + content = content, + visibility = visibility, + resourceIdList = resources.map { it.id }) + ); +// memos.indexOfFirst { memo.id == it.id }.takeIf { it >= 0 }?.let { +// memos[it] = memo +// } + } else { + val memo = api.createMemo( + CreateMemoInput( + content, + visibility = visibility, + resourceIdList = resources.map { it.id }) + ); +// memos.add(0, memo) + } + + } + + + suspend fun filterCreatorId(creatorId: Long? = null) { + query = query.copy(creatorId = creatorId) + _query.emit(_query.value.copy(creatorId = creatorId)) + } + + suspend fun filterRowStatus(rowStatus: MemosRowStatus? = null) { + query = query.copy(rowStatus = rowStatus) + _query.emit(_query.value.copy(rowStatus = rowStatus)) + } + + suspend fun filterVisibility(visibility: MemosVisibility? = null) { + query = query.copy(visibility = visibility) + _query.emit(_query.value.copy(visibility = visibility)) + } + + fun filterContent(content: String? = null) { + query = query.copy(content = content) + screenModelScope.launch { + _query.emit(_query.value.copy(content = content)) + } + } + + + private fun createQuery( + memoQueries: QueryQueries, + query: MemoQueryWhere + ): (limit: Long, offset: Long) -> Query { + return { limit: Long, offset: Long -> + memoQueries.memos( + limit, + offset + ) { id: Long, createdTs: Long, creatorId: Long, creatorName: String?, content: String, pinned: Boolean, rowStatus: MemosRowStatus, visibility: MemosVisibility, updatedTs: Long -> + return@memos com.github.springeye.memosc.model.Memo( + id, + createdTs, + creatorId, + creatorName, + content, + pinned, + rowStatus, + updatedTs, + visibility + ).apply { + loadResource(memoQueries) + } + } + } + } + + suspend fun setPininned(it: Memo) { + api.updateMemoOrganizer(it.id, UpdateMemoOrganizerInput(!it.pinned)) + } + + suspend fun remove(it: Memo) { + api.deleteMemo(it.id) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/NotifiModel.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/NotifiModel.kt new file mode 100644 index 0000000..572a2b7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/NotifiModel.kt @@ -0,0 +1,27 @@ +package com.github.springeye.memosc.ui.home + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +class NotifiModel:ScreenModel { + val items= mutableStateListOf("") + var counter by mutableStateOf( 0) + init { + + snapshotFlow { items.toList() }.onEach { + println(it) + counter+=it.sumOf { it.length }; + }.launchIn(screenModelScope) + } + fun addItem(value:String){ + items.add(value) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/ProfileModel.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/ProfileModel.kt new file mode 100644 index 0000000..4aca8f7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/home/ProfileModel.kt @@ -0,0 +1,86 @@ +package com.github.springeye.memosc.ui.home + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.paging.ExperimentalPagingApi +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import androidx.paging.filter +import app.cash.paging.Pager +import app.cash.paging.map +import app.cash.sqldelight.Query +import app.cash.sqldelight.coroutines.asFlow +import app.cash.sqldelight.coroutines.mapToList +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import com.github.springeye.memosc.CreateMemoInput +import com.github.springeye.memosc.MemosApi +import com.github.springeye.memosc.PatchMemoInput +import com.github.springeye.memosc.UpdateMemoOrganizerInput +import com.github.springeye.memosc.core.createIFile +import com.github.springeye.memosc.core.formatDate +import com.github.springeye.memosc.core.parseDate +import com.github.springeye.memosc.db.MemoQueryWhere +import com.github.springeye.memosc.db.model.QueryQueries +import com.github.springeye.memosc.model.DailyUsageStat +import com.github.springeye.memosc.model.Memo +import com.github.springeye.memosc.model.MemosRowStatus +import com.github.springeye.memosc.model.MemosVisibility +import com.github.springeye.memosc.model.Resource +import com.github.springeye.memosc.model.calculateMatrix +import com.github.springeye.memosc.model.initialMatrix +import com.github.springeye.memosc.repository.MemoPagingSource +import com.github.springeye.memosc.repository.MemoRepository +import io.ktor.client.request.forms.MultiPartFormDataContent +import io.ktor.client.request.forms.formData +import io.ktor.http.Headers +import io.ktor.http.HttpHeaders +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.datetime.Instant +import org.jetbrains.compose.resources.LoadState + +@OptIn(ExperimentalPagingApi::class) +class ProfileModel( + private val api: MemosApi, + private val repository: MemoRepository, + private val memoQueries: QueryQueries, + + + ) : ScreenModel { + + + + + fun getPagerByUserId(uid:Long): Flow> { + return Pager( + config = PagingConfig(pageSize = 10), +// remoteMediator = MemoRemoteMediator(repository, memoQueries,query) + ) { + MemoPagingSource(repository,MemoQueryWhere(creatorId = uid)) + + }.flow.cachedIn(screenModelScope) + } + + + + suspend fun setPininned(it: Memo) { + api.updateMemoOrganizer(it.id, UpdateMemoOrganizerInput(!it.pinned)) + } + + suspend fun remove(it: Memo) { + api.deleteMemo(it.id) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/login/LoginScreenModel.kt b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/login/LoginScreenModel.kt new file mode 100644 index 0000000..1b9bc14 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/github/springeye/memosc/ui/login/LoginScreenModel.kt @@ -0,0 +1,50 @@ +package com.github.springeye.memosc.ui.login + + +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import com.github.springeye.memosc.AppPreferences +import com.github.springeye.memosc.MemosApi +import com.github.springeye.memosc.SignInInput +import io.ktor.client.HttpClient +import io.ktor.http.isSuccess +import kotlinx.coroutines.launch + +class LoginScreenModel(private val api: MemosApi, + private val client: HttpClient, + private val prefers: AppPreferences +): StateScreenModel( + State.Init +) { + sealed class State { + object Init : State() + object Loading : State() + data class Result(val isSuccess:Boolean) : State() + } + fun login(host:String,username:String,password:String){ + screenModelScope.launch { + mutableState.value = State.Loading + prefers.host(host) + val input = SignInInput(email = "", username = username, password = password) + + + + +// client.post("api/v1/auth/signin"){ +// contentType(ContentType.Application.Json) +// setBody(input) +// } + val res=api.signIn(input) + //todo handle login success + if(res.status.isSuccess()) { + prefers.host(host) + prefers.username(username) + prefers.password(password) + mutableState.value = State.Result(true) + }else{ + println(res.toString()) + mutableState.value = State.Result(false) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/model/CodeLang.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/model/CodeLang.kt new file mode 100644 index 0000000..5db27e0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/model/CodeLang.kt @@ -0,0 +1,61 @@ +package com.wakaztahir.codeeditor.model + +import com.wakaztahir.codeeditor.prettify.lang.* +import com.wakaztahir.codeeditor.prettify.parser.Prettify + +enum class CodeLang(val langProvider: Prettify.LangProvider?, val value: List) { + Default(null, listOf("default-code")), + HTML(null, listOf("default-markup")), + C(null, listOf("c")), + CPP(null, listOf("cpp")), + ObjectiveC(null, listOf("cxx")), + CSharp(null, listOf("cs")), + Java(null, listOf("java")), + Bash(null, listOf("bash")), + Python(null, listOf("python")), + Perl(null, listOf("perl")), + Ruby(null, listOf("ruby")), + JavaScript(null, listOf("javascript")), + CoffeeScript(null, listOf("coffee")), + Rust(null, listOf("rust")), + OCAML(null, listOf("ml")), + SML(null, listOf("ml")), + FSharp(null, listOf("fs")), + JSON(null,listOf("json")), + XML(null,listOf("xml")), + Proto(null,listOf("proto")), + RegEx(null,listOf("regex")), + Appollo({ LangAppollo() },LangAppollo.fileExtensions), + Basic({ LangBasic() },LangBasic.fileExtensions), + Clojure({ LangClj() },LangClj.fileExtensions), + CSS({ LangCss() },LangCss.fileExtensions), + Dart({ LangDart() },LangDart.fileExtensions), + Erlang({ LangErlang() },LangErlang.fileExtensions), + Go({ LangGo() },LangGo.fileExtensions), + Haskell({ LangHs() },LangHs.fileExtensions), + Lisp({ LangLisp() },LangLisp.fileExtensions), + Llvm({ LangLlvm() },LangLlvm.fileExtensions), + Lua({ LangLua() },LangLua.fileExtensions), + Matlab({ LangMatlab() },LangMatlab.fileExtensions), + ML({ LangMl() },LangMl.fileExtensions), + Mumps({ LangMumps() },LangMumps.fileExtensions), + N({ LangN() },LangN.fileExtensions), + Pascal({ LangPascal() },LangPascal.fileExtensions), + R({ LangR() },LangR.fileExtensions), + Rd({ LangRd() },LangRd.fileExtensions), + Scala({ LangScala() },LangScala.fileExtensions), + SQL({ LangSql() },LangSql.fileExtensions), + Tex({ LangTex() },LangTex.fileExtensions), + VB({ LangVb() },LangVb.fileExtensions), + VHDL({ LangVhdl() },LangVhdl.fileExtensions), + Tcl({ LangTcl() },LangTcl.fileExtensions), + Wiki({ LangWiki() },LangWiki.fileExtensions), + XQuery({ LangXq() },LangXq.fileExtensions), + YAML({ LangYaml() },LangYaml.fileExtensions), + Markdown({ LangMd() },LangMd.fileExtensions), + Ex({ LangEx() },LangEx.fileExtensions), + Kotlin({ LangKotlin() },LangKotlin.fileExtensions), + Lasso({ LangLasso() },LangLasso.fileExtensions), + Logtalk({ LangLogtalk() },LangLogtalk.fileExtensions), + Swift({ LangSwift() },LangSwift.fileExtensions), +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/parser/ParseResult.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/parser/ParseResult.kt new file mode 100644 index 0000000..6b1d959 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/parser/ParseResult.kt @@ -0,0 +1,143 @@ +// Copyright (c) 2012 Chan Wai Shing +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +package com.wakaztahir.codeeditor.parser + +/** + * The parser parsed result. + * + * This class include the information needed to highlight the syntax. + * Information includes where the content located in the document (offset and + * length) and what style(s) should be applied on that segment of content. + * + * @author Chan Wai Shing @gmail.com> + */ +class ParseResult( + /** + * The start position of the content. + */ + var offset: Int, + /** + * The length of the content. + */ + var length: Int, styleKeys: List? +) { + /** + * The start position of the content. + * @return the start position of the content + */ + /** + * The start position of the content. + * @param offset the start position of the content + */ + /** + * The length of the content. + * @return the length of the content + */ + /** + * The length of the content. + * @param length the length of the content + */ + + /** + * The style keys of the content. The style at higher index of the list will + * override the style of the lower index. + */ + private var styleKeys: MutableList + + /** + * Get the style keys represented by one string key, see + * [Theme.getStylesAttributeSet]. + * @return the style keys of the content + */ + val styleKeysString: String + get() { + val sb = StringBuilder(10) + var i = 0 + val iEnd = styleKeys.size + while (i < iEnd) { + if (i != 0) { + sb.append(" ") + } + sb.append(styleKeys[i]) + i++ + } + return sb.toString() + } + + /** + * The style keys of the content. + * @param styleKey the style key + * @return see the return value of [List.add] + */ + fun addStyleKey(styleKey: String): Boolean { + return styleKeys.add(styleKey) + } + + /** + * The style keys of the content. + * @param styleKey the style key + * @return see the return value of [List.remove] + */ + fun removeStyleKey(styleKey: String): Boolean { + return styleKeys.remove(styleKey) + } + + /** + * The style keys of the content. + */ + fun clearStyleKeys() { + styleKeys.clear() + } + + /** + * {@inheritDoc} + */ + override fun toString(): String { + val sb = StringBuilder() + sb.append("[") + sb.append(offset) + sb.append("; ") + sb.append(length) + sb.append("; ") + var i = 0 + val iEnd = styleKeys.size + while (i < iEnd) { + if (i != 0) { + sb.append(", ") + } + sb.append(styleKeys[i]) + i++ + } + sb.append("]") + return sb.toString() + } + + /** + * Constructor. + * + * @param offset the start position of the content + * @param length the length of the content + * @param styleKeys the style keys of the content + */ + init { + this.styleKeys = styleKeys?.toMutableList() ?: mutableListOf() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/parser/Parser.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/parser/Parser.kt new file mode 100644 index 0000000..0c9bf9f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/parser/Parser.kt @@ -0,0 +1,42 @@ +// Copyright (c) 2012 Chan Wai Shing +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +package com.wakaztahir.codeeditor.parser + +import com.wakaztahir.codeeditor.prettify.parser.Prettify + +/** + * The parser for syntax highlight. + * + * @author Chan Wai Shing @gmail.com> + */ +interface Parser { + /** + * Parse the `content` and return the parsed result. + * + * @param fileExtension the file extension of the content, null means not + * provided + * @param content the content + * @return the parsed result + */ + fun parse(fileExtension: String, content: String): List + fun isSupport(extension:String): Boolean + fun parse(provider: Prettify.LangProvider, content: String): List +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/PrettifyParser.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/PrettifyParser.kt new file mode 100644 index 0000000..38c3d0f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/PrettifyParser.kt @@ -0,0 +1,46 @@ +package com.wakaztahir.codeeditor.prettify + +import com.wakaztahir.codeeditor.parser.ParseResult +import com.wakaztahir.codeeditor.parser.Parser +import com.wakaztahir.codeeditor.prettify.parser.Job +import com.wakaztahir.codeeditor.prettify.parser.Prettify + + +/** + * The prettify parser for syntax highlight. + * @author Chan Wai Shing @gmail.com> + */ +class PrettifyParser : Parser { + + private val prettify: Prettify = Prettify() + + override fun parse(fileExtension: String, content: String): List = + parse(prettify.getLexerForExtension(fileExtension), content) + + override fun parse(provider: Prettify.LangProvider, content: String): List = + parse(prettify.getLexerForLanguageProvider(provider), content) + + override fun isSupport(extension: String): Boolean { + return prettify.isSupport(extension) + } + + private fun parse(lexer: Prettify.CreateSimpleLexer, content: String): List { + val job = Job(0, content) + lexer.decorate(job) + val decorations = job.decorations + val returnList = ArrayList() + + // apply style according to the style list + var i = 0 + val iEnd = decorations.size + while (i < iEnd) { + val endPos = if (i + 2 < iEnd) decorations[i + 2] as Int else content.length + val startPos = decorations[i] as Int + returnList.add( + ParseResult(startPos, endPos - startPos, listOf((decorations[i + 1] as String))) + ) + i += 2 + } + return returnList + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/Lang.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/Lang.kt new file mode 100644 index 0000000..b428892 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/Lang.kt @@ -0,0 +1,36 @@ +// Copyright (C) 2011 Chan Wai Shing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.StylePattern + +/** + * Lang class for Java Prettify. + * Note that the method [.getFileExtensions] should be overridden. + * + * @author Chan Wai Shing @gmail.com> + */ +abstract class Lang { + /** + * Similar to those in JavaScript prettify.js. + */ + abstract val shortcutStylePatterns: List + + /** + * Similar to those in JavaScript prettify.js. + */ + abstract val fallthroughStylePatterns: List + + abstract fun getFileExtensions(): List +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangAppollo.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangAppollo.kt new file mode 100644 index 0000000..cf1cdea --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangAppollo.kt @@ -0,0 +1,95 @@ +// Copyright (C) 2009 Onno Hommes. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-appollo.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for the AGC/AEA Assembly Language as described + * at http://virtualagc.googlecode.com + * + * + * This file could be used by goodle code to allow syntax highlight for + * Virtual AGC SVN repository or if you don't want to commonize + * the header for the agc/aea html assembly listing. + * + * @author ohommes@alumni.cmu.edu + */ +class LangAppollo : Lang() { + + companion object { + val fileExtensions = listOf("apollo", "agc", "aea") + } + + override fun getFileExtensions(): List = fileExtensions + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + // A line comment that starts with ; + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^#[^\r\n]*"), + null + ) + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\t\n\r \\xA0]+"), null + ) + // A double quoted, possibly multi-line, string. + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:ADS|AD|AUG|BZF|BZMF|CAE|CAF|CA|CCS|COM|CS|DAS|DCA|DCOM|DCS|DDOUBL|DIM|DOUBLE|DTCB|DTCF|DV|DXCH|EDRUPT|EXTEND|INCR|INDEX|NDX|INHINT|LXCH|MASK|MSK|MP|MSU|NOOP|OVSK|QXCH|RAND|READ|RELINT|RESUME|RETURN|ROR|RXOR|SQUARE|SU|TCR|TCAA|OVSK|TCF|TC|TS|WAND|WOR|WRITE|XCH|XLQ|XXALQ|ZL|ZQ|ADD|ADZ|SUB|SUZ|MPY|MPR|MPZ|DVP|COM|ABS|CLA|CLZ|LDQ|STO|STQ|ALS|LLS|LRS|TRA|TSQ|TMI|TOV|AXT|TIX|DLY|INP|OUT)\\s"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex("^(?:-?GENADR|=MINUS|2BCADR|VN|BOF|MM|-?2CADR|-?[1-6]DNADR|ADRES|BBCON|[SE]?BANK\\=?|BLOCK|BNKSUM|E?CADR|COUNT\\*?|2?DEC\\*?|-?DNCHAN|-?DNPTR|EQUALS|ERASE|MEMORY|2?OCT|REMADR|SETLOC|SUBRO|ORG|BSS|BES|SYN|EQU|DEFINE|END)\\s"), + null + ) + // A single quote possibly followed by a word that optionally ends with + // = ! or ?. + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^\\'(?:-*(?:\\w|\\\\[\\x21-\\x7e])(?:[\\w-]*|\\\\[\\x21-\\x7e])[=!?]?)?") + ) + // Any word including labels that optionally ends with = ! or ?. + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex( + "^-*(?:[!-z_]|\\\\[\\x21-\\x7e])(?:[\\w-]*|\\\\[\\x21-\\x7e])[=!?]?", + RegexOption.IGNORE_CASE + ) + ) + // A printable non-space non-special character + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[^\\w\\t\\n\\r \\xA0()\\\"\\\\\\';]+") + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangBasic.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangBasic.kt new file mode 100644 index 0000000..1e210ec --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangBasic.kt @@ -0,0 +1,64 @@ +// Contributed by peter dot kofler at code minus cop dot org +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-basic.js in JavaScript Prettify. + * + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *
(my BASIC code)
+ * + * @author peter dot kofler at code minus cop dot org + */ +class LangBasic : Lang() { + + companion object { + val fileExtensions = listOf("basic", "cbm") + } + + override fun getFileExtensions(): List = fileExtensions + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + // "single-line-string" + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^(?:\"(?:[^\\\\\"\\r\\n]|\\\\.)*(?:\"|$))"), + null + ) + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^\\s+"), null + ) + + // A line comment that starts with REM + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, Regex("^REM[^\\r\\n]*"), null + ) + + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^\\b(?:AND|CLOSE|CLR|CMD|CONT|DATA|DEF ?FN|DIM|END|FOR|GET|GOSUB|GOTO|IF|INPUT|LET|LIST|LOAD|NEW|NEXT|NOT|ON|OPEN|OR|POKE|PRINT|READ|RESTORE|RETURN|RUN|SAVE|STEP|STOP|SYS|THEN|TO|VERIFY|WAIT)\\b"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[A-Z][A-Z0-9]?(?:\\$|%)?", RegexOption.IGNORE_CASE), null + ) + // Literals .0, 0, 0.0 0E13 + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+\\-]?\\d+)?", RegexOption.IGNORE_CASE), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, Regex("^.[^\\s\\w\\.$%\"]*"), null + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangClj.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangClj.kt new file mode 100644 index 0000000..3bc3671 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangClj.kt @@ -0,0 +1,100 @@ +/** + * @license Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-clj.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for Clojure. + * + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *
(my lisp code)
+ * The lang-cl class identifies the language as common lisp. + * This file supports the following language extensions: + * lang-clj - Clojure + * + * + * I used lang-lisp.js as the basis for this adding the clojure specific + * keywords and syntax. + * + * "Name" = 'Clojure' + * "Author" = 'Rich Hickey' + * "Version" = '1.2' + * "About" = 'Clojure is a lisp for the jvm with concurrency primitives and a richer set of types.' + * + * + * I used [Clojure.org Reference](http://clojure.org/Reference) as + * the basis for the reserved word list. + * + * + * @author jwall@google.com + */ +class LangClj : Lang() { + companion object { + val fileExtensions: List + get() = listOf("clj") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + // clojure has more paren types than minimal lisp. + shortcutStylePatterns.new("opn", Regex("^[\\(\\{\\[]+"), null) + shortcutStylePatterns.new("clo", Regex("^[\\)\\}\\]]+"), null) + // A line comment that starts with ; + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^;[^\r\n]*"), + null + ) + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\t\n\r \\xA0]+"), null + ) + // A double quoted, possibly multi-line, string. + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)"), + null + ) + // clojure has a much larger set of keywords + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\\b"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex("^:[0-9a-zA-Z\\-]+") + ) + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangCss.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangCss.kt new file mode 100644 index 0000000..2ed5770 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangCss.kt @@ -0,0 +1,171 @@ +// Copyright (C) 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-css.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for CSS. + * + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *

+ *
+ *
+ * http://www.w3.org/TR/CSS21/grammar.html Section G2 defines the lexical
+ * grammar.  This scheme does not recognize keywords containing escapes.
+ *
+ * @author mikesamuel@gmail.com
+ */
+class LangCss : Lang() {
+    companion object {
+        val fileExtensions: List
+            get() = listOf("css")
+    }
+
+    override val fallthroughStylePatterns = ArrayList()
+    override val shortcutStylePatterns = ArrayList()
+
+    init {
+
+        // The space production 
+        shortcutStylePatterns.new(
+            Prettify.PR_PLAIN,
+            Regex("^[ \t\r\n\\f]+"),
+            null
+        )
+        // Quoted strings.   and 
+        fallthroughStylePatterns.new(
+            Prettify.PR_STRING,
+            Regex("^\\\"(?:[^\n\r\\f\\\\\\\"]|\\\\(?:\r\n?|\n|\\f)|\\\\[\\s\\S])*\\\""),
+            null
+        )
+        fallthroughStylePatterns.new(
+            Prettify.PR_STRING,
+            Regex("^\\'(?:[^\n\r\\f\\\\\\']|\\\\(?:\r\n?|\n|\\f)|\\\\[\\s\\S])*\\'"),
+            null
+        )
+        fallthroughStylePatterns.new(
+            "lang-css-str",
+            Regex("^url\\(([^\\)\\\"\\']+)\\)", RegexOption.IGNORE_CASE)
+        )
+        fallthroughStylePatterns.new(
+            Prettify.PR_KEYWORD,
+            Regex(
+                "^(?:url|rgb|\\!important|@import|@page|@media|@charset|inherit)(?=[^\\-\\w]|$)",
+                RegexOption.IGNORE_CASE
+            ),
+            null
+        )
+        // A property name -- an identifier followed by a colon.
+        fallthroughStylePatterns.new(
+            "lang-css-kw",
+            Regex(
+                "^(-?(?:[_a-z]|(?:\\\\[0-9a-f]+ ?))(?:[_a-z0-9\\-]|\\\\(?:\\\\[0-9a-f]+ ?))*)\\s*:",
+                RegexOption.IGNORE_CASE
+            )
+        )
+        // A C style block comment.  The  production.
+        fallthroughStylePatterns.new(
+            Prettify.PR_COMMENT,
+            Regex("^\\/\\*[^*]*\\*+(?:[^\\/*][^*]*\\*+)*\\/")
+        )
+        // Escaping text spans
+        fallthroughStylePatterns.new(
+            Prettify.PR_COMMENT,
+            Regex("^(?:)")
+        )
+        // A number possibly containing a suffix.
+        fallthroughStylePatterns.new(
+            Prettify.PR_LITERAL,
+            Regex("^(?:\\d+|\\d*\\.\\d+)(?:%|[a-z]+)?", RegexOption.IGNORE_CASE)
+        )
+        // A hex color
+        fallthroughStylePatterns.new(
+            Prettify.PR_LITERAL,
+            Regex("^#(?:[0-9a-f]{3}){1,2}\\b", RegexOption.IGNORE_CASE)
+        )
+        // An identifier
+        fallthroughStylePatterns.new(
+            Prettify.PR_PLAIN,
+            Regex(
+                "^-?(?:[_a-z]|(?:\\\\[\\da-f]+ ?))(?:[_a-z\\d\\-]|\\\\(?:\\\\[\\da-f]+ ?))*",
+                RegexOption.IGNORE_CASE
+            )
+        )
+        // A run of punctuation
+        fallthroughStylePatterns.new(
+            Prettify.PR_PUNCTUATION,
+            Regex("^[^\\s\\w\\'\\\"]+", RegexOption.IGNORE_CASE)
+        )
+    }
+
+    override fun getFileExtensions(): List {
+        return fileExtensions
+    }
+
+    class LangCssKeyword : Lang() {
+        companion object {
+            val fileExtensions: List
+                get() = listOf("css-kw")
+        }
+
+        override val fallthroughStylePatterns = ArrayList()
+        override val shortcutStylePatterns = ArrayList()
+
+        init {
+            fallthroughStylePatterns.new(
+                Prettify.PR_KEYWORD,
+                Regex(
+                    "^-?(?:[_a-z]|(?:\\\\[\\da-f]+ ?))(?:[_a-z\\d\\-]|\\\\(?:\\\\[\\da-f]+ ?))*",
+                    RegexOption.IGNORE_CASE
+                )
+            )
+        }
+
+        override fun getFileExtensions(): List {
+            return fileExtensions
+        }
+    }
+
+    class LangCssString : Lang() {
+        companion object {
+            val fileExtensions: List = listOf("css-str")
+        }
+
+        override val fallthroughStylePatterns = ArrayList()
+        override val shortcutStylePatterns = ArrayList()
+
+        init {
+            fallthroughStylePatterns.new(
+                Prettify.PR_STRING,
+                Regex("^[^\\)\\\"\\']+")
+            )
+        }
+
+        override fun getFileExtensions(): List {
+            return fileExtensions
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangDart.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangDart.kt
new file mode 100644
index 0000000..2560a65
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangDart.kt
@@ -0,0 +1,164 @@
+/**
+ * @license Copyright (C) 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.wakaztahir.codeeditor.prettify.lang
+
+import com.wakaztahir.codeeditor.prettify.parser.Prettify
+import com.wakaztahir.codeeditor.prettify.parser.StylePattern
+import com.wakaztahir.codeeditor.utils.new
+
+/**
+ * This is similar to the lang-dart.js in JavaScript Prettify.
+ *
+ * All comments are adapted from the JavaScript Prettify.
+ *
+ *
+ *
+ * Registers a language handler for Dart.
+ *
+ *
+ * Loosely structured based on the DartLexer in Pygments: http://pygments.org/.
+ *
+ * To use, include prettify.js and this file in your HTML page.
+ * Then put your code in an HTML tag like
+ * 
(Dart code)
+ * + * @author armstrong.timothy@gmail.com + */ +class LangDart : Lang() { + companion object { + val fileExtensions: List + get() = listOf(("dart")) + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + // Whitespace. + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\t\n\r \\xA0]+"), null + ) + + // Script tag. + fallthroughStylePatterns.new(Prettify.PR_COMMENT, Regex("^#!(?:.*)")) + // `import`, `library`, `part of`, `part`, `as`, `show`, and `hide` + // keywords. + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^\\b(?:import|library|part of|part|as|show|hide)\\b", RegexOption.IGNORE_CASE) + ) + // Single-line comments. + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^\\/\\/(?:.*)") + ) + // Multiline comments. + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^\\/\\*[^*]*\\*+(?:[^\\/*][^*]*\\*+)*\\/") + ) + // `class` and `interface` keywords. + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^\\b(?:class|interface)\\b", RegexOption.IGNORE_CASE) + ) + // General keywords. + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex( + "^\\b(?:assert|break|case|catch|continue|default|do|else|finally|for|if|in|is|new|return|super|switch|this|throw|try|while)\\b", + RegexOption.IGNORE_CASE + ) + ) + // Declaration keywords. + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex( + "^\\b(?:abstract|const|extends|factory|final|get|implements|native|operator|set|static|typedef|var)\\b", + RegexOption.IGNORE_CASE + ) + ) + // Keywords for types. + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex( + "^\\b(?:bool|double|Dynamic|int|num|Object|String|void)\\b", + RegexOption.IGNORE_CASE + ) + ) + // Keywords for constants. + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("\\b(?:false|null|true)\\b", RegexOption.IGNORE_CASE) + ) + // Multiline strings, single- and double-quoted. + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^r?[\\']{3}[\\s|\\S]*?[^\\\\][\\']{3}") + ) + + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^r?[\\\"]{3}[\\s|\\S]*?[^\\\\][\\\"]{3}") + ) + + // Normal and raw strings, single- and double-quoted. + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^r?\\'(\\'|(?:[^\\n\\r\\f])*?[^\\\\]\\')") + ) + + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^r?\\\"(\\\"|(?:[^\\n\\r\\f])*?[^\\\\]\\\")") + ) + // Identifiers. + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[a-z_$][a-z0-9_]*", RegexOption.IGNORE_CASE) + ) + // Operators. + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[~!%^&*+=|?:<>/-]") + ) + // Hex numbers. + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^\\b0x[0-9a-f]+", RegexOption.IGNORE_CASE) + ) + // Decimal numbers. + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^\\b\\d+(?:\\.\\d*)?(?:e[+-]?\\d+)?", RegexOption.IGNORE_CASE) + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^\\b\\.\\d+(?:e[+-]?\\d+)?", RegexOption.IGNORE_CASE) + ) + // Punctuation. + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[(){}\\[\\],.;]") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangErlang.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangErlang.kt new file mode 100644 index 0000000..b4804a9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangErlang.kt @@ -0,0 +1,137 @@ +// Copyright (C) 2013 Andrew Allen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-erlang.js in JavaScript Prettify. + * + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * + * + * + * Derived from https://raw.github.com/erlang/otp/dev/lib/compiler/src/core_parse.yrl + * Modified from Mike Samuel's Haskell plugin for google-code-prettify + * + * @author achew22@gmail.com + */ +class LangErlang : Lang() { + companion object { + val fileExtensions: List + get() = listOf("erlang", "erl") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + + // Whitespace + // whitechar -> newline | vertab | space | tab | uniWhite + // newline -> return linefeed | return | linefeed | formfeed + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("\\t\\n\\x0B\\x0C\\r ]+"), null + ) + // Single line double-quoted strings. + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\\"(?:[^\\\"\\\\\\n\\x0C\\r]|\\\\[\\s\\S])*(?:\\\"|$)"), + null + ) + + // Handle atoms + shortcutStylePatterns.new( + Prettify.PR_LITERAL, Regex("^[a-z][a-zA-Z0-9_]*") + ) + // Handle single quoted atoms + shortcutStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^\\'(?:[^\\'\\\\\\n\\x0C\\r]|\\\\[^&])+\\'?"), + null + ) + + // Handle macros. Just to be extra clear on this one, it detects the ? + // then uses the regexp to end it so be very careful about matching + // all the terminal elements + shortcutStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^\\?[^ \\t\\n({]+"), + null + ) + + // decimal -> digit{digit} + // octal -> octit{octit} + // hexadecimal -> hexit{hexit} + // integer -> decimal + // | 0o octal | 0O octal + // | 0x hexadecimal | 0X hexadecimal + // float -> decimal . decimal [exponent] + // | decimal exponent + // exponent -> (e | E) [+ | -] decimal + shortcutStylePatterns.new( + Prettify.PR_LITERAL, + Regex( + "^(?:0o[0-7]+|0x[\\da-f]+|\\d+(?:\\.\\d+)?(?:e[+\\-]?\\d+)?)", + RegexOption.IGNORE_CASE + ), + null + ) + + + // TODO: catch @declarations inside comments + + // Comments in erlang are started with % and go till a newline + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, Regex("^%[^\\n\\r]*") + ) + + // Catch macros + //[PR['PR_TAG'], /?[^( \n)]+/], + /** + * %% Keywords (atoms are assumed to always be single-quoted). + * 'module' 'attributes' 'do' 'let' 'in' 'letrec' + * 'apply' 'call' 'primop' + * 'case' 'of' 'end' 'when' 'fun' 'try' 'catch' 'receive' 'after' + */ + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:module|attributes|do|let|in|letrec|apply|call|primop|case|of|end|when|fun|try|catch|receive|after|char|integer|float,atom,string,var)\\b") + ) + /** + * Catch definitions (usually defined at the top of the file) + * Anything that starts -something + */ + fallthroughStylePatterns.new(Prettify.PR_KEYWORD, Regex("^-[a-z_]+")) + + // Catch variables + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex("^[A-Z_][a-zA-Z0-9_]*") + ) + + // matches the symbol production + fallthroughStylePatterns.new(Prettify.PR_PUNCTUATION, Regex("^[.,;]")) + } + + override fun getFileExtensions(): List = fileExtensions + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangEx.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangEx.kt new file mode 100644 index 0000000..a7413be --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangEx.kt @@ -0,0 +1,122 @@ +package com.wakaztahir.codeeditor.prettify.lang + + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + + +class LangEx : Lang() { + + companion object { + val fileExtensions = listOf("ex", "exs") + } + + override fun getFileExtensions(): List = fileExtensions + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + shortcutStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[\\t\\n\\r \\xA0]+"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^#.*"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^'(?:[^'\\\\]|\\\\(?:.|\\n|\\r))*'?"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_ATTRIB_NAME, + Regex("^@\\w+"), + null + ) + shortcutStylePatterns.new( + + Prettify.PR_PUNCTUATION, + Regex("^[!%&()*+,\\-;<=>?\\[\\\\\\]^{|}]+"), + null + + ) + shortcutStylePatterns.new( + + Prettify.PR_LITERAL, + Regex("^(?:0o[0-7](?:[0-7]|_[0-7])*|0x[\\da-fA-F](?:[\\da-fA-F]|_[\\da-fA-F])*|\\d(?:\\d|_\\d)*(?:\\.\\d(?:\\d|_\\d)*)?(?:[eE][+\\-]?\\d(?:\\d|_\\d)*)?)"), + null + + ) + fallthroughStylePatterns.new( + Prettify.PR_ATTRIB_NAME, + Regex("^iex(?:\\(\\d+\\))?> ") + ) + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^::"), + null + + ) + fallthroughStylePatterns.new( + + Prettify.PR_LITERAL, + Regex("^:(?:\\w+[\\!\\?\\@]?|\"(?:[^\"\\\\]|\\\\.)*\"?)") + ) + fallthroughStylePatterns.new( + + Prettify.PR_ATTRIB_NAME, + Regex("^(?:__(?:CALLER|ENV|MODULE|DIR)__)") + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:alias|case|catch|def(?:delegate|exception|impl|macrop?|module|overridable|p?|protocol|struct)|do|else|end|fn|for|if|in|import|quote|raise|require|rescue|super|throw|try|unless|unquote(?:_splicing)?|use|when|with|yield)\\b") + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^(?:true|false|nil)\\b") + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^(?:\\w+[\\!\\?\\@]?|\"(?:[^\"\\\\]|\\\\.)*\"):(?!:)") + ) + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^\"\"\"\\s*(\\r|\\n)+(?:\"\"?(?!\")|[^\\\\\"]|\\\\(?:.|\\n|\\r))*\"{0,3}") + ) + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^\"(?:[^\"\\\\]|\\\\(?:.|\\n|\\r))*\"?(?!\")") + ) + fallthroughStylePatterns.new(Prettify.PR_TYPE, Regex("^[A-Z]\\w*")) + fallthroughStylePatterns.new(Prettify.PR_COMMENT, Regex("^_\\w*")) + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[\$a-z]\\w*[\\!\\?]?") + ) + fallthroughStylePatterns.new( + Prettify.PR_ATTRIB_VALUE, + Regex( + "^~[A-Z](?:\\/(?:[^\\/\\r\\n\\\\]|\\\\.)+\\/|\\|(?:[^\\|\\r\\n\\\\]|\\\\.)+\\||\"(?:[^\"\\r\\n\\\\]|\\\\.)+\"|'(?:[^'\\r\\n\\\\]|\\\\.)+')[A-Z]*", + RegexOption.IGNORE_CASE + ) + ) + fallthroughStylePatterns.new( + Prettify.PR_ATTRIB_VALUE, + Regex( + "^~[A-Z](?:\\((?:[^\\)\\r\\n\\\\]|\\\\.)+\\)|\\[(?:[^\\]\\r\\n\\\\]|\\\\.)+\\]|\\{(?:[^\\}\\r\\n\\\\]|\\\\.)+\\}|\\<(?:[^\\>\\r\\n\\\\]|\\\\.)+\\>)[A-Z]*", + RegexOption.IGNORE_CASE + ) + ) + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^(?:\\.+|\\/|[:~])") + ) + + + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangGo.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangGo.kt new file mode 100644 index 0000000..ae7dcbf --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangGo.kt @@ -0,0 +1,89 @@ +// Copyright (C) 2010 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-go.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for the Go language.. + * + * + * Based on the lexical grammar at + * http://golang.org/doc/go_spec.html#Lexical_elements + * + * + * Go uses a minimal style for highlighting so the below does not distinguish + * strings, keywords, literals, etc. by design. + * From a discussion with the Go designers: + *
+ * On Thursday, July 22, 2010, Mike Samuel <...> wrote:
+ * > On Thu, Jul 22, 2010, Rob 'Commander' Pike <...> wrote:
+ * >> Personally, I would vote for the subdued style godoc presents at http://golang.org
+ * >>
+ * >> Not as fancy as some like, but a case can be made it's the official style.
+ * >> If people want more colors, I wouldn't fight too hard, in the interest of
+ * >> encouragement through familiarity, but even then I would ask to shy away
+ * >> from technicolor starbursts.
+ * >
+ * > Like http://golang.org/pkg/go/scanner/ where comments are blue and all
+ * > other content is black?  I can do that.
+
* + * + * @author mikesamuel@gmail.com + */ +class LangGo : Lang() { + companion object { + val fileExtensions: List + get() = listOf("go") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + + + // Whitespace is made up of spaces, tabs and newline characters. + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\\t\\n\\r \\xA0]+"), null + ) + // Not escaped as a string. See note on minimalism above. + shortcutStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^(?:\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)|\\'(?:[^\\'\\\\]|\\\\[\\s\\S])+(?:\\'|$)|`[^`]*(?:`|$))"), + null + ) + // Block comments are delimited by /* and */. + // Single-line comments begin with // and extend to the end of a line. + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, Regex("^(?:\\/\\/[^\\r\\n]*|\\/\\*[\\s\\S]*?\\*\\/)") + ) + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, Regex("^(?:[^\\/\\\"\\'`]|\\/(?![\\/\\*]))+", RegexOption.IGNORE_CASE) + ) + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangHs.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangHs.kt new file mode 100644 index 0000000..f317d81 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangHs.kt @@ -0,0 +1,146 @@ +// Copyright (C) 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-hs.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for Haskell. + * + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *
(my lisp code)
+ * The lang-cl class identifies the language as common lisp. + * This file supports the following language extensions: + * lang-cl - Common Lisp + * lang-el - Emacs Lisp + * lang-lisp - Lisp + * lang-scm - Scheme + * + * + * I used http://www.informatik.uni-freiburg.de/~thiemann/haskell/haskell98-report-html/syntax-iso.html + * as the basis, but ignore the way the ncomment production nests since this + * makes the lexical grammar irregular. It might be possible to support + * ncomments using the lookbehind filter. + * + * + * @author mikesamuel@gmail.com + */ +class LangHs : Lang() { + companion object { + val fileExtensions: List + get() = listOf(("hs")) + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + // Whitespace + // whitechar -> newline | vertab | space | tab | uniWhite + // newline -> return linefeed | return | linefeed | formfeed + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\\t\\n\\x0B\\x0C\\r ]+"), null + ) + // Single line double and single-quoted strings. + // char -> ' (graphic<' | \> | space | escape<\&>) ' + // string -> " {graphic<" | \> | space | escape | gap}" + // escape -> \ ( charesc | ascii | decimal | o octal + // | x hexadecimal ) + // charesc -> a | b | f | n | r | t | v | \ | " | ' | & + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\\"(?:[^\\\"\\\\\\n\\x0C\\r]|\\\\[\\s\\S])*(?:\\\"|$)"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\'(?:[^\\'\\\\\\n\\x0C\\r]|\\\\[^&])\\'?"), + null + ) + // decimal -> digit{digit} + // octal -> octit{octit} + // hexadecimal -> hexit{hexit} + // integer -> decimal + // | 0o octal | 0O octal + // | 0x hexadecimal | 0X hexadecimal + // float -> decimal . decimal [exponent] + // | decimal exponent + // exponent -> (e | E) [+ | -] decimal + shortcutStylePatterns.new( + Prettify.PR_LITERAL, + Regex( + "^(?:0o[0-7]+|0x[\\da-f]+|\\d+(?:\\.\\d+)?(?:e[+\\-]?\\d+)?)", + RegexOption.IGNORE_CASE + ), + null + ) + // Haskell does not have a regular lexical grammar due to the nested + // ncomment. + // comment -> dashes [ any {any}] newline + // ncomment -> opencom ANYseq {ncomment ANYseq}closecom + // dashes -> '--' {'-'} + // opencom -> '{-' + // closecom -> '-}' + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^(?:(?:--+(?:[^\\r\\n\\x0C]*)?)|(?:\\{-(?:[^-]|-+[^-\\}])*-\\}))") + ) + // reservedid -> case | class | data | default | deriving | do + // | else | if | import | in | infix | infixl | infixr + // | instance | let | module | newtype | of | then + // | type | where | _ + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^a-zA-Z0-9\\']|$)"), + null + ) + // qvarid -> [ modid . ] varid + // qconid -> [ modid . ] conid + // varid -> (small {small | large | digit | ' }) + // conid -> large {small | large | digit | ' } + // modid -> conid + // small -> ascSmall | uniSmall | _ + // ascSmall -> a | b | ... | z + // uniSmall -> any Unicode lowercase letter + // large -> ascLarge | uniLarge + // ascLarge -> A | B | ... | Z + // uniLarge -> any uppercase or titlecase Unicode letter + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^(?:[A-Z][\\w\\']*\\.)*[a-zA-Z][\\w\\']*") + ) + // matches the symbol production + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[^\\t\\n\\x0B\\x0C\\r a-zA-Z0-9\\'\\\"]+") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangKotlin.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangKotlin.kt new file mode 100644 index 0000000..3dc1554 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangKotlin.kt @@ -0,0 +1,72 @@ +package com.wakaztahir.codeeditor.prettify.lang + + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +class LangKotlin : Lang() { + + companion object { + val fileExtensions = listOf("kt", "kotlin") + } + + override fun getFileExtensions(): List = fileExtensions + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + shortcutStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[\\t\\n\\r \\xA0]+"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[.!%&()*+,\\-;<=>?\\[\\\\\\]^{|}:]+"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^\\b(package|public|protected|external|internal|private|open|abstract|constructor|final|override|import|for|while|as|typealias|get|set|((data|enum|annotation|sealed) )?class|this|super|val|var|fun|is|in|throw|return|break|continue|(companion )?object|if|try|else|do|when|init|interface|typeof)\\b") + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^(?:true|false|null)\\b") + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^(0[xX][0-9a-fA-F_]+L?|0[bB][0-1]+L?|[0-9_.]+([eE]-?[0-9]+)?[fFL]?)") + ) + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex("^(\\b[A-Z]+[a-z][a-zA-Z0-9_$@]*|`.*`)"), + null + ) + fallthroughStylePatterns.new(Prettify.PR_COMMENT, Regex("^\\/\\/.*")) + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^\\/\\*[\\s\\S]*?(?:\\*\\/|$)") + ) + fallthroughStylePatterns.new(Prettify.PR_STRING, Regex("'.'")) + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^\"([^\"\\\\]|\\\\[\\s\\S])*\"") + ) + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^\"{3}[\\s\\S]*?[^\\\\]\"{3}") + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^@([a-zA-Z0-9_$@]*|`.*`)") + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^[a-zA-Z0-9_]+@") + ) + + + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLasso.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLasso.kt new file mode 100644 index 0000000..5c46d85 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLasso.kt @@ -0,0 +1,107 @@ +package com.wakaztahir.codeeditor.prettify.lang + + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +class LangLasso : Lang() { + + companion object { + val fileExtensions = listOf("lasso", "ls", "lassoscript") + } + + override fun getFileExtensions(): List = fileExtensions + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + shortcutStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[\\t\\n\\r \\xA0]+"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\'[^\\'\\\\]*(?:\\\\[\\s\\S][^\\'\\\\]*)*(?:\\'|$)"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\\"[^\\\"\\\\]*(?:\\\\[\\s\\S][^\\\"\\\\]*)*(?:\\\"|$)"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\`[^\\`]*(?:\\`|$)"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^0x[\\da-f]+|\\d+", RegexOption.IGNORE_CASE), + null + ) + shortcutStylePatterns.new( + Prettify.PR_ATTRIB_NAME, + Regex("^[#$][a-z_][\\w.]*|#\\d+\\b|#![ \\S]+lasso9\\b", RegexOption.IGNORE_CASE), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_TAG, + Regex( + "^[\\[\\]]|<\\?(?:lasso(?:script)?|=)|\\?>|(no_square_brackets|noprocess)\\b", + RegexOption.IGNORE_CASE + ) + ) + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^\\/\\/[^\\r\\n]*|\\/\\*[\\s\\S]*?\\*\\/") + ) + fallthroughStylePatterns.new( + Prettify.PR_ATTRIB_NAME, + Regex( + "^-(?!infinity)[a-z_][\\w.]*|\\.\\s*'[a-z_][\\w.]*'|\\.{3}", + RegexOption.IGNORE_CASE + ) + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^\\d*\\.\\d+(?:e[-+]?\\d+)?|(infinity|NaN)\\b", RegexOption.IGNORE_CASE) + ) + fallthroughStylePatterns.new( + Prettify.PR_ATTRIB_VALUE, + Regex("^::\\s*[a-z_][\\w.]*", RegexOption.IGNORE_CASE) + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex( + "^(?:true|false|none|minimal|full|all|void|and|or|not|bw|nbw|ew|new|cn|ncn|lt|lte|gt|gte|eq|neq|rx|nrx|ft)\\b", + RegexOption.IGNORE_CASE + ) + ) + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex( + "^(?:array|date|decimal|duration|integer|map|pair|string|tag|xml|null|boolean|bytes|keyword|list|locale|queue|set|stack|staticarray|local|var|variable|global|data|self|inherited|currentcapture|givenblock)\\b|^\\.\\.?", + RegexOption.IGNORE_CASE + ) + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, Regex( + "^(?:cache|database_names|database_schemanames|database_tablenames|define_tag|define_type|email_batch|encode_set|html_comment|handle|handle_error|header|if|inline|iterate|ljax_target|link|link_currentaction|link_currentgroup|link_currentrecord|link_detail|link_firstgroup|link_firstrecord|link_lastgroup|link_lastrecord|link_nextgroup|link_nextrecord|link_prevgroup|link_prevrecord|log|loop|namespace_using|output_none|portal|private|protect|records|referer|referrer|repeating|resultset|rows|search_args|search_arguments|select|sort_args|sort_arguments|thread_atomic|value_list|while|abort|case|else|fail_if|fail_ifnot|fail|if_empty|if_false|if_null|if_true|loop_abort|loop_continue|loop_count|params|params_up|return|return_value|run_children|soap_definetag|soap_lastrequest|soap_lastresponse|tag_name|ascending|average|by|define|descending|do|equals|frozen|group|handle_failure|import|in|into|join|let|match|max|min|on|order|parent|protected|provide|public|require|returnhome|skip|split_thread|sum|take|thread|to|trait|type|where|with|yield|yieldhome)\\b", + RegexOption.IGNORE_CASE + ) + ) + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[a-z_][\\w.]*(?:=\\s*(?=\\())?", RegexOption.IGNORE_CASE) + ) + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^:=|[-+*\\/%=<>&|!?\\\\]+") + ) + + + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLisp.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLisp.kt new file mode 100644 index 0000000..edc3868 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLisp.kt @@ -0,0 +1,143 @@ +// Copyright (C) 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-lisp.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for Common Lisp and related languages. + * + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *
(my lisp code)
+ * The lang-cl class identifies the language as common lisp. + * This file supports the following language extensions: + * lang-cl - Common Lisp + * lang-el - Emacs Lisp + * lang-lisp - Lisp + * lang-scm - Scheme + * lang-lsp - FAT 8.3 filename version of lang-lisp. + * + * + * I used http://www.devincook.com/goldparser/doc/meta-language/grammar-LISP.htm + * as the basis, but added line comments that start with ; and changed the atom + * production to disallow unquoted semicolons. + * + * "Name" = 'LISP' + * "Author" = 'John McCarthy' + * "Version" = 'Minimal' + * "About" = 'LISP is an abstract language that organizes ALL' + * | 'data around "lists".' + * + * "Start Symbol" = [s-Expression] + * + * {Atom Char} = {Printable} - {Whitespace} - [()"\''] + * + * Atom = ( {Atom Char} | '\'{Printable} )+ + * + * [s-Expression] ::= [Quote] Atom + * | [Quote] '(' [Series] ')' + * | [Quote] '(' [s-Expression] '.' [s-Expression] ')' + * + * [Series] ::= [s-Expression] [Series] + * | + * + * [Quote] ::= '' !Quote = do not evaluate + * | + * + * + * I used [Practical Common Lisp](http://gigamonkeys.com/book/) as + * the basis for the reserved word list. + * + * + * @author mikesamuel@gmail.com + */ +class LangLisp : Lang() { + companion object { + val fileExtensions: List + get() = listOf("cl", "el", "lisp", "lsp", "scm", "ss", "rkt") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + + shortcutStylePatterns.new("opn", Regex("^\\(+"), null) + shortcutStylePatterns.new("clo", Regex("^\\)+"), null) + // A line comment that starts with ; + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^;[^\r\n]*"), + null + ) + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\t\n\r \\xA0]+"), null + ) + // A double quoted, possibly multi-line, string. + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex( + "^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\\b", + RegexOption.IGNORE_CASE + ), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex( + "^[+\\-]?(?:[0#]x[0-9a-f]+|\\d+\\/\\d+|(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:[ed][+\\-]?\\d+)?)", + RegexOption.IGNORE_CASE + ) + ) + // A single quote possibly followed by a word that optionally ends with + // = ! or ?. + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^\\'(?:-*(?:\\w|\\\\[\\x21-\\x7e])(?:[\\w-]*|\\\\[\\x21-\\x7e])[=!?]?)?") + ) + // A word that optionally ends with = ! or ?. + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, Regex( + "^-*(?:[a-z_]|\\\\[\\x21-\\x7e])(?:[\\w-]*|\\\\[\\x21-\\x7e])[=!?]?", + RegexOption.IGNORE_CASE + ) + ) + // A printable non-space non-special character + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, Regex("^[^\\w\\t\\n\\r \\xA0()\\\"\\\\\\';]+") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLlvm.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLlvm.kt new file mode 100644 index 0000000..8f09f26 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLlvm.kt @@ -0,0 +1,100 @@ +// Copyright (C) 2013 Nikhil Dabas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-ml.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * Registers a language handler for LLVM. From + * https://gist.github.com/ndabas/2850418 + * + * + * To use, include prettify.js and this file in your HTML page. Then put your + * code in an HTML tag like
(my LLVM code)
+ * + * + * The regular expressions were adapted from: + * https://github.com/hansstimer/llvm.tmbundle/blob/76fedd8f50fd6108b1780c51d79fbe3223de5f34/Syntaxes/LLVM.tmLanguage + * + * http://llvm.org/docs/LangRef.html#constants describes the language grammar. + * + * @author Nikhil Dabas + */ +class LangLlvm : Lang() { + companion object { + val fileExtensions: List + get() = listOf("llvm", "ll") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + + + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\t\n\r \\xA0]+"), null + ) + // A double quoted, possibly multi-line, string. + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^!?\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)"), + null + ) + // comment.llvm + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^;[^\r\n]*"), + null + ) + // variable.llvm + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[%@!](?:[-a-zA-Z$._][-a-zA-Z$._0-9]*|\\d+)") + ) + // According to http://llvm.org/docs/LangRef.html#well-formedness + // These reserved words cannot conflict with variable names, because none of them start with a prefix character ('%' or '@'). + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^[A-Za-z_][0-9A-Za-z_]*"), + null + ) + // constant.numeric.float.llvm + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex("^\\d+\\.\\d+") + ) + // constant.numeric.integer.llvm + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex("^(?:\\d+|0[xX][a-fA-F0-9]+)") + ) + // punctuation + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, Regex("^[()\\[\\]{},=*<>:]|\\.\\.\\.$") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLogtalk.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLogtalk.kt new file mode 100644 index 0000000..037acf8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLogtalk.kt @@ -0,0 +1,69 @@ +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +class LangLogtalk : Lang() { + + companion object { + val fileExtensions = listOf("logtalk", "lgt") + } + + override fun getFileExtensions(): List = fileExtensions + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\\"(?:[^\\\"\\\\\\n\\x0C\\r]|\\\\[\\s\\S])*(?:\\\"|$)"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^[a-z][a-zA-Z0-9_]*") + ) + shortcutStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^\\'(?:[^\\'\\\\\\n\\x0C\\r]|\\\\[^&])+\\'?"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_LITERAL, + Regex( + "^(?:0'.|0b[0-1]+|0o[0-7]+|0x[\\da-f]+|\\d+(?:\\.\\d+)?(?:e[+\\-]?\\d+)?)", + RegexOption.IGNORE_CASE + ), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^%[^\\r\\n]*"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^\\/\\*[\\s\\S]*?\\*\\/") + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^\\s*:-\\s(c(a(lls|tegory)|oinductive)|p(ublic|r(ot(ocol|ected)|ivate))|e(l(if|se)|n(coding|sure_loaded)|xport)|i(f|n(clude|itialization|fo))|alias|d(ynamic|iscontiguous)|m(eta_(non_terminal|predicate)|od(e|ule)|ultifile)|reexport|s(et_(logtalk|prolog)_flag|ynchronized)|o(bject|p)|use(s|_module))") + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^\\s*:-\\s(e(lse|nd(if|_(category|object|protocol)))|built_in|dynamic|synchronized|threaded)") + ) + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex("^[A-Z_][a-zA-Z0-9_]*") + ) + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[.,;{}:^<>=\\\\/+*?#!-]") + ) + + + } +} diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLua.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLua.kt new file mode 100644 index 0000000..38aeaec --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangLua.kt @@ -0,0 +1,103 @@ +// Copyright (C) 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-lua.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for Lua. + * + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *
(my Lua code)
+ * + * + * I used http://www.lua.org/manual/5.1/manual.html#2.1 + * Because of the long-bracket concept used in strings and comments, Lua does + * not have a regular lexical grammar, but luckily it fits within the space + * of irregular grammars supported by javascript regular expressions. + * + * @author mikesamuel@gmail.com + */ +class LangLua : Lang() { + companion object { + val fileExtensions: List + get() = listOf("lua") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\t\n\r \\xA0]+"), null + ) + // A double or single quoted, possibly multi-line, string. + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^(?:\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)|\\'(?:[^\\'\\\\]|\\\\[\\s\\S])*(?:\\'|$))"), + null + ) + // A comment is either a line comment that starts with two dashes, or + // two dashes preceding a long bracketed block. + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, Regex("^--(?:\\[(=*)\\[[\\s\\S]*?(?:\\]\\1\\]|$)|[^\\r\\n]*)") + ) + // A long bracketed block not preceded by -- is a string. + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^\\[(=*)\\[[\\s\\S]*?(?:\\]\\1\\]|$)") + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\\b"), + null + ) + // A number is a hex integer literal, a decimal real literal, or in + // scientific notation. + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex( + "^[+-]?(?:0x[\\da-f]+|(?:(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:e[+\\-]?\\d+)?))", + RegexOption.IGNORE_CASE + ) + ) + // An identifier + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[a-z_]\\w*", RegexOption.IGNORE_CASE) + ) + // A run of punctuation + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[^\\w\\t\\n\\r \\xA0][^\\w\\n\\r \\xA0\\\"\\'\\-\\+=]*") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMatlab.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMatlab.kt new file mode 100644 index 0000000..4242c77 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMatlab.kt @@ -0,0 +1,283 @@ +// Copyright (c) 2013 by Amro +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-ml.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * 0 Registers a language handler for MATLAB. + * + * To use, include prettify.js and this file in your HTML page. Then put your + * code inside an HTML tag like
+
* + * + * @see https://github.com/amroamroamro/prettify-matlab + */ +class LangMatlab : Lang() { + + companion object { + const val PR_IDENTIFIER = "ident" + const val PR_CONSTANT = "const" + const val PR_FUNCTION = "fun" + const val PR_FUNCTION_TOOLBOX = "fun_tbx" + const val PR_SYSCMD = "syscmd" + const val PR_CODE_OUTPUT = "codeoutput" + const val PR_ERROR = "err" + const val PR_WARNING = "wrn" + const val PR_TRANSPOSE = "transpose" + const val PR_LINE_CONTINUATION = "linecont" + val fileExtensions: List + get() = listOf("matlab") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + // identifiers: variable/function name, or a chain of variable names joined by dots (obj.method, struct.field1.field2, etc..) + // valid variable names (start with letter, and contains letters, digits, and underscores). + // we match "xx.yy" as a whole so that if "xx" is plain and "yy" is not, we dont get a false positive for "yy" + //var reIdent = '(?:[a-zA-Z][a-zA-Z0-9_]*)'; + //var reIdentChain = '(?:' + reIdent + '(?:\.' + reIdent + ')*' + ')'; + + // patterns that always start with a known character. Must have a shortcut string. + // whitespaces: space, tab, carriage return, line feed, line tab, form-feed, non-break space + shortcutStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[ \\t\\r\\n\\v\\f\\xA0]+"), + null + ) + // block comments + //TODO: chokes on nested block comments + //TODO: false positives when the lines with %{ and %} contain non-spaces + //[PR.PR_COMMENT, /^%(?:[^\{].*|\{(?:%|%*[^\}%])*(?:\}+%?)?)/, null], + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^%\\{[^%]*%+(?:[^\\}%][^%]*%+)*\\}"), + null + ) + + // single-line comments + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^%[^\\r\\n]*"), + null + ) + // system commands + shortcutStylePatterns.new(PR_SYSCMD, Regex("^![^\\r\\n]*"), null) + + // patterns that will be tried in order if the shortcut ones fail. May have shortcuts. + // line continuation + fallthroughStylePatterns.new( + PR_LINE_CONTINUATION, + Regex("^\\.\\.\\.\\s*[\\r\\n]"), + null + ) + // error message + fallthroughStylePatterns.new( + PR_ERROR, + Regex("^\\?\\?\\? [^\\r\\n]*"), + null + ) + // warning message + fallthroughStylePatterns.new( + PR_WARNING, + Regex("^Warning: [^\\r\\n]*"), + null + ) + // command prompt/output + //[PR_CODE_OUTPUT, /^>>\s+[^\r\n]*[\r\n]{1,2}[^=]*=[^\r\n]*[\r\n]{1,2}[^\r\n]*/, null], // full command output (both loose/compact format): `>> EXP\nVAR =\n VAL` + fallthroughStylePatterns.new( + PR_CODE_OUTPUT, + Regex("^>>\\s+"), + null + ) // only the command prompt `>> ` + fallthroughStylePatterns.new( + PR_CODE_OUTPUT, + Regex("^octave:\\d+>\\s+"), + null + ) // Octave command prompt `octave:1> ` + // identifier (chain) or closing-parenthesis/brace/bracket, and IS followed by transpose operator + // this way we dont misdetect the transpose operator ' as the start of a string + fallthroughStylePatterns.new( + "lang-matlab-operators", + Regex("^((?:[a-zA-Z][a-zA-Z0-9_]*(?:\\.[a-zA-Z][a-zA-Z0-9_]*)*|\\)|\\]|\\}|\\.)')"), + null + ) + // identifier (chain), and NOT followed by transpose operator + // this must come AFTER the "is followed by transpose" step (otherwise it chops the last char of identifier) + fallthroughStylePatterns.new( + "lang-matlab-identifiers", + Regex("^([a-zA-Z][a-zA-Z0-9_]*(?:\\.[a-zA-Z][a-zA-Z0-9_]*)*)(?!')"), + null + ) + // single-quoted strings: allow for escaping with '', no multilines + //[PR.PR_STRING, /(?:(?<=(?:\(|\[|\{|\s|=|;|,|:))|^)'(?:[^']|'')*'(?=(?:\)|\]|\}|\s|=|;|,|:|~|<|>|&|-|\+|\*|\.|\^|\|))/, null], // string vs. transpose (check before/after context using negative/positive lookbehind/lookahead) + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^'(?:[^']|'')*'"), + null + ) // "'" + // floating point numbers: 1, 1.0, 1i, -1.1E-1 + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^[+\\-]?\\.?\\d+(?:\\.\\d*)?(?:[Ee][+\\-]?\\d+)?[ij]?"), + null + ) + // parentheses, braces, brackets + fallthroughStylePatterns.new( + Prettify.PR_TAG, + Regex("^(?:\\{|\\}|\\(|\\)|\\[|\\])"), + null + ) // "{}()[]" + // other operators + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^(?:<|>|=|~|@|&|;|,|:|!|\\-|\\+|\\*|\\^|\\.|\\||\\\\|\\/)"), + null + ) + } + + override fun getFileExtensions(): List = fileExtensions + + class LangMatlabIdentifier : Lang() { + companion object { + val fileExtensions: List + get() = listOf(("matlab-identifiers")) + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + // Refer to: http://www.mathworks.com/help/matlab/functionlist-alpha.html + val coreFunctions = + "abs|accumarray|acos(?:d|h)?|acot(?:d|h)?|acsc(?:d|h)?|actxcontrol(?:list|select)?|actxGetRunningServer|actxserver|addlistener|addpath|addpref|addtodate|airy|align|alim|all|allchild|alpha|alphamap|amd|ancestor|and|angle|annotation|any|area|arrayfun|asec(?:d|h)?|asin(?:d|h)?|assert|assignin|atan(?:2|d|h)?|audiodevinfo|audioplayer|audiorecorder|aufinfo|auread|autumn|auwrite|avifile|aviinfo|aviread|axes|axis|balance|bar(?:3|3h|h)?|base2dec|beep|BeginInvoke|bench|bessel(?:h|i|j|k|y)|beta|betainc|betaincinv|betaln|bicg|bicgstab|bicgstabl|bin2dec|bitand|bitcmp|bitget|bitmax|bitnot|bitor|bitset|bitshift|bitxor|blanks|blkdiag|bone|box|brighten|brush|bsxfun|builddocsearchdb|builtin|bvp4c|bvp5c|bvpget|bvpinit|bvpset|bvpxtend|calendar|calllib|callSoapService|camdolly|cameratoolbar|camlight|camlookat|camorbit|campan|campos|camproj|camroll|camtarget|camup|camva|camzoom|cart2pol|cart2sph|cast|cat|caxis|cd|cdf2rdf|cdfepoch|cdfinfo|cdflib(?:\\.(?:close|closeVar|computeEpoch|computeEpoch16|create|createAttr|createVar|delete|deleteAttr|deleteAttrEntry|deleteAttrgEntry|deleteVar|deleteVarRecords|epoch16Breakdown|epochBreakdown|getAttrEntry|getAttrgEntry|getAttrMaxEntry|getAttrMaxgEntry|getAttrName|getAttrNum|getAttrScope|getCacheSize|getChecksum|getCompression|getCompressionCacheSize|getConstantNames|getConstantValue|getCopyright|getFileBackward|getFormat|getLibraryCopyright|getLibraryVersion|getMajority|getName|getNumAttrEntries|getNumAttrgEntries|getNumAttributes|getNumgAttributes|getReadOnlyMode|getStageCacheSize|getValidate|getVarAllocRecords|getVarBlockingFactor|getVarCacheSize|getVarCompression|getVarData|getVarMaxAllocRecNum|getVarMaxWrittenRecNum|getVarName|getVarNum|getVarNumRecsWritten|getVarPadValue|getVarRecordData|getVarReservePercent|getVarsMaxWrittenRecNum|getVarSparseRecords|getVersion|hyperGetVarData|hyperPutVarData|inquire|inquireAttr|inquireAttrEntry|inquireAttrgEntry|inquireVar|open|putAttrEntry|putAttrgEntry|putVarData|putVarRecordData|renameAttr|renameVar|setCacheSize|setChecksum|setCompression|setCompressionCacheSize|setFileBackward|setFormat|setMajority|setReadOnlyMode|setStageCacheSize|setValidate|setVarAllocBlockRecords|setVarBlockingFactor|setVarCacheSize|setVarCompression|setVarInitialRecs|setVarPadValue|SetVarReservePercent|setVarsCacheSize|setVarSparseRecords))?|cdfread|cdfwrite|ceil|cell2mat|cell2struct|celldisp|cellfun|cellplot|cellstr|cgs|checkcode|checkin|checkout|chol|cholinc|cholupdate|circshift|cla|clabel|class|clc|clear|clearvars|clf|clipboard|clock|close|closereq|cmopts|cmpermute|cmunique|colamd|colon|colorbar|colordef|colormap|colormapeditor|colperm|Combine|comet|comet3|commandhistory|commandwindow|compan|compass|complex|computer|cond|condeig|condest|coneplot|conj|containers\\.Map|contour(?:3|c|f|slice)?|contrast|conv|conv2|convhull|convhulln|convn|cool|copper|copyfile|copyobj|corrcoef|cos(?:d|h)?|cot(?:d|h)?|cov|cplxpair|cputime|createClassFromWsdl|createSoapMessage|cross|csc(?:d|h)?|csvread|csvwrite|ctranspose|cumprod|cumsum|cumtrapz|curl|customverctrl|cylinder|daqread|daspect|datacursormode|datatipinfo|date|datenum|datestr|datetick|datevec|dbclear|dbcont|dbdown|dblquad|dbmex|dbquit|dbstack|dbstatus|dbstep|dbstop|dbtype|dbup|dde23|ddeget|ddesd|ddeset|deal|deblank|dec2base|dec2bin|dec2hex|decic|deconv|del2|delaunay|delaunay3|delaunayn|DelaunayTri|delete|demo|depdir|depfun|det|detrend|deval|diag|dialog|diary|diff|diffuse|dir|disp|display|dither|divergence|dlmread|dlmwrite|dmperm|doc|docsearch|dos|dot|dragrect|drawnow|dsearch|dsearchn|dynamicprops|echo|echodemo|edit|eig|eigs|ellipj|ellipke|ellipsoid|empty|enableNETfromNetworkDrive|enableservice|EndInvoke|enumeration|eomday|eq|erf|erfc|erfcinv|erfcx|erfinv|error|errorbar|errordlg|etime|etree|etreeplot|eval|evalc|evalin|event\\.(?:EventData|listener|PropertyEvent|proplistener)|exifread|exist|exit|exp|expint|expm|expm1|export2wsdlg|eye|ezcontour|ezcontourf|ezmesh|ezmeshc|ezplot|ezplot3|ezpolar|ezsurf|ezsurfc|factor|factorial|fclose|feather|feature|feof|ferror|feval|fft|fft2|fftn|fftshift|fftw|fgetl|fgets|fieldnames|figure|figurepalette|fileattrib|filebrowser|filemarker|fileparts|fileread|filesep|fill|fill3|filter|filter2|find|findall|findfigs|findobj|findstr|finish|fitsdisp|fitsinfo|fitsread|fitswrite|fix|flag|flipdim|fliplr|flipud|floor|flow|fminbnd|fminsearch|fopen|format|fplot|fprintf|frame2im|fread|freqspace|frewind|fscanf|fseek|ftell|FTP|full|fullfile|func2str|functions|funm|fwrite|fzero|gallery|gamma|gammainc|gammaincinv|gammaln|gca|gcbf|gcbo|gcd|gcf|gco|ge|genpath|genvarname|get|getappdata|getenv|getfield|getframe|getpixelposition|getpref|ginput|gmres|gplot|grabcode|gradient|gray|graymon|grid|griddata(?:3|n)?|griddedInterpolant|gsvd|gt|gtext|guidata|guide|guihandles|gunzip|gzip|h5create|h5disp|h5info|h5read|h5readatt|h5write|h5writeatt|hadamard|handle|hankel|hdf|hdf5|hdf5info|hdf5read|hdf5write|hdfinfo|hdfread|hdftool|help|helpbrowser|helpdesk|helpdlg|helpwin|hess|hex2dec|hex2num|hgexport|hggroup|hgload|hgsave|hgsetget|hgtransform|hidden|hilb|hist|histc|hold|home|horzcat|hostid|hot|hsv|hsv2rgb|hypot|ichol|idivide|ifft|ifft2|ifftn|ifftshift|ilu|im2frame|im2java|imag|image|imagesc|imapprox|imfinfo|imformats|import|importdata|imread|imwrite|ind2rgb|ind2sub|inferiorto|info|inline|inmem|inpolygon|input|inputdlg|inputname|inputParser|inspect|instrcallback|instrfind|instrfindall|int2str|integral(?:2|3)?|interp(?:1|1q|2|3|ft|n)|interpstreamspeed|intersect|intmax|intmin|inv|invhilb|ipermute|isa|isappdata|iscell|iscellstr|ischar|iscolumn|isdir|isempty|isequal|isequaln|isequalwithequalnans|isfield|isfinite|isfloat|isglobal|ishandle|ishghandle|ishold|isinf|isinteger|isjava|iskeyword|isletter|islogical|ismac|ismatrix|ismember|ismethod|isnan|isnumeric|isobject|isocaps|isocolors|isonormals|isosurface|ispc|ispref|isprime|isprop|isreal|isrow|isscalar|issorted|isspace|issparse|isstr|isstrprop|isstruct|isstudent|isunix|isvarname|isvector|javaaddpath|javaArray|javachk|javaclasspath|javacomponent|javaMethod|javaMethodEDT|javaObject|javaObjectEDT|javarmpath|jet|keyboard|kron|lasterr|lasterror|lastwarn|lcm|ldivide|ldl|le|legend|legendre|length|libfunctions|libfunctionsview|libisloaded|libpointer|libstruct|license|light|lightangle|lighting|lin2mu|line|lines|linkaxes|linkdata|linkprop|linsolve|linspace|listdlg|listfonts|load|loadlibrary|loadobj|log|log10|log1p|log2|loglog|logm|logspace|lookfor|lower|ls|lscov|lsqnonneg|lsqr|lt|lu|luinc|magic|makehgtform|mat2cell|mat2str|material|matfile|matlab\\.io\\.MatFile|matlab\\.mixin\\.(?:Copyable|Heterogeneous(?:\\.getDefaultScalarElement)?)|matlabrc|matlabroot|max|maxNumCompThreads|mean|median|membrane|memmapfile|memory|menu|mesh|meshc|meshgrid|meshz|meta\\.(?:class(?:\\.fromName)?|DynamicProperty|EnumeratedValue|event|MetaData|method|package(?:\\.(?:fromName|getAllPackages))?|property)|metaclass|methods|methodsview|mex(?:\\.getCompilerConfigurations)?|MException|mexext|mfilename|min|minres|minus|mislocked|mkdir|mkpp|mldivide|mlint|mlintrpt|mlock|mmfileinfo|mmreader|mod|mode|more|move|movefile|movegui|movie|movie2avi|mpower|mrdivide|msgbox|mtimes|mu2lin|multibandread|multibandwrite|munlock|namelengthmax|nargchk|narginchk|nargoutchk|native2unicode|nccreate|ncdisp|nchoosek|ncinfo|ncread|ncreadatt|ncwrite|ncwriteatt|ncwriteschema|ndgrid|ndims|ne|NET(?:\\.(?:addAssembly|Assembly|convertArray|createArray|createGeneric|disableAutoRelease|enableAutoRelease|GenericClass|invokeGenericMethod|NetException|setStaticProperty))?|netcdf\\.(?:abort|close|copyAtt|create|defDim|defGrp|defVar|defVarChunking|defVarDeflate|defVarFill|defVarFletcher32|delAtt|endDef|getAtt|getChunkCache|getConstant|getConstantNames|getVar|inq|inqAtt|inqAttID|inqAttName|inqDim|inqDimID|inqDimIDs|inqFormat|inqGrpName|inqGrpNameFull|inqGrpParent|inqGrps|inqLibVers|inqNcid|inqUnlimDims|inqVar|inqVarChunking|inqVarDeflate|inqVarFill|inqVarFletcher32|inqVarID|inqVarIDs|open|putAtt|putVar|reDef|renameAtt|renameDim|renameVar|setChunkCache|setDefaultFormat|setFill|sync)|newplot|nextpow2|nnz|noanimate|nonzeros|norm|normest|not|notebook|now|nthroot|null|num2cell|num2hex|num2str|numel|nzmax|ode(?:113|15i|15s|23|23s|23t|23tb|45)|odeget|odeset|odextend|onCleanup|ones|open|openfig|opengl|openvar|optimget|optimset|or|ordeig|orderfields|ordqz|ordschur|orient|orth|pack|padecoef|pagesetupdlg|pan|pareto|parseSoapResponse|pascal|patch|path|path2rc|pathsep|pathtool|pause|pbaspect|pcg|pchip|pcode|pcolor|pdepe|pdeval|peaks|perl|perms|permute|pie|pink|pinv|planerot|playshow|plot|plot3|plotbrowser|plotedit|plotmatrix|plottools|plotyy|plus|pol2cart|polar|poly|polyarea|polyder|polyeig|polyfit|polyint|polyval|polyvalm|pow2|power|ppval|prefdir|preferences|primes|print|printdlg|printopt|printpreview|prod|profile|profsave|propedit|propertyeditor|psi|publish|PutCharArray|PutFullMatrix|PutWorkspaceData|pwd|qhull|qmr|qr|qrdelete|qrinsert|qrupdate|quad|quad2d|quadgk|quadl|quadv|questdlg|quit|quiver|quiver3|qz|rand|randi|randn|randperm|RandStream(?:\\.(?:create|getDefaultStream|getGlobalStream|list|setDefaultStream|setGlobalStream))?|rank|rat|rats|rbbox|rcond|rdivide|readasync|real|reallog|realmax|realmin|realpow|realsqrt|record|rectangle|rectint|recycle|reducepatch|reducevolume|refresh|refreshdata|regexp|regexpi|regexprep|regexptranslate|rehash|rem|Remove|RemoveAll|repmat|reset|reshape|residue|restoredefaultpath|rethrow|rgb2hsv|rgb2ind|rgbplot|ribbon|rmappdata|rmdir|rmfield|rmpath|rmpref|rng|roots|rose|rosser|rot90|rotate|rotate3d|round|rref|rsf2csf|run|save|saveas|saveobj|savepath|scatter|scatter3|schur|sec|secd|sech|selectmoveresize|semilogx|semilogy|sendmail|serial|set|setappdata|setdiff|setenv|setfield|setpixelposition|setpref|setstr|setxor|shading|shg|shiftdim|showplottool|shrinkfaces|sign|sin(?:d|h)?|size|slice|smooth3|snapnow|sort|sortrows|sound|soundsc|spalloc|spaugment|spconvert|spdiags|specular|speye|spfun|sph2cart|sphere|spinmap|spline|spones|spparms|sprand|sprandn|sprandsym|sprank|spring|sprintf|spy|sqrt|sqrtm|squeeze|ss2tf|sscanf|stairs|startup|std|stem|stem3|stopasync|str2double|str2func|str2mat|str2num|strcat|strcmp|strcmpi|stream2|stream3|streamline|streamparticles|streamribbon|streamslice|streamtube|strfind|strjust|strmatch|strncmp|strncmpi|strread|strrep|strtok|strtrim|struct2cell|structfun|strvcat|sub2ind|subplot|subsasgn|subsindex|subspace|subsref|substruct|subvolume|sum|summer|superclasses|superiorto|support|surf|surf2patch|surface|surfc|surfl|surfnorm|svd|svds|swapbytes|symamd|symbfact|symmlq|symrcm|symvar|system|tan(?:d|h)?|tar|tempdir|tempname|tetramesh|texlabel|text|textread|textscan|textwrap|tfqmr|throw|tic|Tiff(?:\\.(?:getTagNames|getVersion))?|timer|timerfind|timerfindall|times|timeseries|title|toc|todatenum|toeplitz|toolboxdir|trace|transpose|trapz|treelayout|treeplot|tril|trimesh|triplequad|triplot|TriRep|TriScatteredInterp|trisurf|triu|tscollection|tsearch|tsearchn|tstool|type|typecast|uibuttongroup|uicontextmenu|uicontrol|uigetdir|uigetfile|uigetpref|uiimport|uimenu|uiopen|uipanel|uipushtool|uiputfile|uiresume|uisave|uisetcolor|uisetfont|uisetpref|uistack|uitable|uitoggletool|uitoolbar|uiwait|uminus|undocheckout|unicode2native|union|unique|unix|unloadlibrary|unmesh|unmkpp|untar|unwrap|unzip|uplus|upper|urlread|urlwrite|usejava|userpath|validateattributes|validatestring|vander|var|vectorize|ver|verctrl|verLessThan|version|vertcat|VideoReader(?:\\.isPlatformSupported)?|VideoWriter(?:\\.getProfiles)?|view|viewmtx|visdiff|volumebounds|voronoi|voronoin|wait|waitbar|waitfor|waitforbuttonpress|warndlg|warning|waterfall|wavfinfo|wavplay|wavread|wavrecord|wavwrite|web|weekday|what|whatsnew|which|whitebg|who|whos|wilkinson|winopen|winqueryreg|winter|wk1finfo|wk1read|wk1write|workspace|xlabel|xlim|xlsfinfo|xlsread|xlswrite|xmlread|xmlwrite|xor|xslt|ylabel|ylim|zeros|zip|zlabel|zlim|zoom" + val statsFunctions = + "addedvarplot|andrewsplot|anova(?:1|2|n)|ansaribradley|aoctool|barttest|bbdesign|beta(?:cdf|fit|inv|like|pdf|rnd|stat)|bino(?:cdf|fit|inv|pdf|rnd|stat)|biplot|bootci|bootstrp|boxplot|candexch|candgen|canoncorr|capability|capaplot|caseread|casewrite|categorical|ccdesign|cdfplot|chi2(?:cdf|gof|inv|pdf|rnd|stat)|cholcov|Classification(?:BaggedEnsemble|Discriminant(?:\\.(?:fit|make|template))?|Ensemble|KNN(?:\\.(?:fit|template))?|PartitionedEnsemble|PartitionedModel|Tree(?:\\.(?:fit|template))?)|classify|classregtree|cluster|clusterdata|cmdscale|combnk|Compact(?:Classification(?:Discriminant|Ensemble|Tree)|Regression(?:Ensemble|Tree)|TreeBagger)|confusionmat|controlchart|controlrules|cophenet|copula(?:cdf|fit|param|pdf|rnd|stat)|cordexch|corr|corrcov|coxphfit|createns|crosstab|crossval|cvpartition|datasample|dataset|daugment|dcovary|dendrogram|dfittool|disttool|dummyvar|dwtest|ecdf|ecdfhist|ev(?:cdf|fit|inv|like|pdf|rnd|stat)|ExhaustiveSearcher|exp(?:cdf|fit|inv|like|pdf|rnd|stat)|factoran|fcdf|ff2n|finv|fitdist|fitensemble|fpdf|fracfact|fracfactgen|friedman|frnd|fstat|fsurfht|fullfact|gagerr|gam(?:cdf|fit|inv|like|pdf|rnd|stat)|GeneralizedLinearModel(?:\\.fit)?|geo(?:cdf|inv|mean|pdf|rnd|stat)|gev(?:cdf|fit|inv|like|pdf|rnd|stat)|gline|glmfit|glmval|glyphplot|gmdistribution(?:\\.fit)?|gname|gp(?:cdf|fit|inv|like|pdf|rnd|stat)|gplotmatrix|grp2idx|grpstats|gscatter|haltonset|harmmean|hist3|histfit|hmm(?:decode|estimate|generate|train|viterbi)|hougen|hyge(?:cdf|inv|pdf|rnd|stat)|icdf|inconsistent|interactionplot|invpred|iqr|iwishrnd|jackknife|jbtest|johnsrnd|KDTreeSearcher|kmeans|knnsearch|kruskalwallis|ksdensity|kstest|kstest2|kurtosis|lasso|lassoglm|lassoPlot|leverage|lhsdesign|lhsnorm|lillietest|LinearModel(?:\\.fit)?|linhyptest|linkage|logn(?:cdf|fit|inv|like|pdf|rnd|stat)|lsline|mad|mahal|maineffectsplot|manova1|manovacluster|mdscale|mhsample|mle|mlecov|mnpdf|mnrfit|mnrnd|mnrval|moment|multcompare|multivarichart|mvn(?:cdf|pdf|rnd)|mvregress|mvregresslike|mvt(?:cdf|pdf|rnd)|NaiveBayes(?:\\.fit)?|nan(?:cov|max|mean|median|min|std|sum|var)|nbin(?:cdf|fit|inv|pdf|rnd|stat)|ncf(?:cdf|inv|pdf|rnd|stat)|nct(?:cdf|inv|pdf|rnd|stat)|ncx2(?:cdf|inv|pdf|rnd|stat)|NeighborSearcher|nlinfit|nlintool|nlmefit|nlmefitsa|nlparci|nlpredci|nnmf|nominal|NonLinearModel(?:\\.fit)?|norm(?:cdf|fit|inv|like|pdf|rnd|stat)|normplot|normspec|ordinal|outlierMeasure|parallelcoords|paretotails|partialcorr|pcacov|pcares|pdf|pdist|pdist2|pearsrnd|perfcurve|perms|piecewisedistribution|plsregress|poiss(?:cdf|fit|inv|pdf|rnd|tat)|polyconf|polytool|prctile|princomp|ProbDist(?:Kernel|Parametric|UnivKernel|UnivParam)?|probplot|procrustes|qqplot|qrandset|qrandstream|quantile|randg|random|randsample|randtool|range|rangesearch|ranksum|rayl(?:cdf|fit|inv|pdf|rnd|stat)|rcoplot|refcurve|refline|regress|Regression(?:BaggedEnsemble|Ensemble|PartitionedEnsemble|PartitionedModel|Tree(?:\\.(?:fit|template))?)|regstats|relieff|ridge|robustdemo|robustfit|rotatefactors|rowexch|rsmdemo|rstool|runstest|sampsizepwr|scatterhist|sequentialfs|signrank|signtest|silhouette|skewness|slicesample|sobolset|squareform|statget|statset|stepwise|stepwisefit|surfht|tabulate|tblread|tblwrite|tcdf|tdfread|tiedrank|tinv|tpdf|TreeBagger|treedisp|treefit|treeprune|treetest|treeval|trimmean|trnd|tstat|ttest|ttest2|unid(?:cdf|inv|pdf|rnd|stat)|unif(?:cdf|inv|it|pdf|rnd|stat)|vartest(?:2|n)?|wbl(?:cdf|fit|inv|like|pdf|rnd|stat)|wblplot|wishrnd|x2fx|xptread|zscore|ztest" + val imageFunctions = + "adapthisteq|analyze75info|analyze75read|applycform|applylut|axes2pix|bestblk|blockproc|bwarea|bwareaopen|bwboundaries|bwconncomp|bwconvhull|bwdist|bwdistgeodesic|bweuler|bwhitmiss|bwlabel|bwlabeln|bwmorph|bwpack|bwperim|bwselect|bwtraceboundary|bwulterode|bwunpack|checkerboard|col2im|colfilt|conndef|convmtx2|corner|cornermetric|corr2|cp2tform|cpcorr|cpselect|cpstruct2pairs|dct2|dctmtx|deconvblind|deconvlucy|deconvreg|deconvwnr|decorrstretch|demosaic|dicom(?:anon|dict|info|lookup|read|uid|write)|edge|edgetaper|entropy|entropyfilt|fan2para|fanbeam|findbounds|fliptform|freqz2|fsamp2|fspecial|ftrans2|fwind1|fwind2|getheight|getimage|getimagemodel|getline|getneighbors|getnhood|getpts|getrangefromclass|getrect|getsequence|gray2ind|graycomatrix|graycoprops|graydist|grayslice|graythresh|hdrread|hdrwrite|histeq|hough|houghlines|houghpeaks|iccfind|iccread|iccroot|iccwrite|idct2|ifanbeam|im2bw|im2col|im2double|im2int16|im2java2d|im2single|im2uint16|im2uint8|imabsdiff|imadd|imadjust|ImageAdapter|imageinfo|imagemodel|imapplymatrix|imattributes|imbothat|imclearborder|imclose|imcolormaptool|imcomplement|imcontour|imcontrast|imcrop|imdilate|imdisplayrange|imdistline|imdivide|imellipse|imerode|imextendedmax|imextendedmin|imfill|imfilter|imfindcircles|imfreehand|imfuse|imgca|imgcf|imgetfile|imhandles|imhist|imhmax|imhmin|imimposemin|imlincomb|imline|immagbox|immovie|immultiply|imnoise|imopen|imoverview|imoverviewpanel|impixel|impixelinfo|impixelinfoval|impixelregion|impixelregionpanel|implay|impoint|impoly|impositionrect|improfile|imputfile|impyramid|imreconstruct|imrect|imregconfig|imregionalmax|imregionalmin|imregister|imresize|imroi|imrotate|imsave|imscrollpanel|imshow|imshowpair|imsubtract|imtool|imtophat|imtransform|imview|ind2gray|ind2rgb|interfileinfo|interfileread|intlut|ippl|iptaddcallback|iptcheckconn|iptcheckhandle|iptcheckinput|iptcheckmap|iptchecknargin|iptcheckstrs|iptdemos|iptgetapi|iptGetPointerBehavior|iptgetpref|ipticondir|iptnum2ordinal|iptPointerManager|iptprefs|iptremovecallback|iptSetPointerBehavior|iptsetpref|iptwindowalign|iradon|isbw|isflat|isgray|isicc|isind|isnitf|isrgb|isrset|lab2double|lab2uint16|lab2uint8|label2rgb|labelmatrix|makecform|makeConstrainToRectFcn|makehdr|makelut|makeresampler|maketform|mat2gray|mean2|medfilt2|montage|nitfinfo|nitfread|nlfilter|normxcorr2|ntsc2rgb|openrset|ordfilt2|otf2psf|padarray|para2fan|phantom|poly2mask|psf2otf|qtdecomp|qtgetblk|qtsetblk|radon|rangefilt|reflect|regionprops|registration\\.metric\\.(?:MattesMutualInformation|MeanSquares)|registration\\.optimizer\\.(?:OnePlusOneEvolutionary|RegularStepGradientDescent)|rgb2gray|rgb2ntsc|rgb2ycbcr|roicolor|roifill|roifilt2|roipoly|rsetwrite|std2|stdfilt|strel|stretchlim|subimage|tformarray|tformfwd|tforminv|tonemap|translate|truesize|uintlut|viscircles|warp|watershed|whitepoint|wiener2|xyz2double|xyz2uint16|ycbcr2rgb" + val optimFunctions = + "bintprog|color|fgoalattain|fminbnd|fmincon|fminimax|fminsearch|fminunc|fseminf|fsolve|fzero|fzmult|gangstr|ktrlink|linprog|lsqcurvefit|lsqlin|lsqnonlin|lsqnonneg|optimget|optimset|optimtool|quadprog" + + // list of keywords (`iskeyword`) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^\\b(?:break|case|catch|classdef|continue|else|elseif|end|for|function|global|if|otherwise|parfor|persistent|return|spmd|switch|try|while)\\b"), + null + ) + // some specials variables/constants + fallthroughStylePatterns.new( + PR_CONSTANT, + Regex("^\\b(?:true|false|inf|Inf|nan|NaN|eps|pi|ans|nargin|nargout|varargin|varargout)\\b"), + null + ) + // some data types + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex("^\\b(?:cell|struct|char|double|single|logical|u?int(?:8|16|32|64)|sparse)\\b"), + null + ) + // commonly used builtin functions from core MATLAB and a few popular toolboxes + fallthroughStylePatterns.new( + PR_FUNCTION, Regex( + "^\\b(?:$coreFunctions)\\b" + ), null + ) + fallthroughStylePatterns.new( + PR_FUNCTION_TOOLBOX, Regex( + "^\\b(?:$statsFunctions)\\b" + ), null + ) + fallthroughStylePatterns.new( + PR_FUNCTION_TOOLBOX, Regex( + "^\\b(?:$imageFunctions)\\b" + ), null + ) + fallthroughStylePatterns.new( + PR_FUNCTION_TOOLBOX, Regex( + "^\\b(?:$optimFunctions)\\b" + ), null + ) + // plain identifier (user-defined variable/function name) + fallthroughStylePatterns.new( + PR_IDENTIFIER, + Regex("^[a-zA-Z][a-zA-Z0-9_]*(?:\\.[a-zA-Z][a-zA-Z0-9_]*)*"), + null + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } + } + + class LangMatlabOperator : Lang() { + companion object { + val fileExtensions: List + get() = listOf("matlab-operators") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + // forward to identifiers to match + fallthroughStylePatterns.new( + "lang-matlab-identifiers", + Regex("^([a-zA-Z][a-zA-Z0-9_]*(?:\\.[a-zA-Z][a-zA-Z0-9_]*)*)"), + null + ) + // parentheses, braces, brackets + fallthroughStylePatterns.new( + Prettify.PR_TAG, + Regex("^(?:\\{|\\}|\\(|\\)|\\[|\\])"), + null + ) // "{}()[]" + // other operators + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^(?:<|>|=|~|@|&|;|,|:|!|\\-|\\+|\\*|\\^|\\.|\\||\\\\|\\/)"), + null + ) + // transpose operators + fallthroughStylePatterns.new(PR_TRANSPOSE, Regex("^'"), null) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMd.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMd.kt new file mode 100644 index 0000000..6dc77f4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMd.kt @@ -0,0 +1,37 @@ +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * Registers a language handler for markdown. + * + * @author Kirill Biakov (kbiakov@gmail.com) + */ +class LangMd : Lang() { + companion object { + val fileExtensions: List + get() = listOf("md", "markdown") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + fallthroughStylePatterns.new( + Prettify.PR_DECLARATION, + Regex("^#.*?[\\n\\r]") + ) + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^```[\\s\\S]*?(?:```|$)") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMl.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMl.kt new file mode 100644 index 0000000..8daf520 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMl.kt @@ -0,0 +1,102 @@ +// Copyright (C) 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-ml.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for OCaml, SML, F# and similar languages. + * + * Based on the lexical grammar at + * http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/spec.html#_Toc270597388 + * + * @author mikesamuel@gmail.com + */ +class LangMl : Lang() { + companion object { + val fileExtensions: List + get() = listOf("fs", "ml") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + + + // Whitespace is made up of spaces, tabs and newline characters. + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\\t\\n\\r \\xA0]+"), null + ) + // #if ident/#else/#endif directives delimit conditional compilation + // sections + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex( + "^#(?:if[\\t\\n\\r \\xA0]+(?:[a-z_$][\\w\\']*|``[^\\r\\n\\t`]*(?:``|$))|else|endif|light)", + RegexOption.IGNORE_CASE + ), + null + ) + // A double or single quoted, possibly multi-line, string. + // F# allows escaped newlines in strings. + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^(?:\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)|\\'(?:[^\\'\\\\]|\\\\[\\s\\S])(?:\\'|$))"), + null + ) + // Block comments are delimited by (* and *) and may be + // nested. Single-line comments begin with // and extend to + // the end of a line. + // TODO: (*...*) comments can be nested. This does not handle that. + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, Regex("^(?:\\/\\/[^\\r\\n]*|\\(\\*[\\s\\S]*?\\*\\))") + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:abstract|and|as|assert|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|if|in|inherit|inline|interface|internal|lazy|let|match|member|module|mutable|namespace|new|null|of|open|or|override|private|public|rec|return|static|struct|then|to|true|try|type|upcast|use|val|void|when|while|with|yield|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|global|include|method|mixin|object|parallel|process|protected|pure|sealed|trait|virtual|volatile)\\b") + ) + // A number is a hex integer literal, a decimal real literal, or in + // scientific notation. + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex( + "^[+\\-]?(?:0x[\\da-f]+|(?:(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:e[+\\-]?\\d+)?))", + RegexOption.IGNORE_CASE + ) + ) + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^(?:[a-z_][\\w']*[!?#]?|``[^\\r\\n\\t`]*(?:``|$))", RegexOption.IGNORE_CASE) + ) + // A printable non-space non-special character + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, Regex("^[^\\t\\n\\r \\xA0\\\"\\'\\w]+") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMumps.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMumps.kt new file mode 100644 index 0000000..7359f7c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangMumps.kt @@ -0,0 +1,180 @@ +// Copyright (C) 2011 Kitware Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-mumps.js in JavaScript Prettify. + * + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *
(my SQL code)
+ * + * + * Commands, intrinsic functions and variables taken from ISO/IEC 11756:1999(E) + * + * @author chris.harris@kitware.com + * + * + * Known issues: + * + * + * - Currently can't distinguish between keywords and local or global variables having the same name + * for exampe SET IF="IF?" + * - m file are already used for MatLab hence using mumps. + */ +class LangMumps : Lang() { + companion object { + val fileExtensions: List + get() = listOf("mumps") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + + val commands = "B|BREAK|" + + "C|CLOSE|" + + "D|DO|" + + "E|ELSE|" + + "F|FOR|" + + "G|GOTO|" + + "H|HALT|" + + "H|HANG|" + + "I|IF|" + + "J|JOB|" + + "K|KILL|" + + "L|LOCK|" + + "M|MERGE|" + + "N|NEW|" + + "O|OPEN|" + + "Q|QUIT|" + + "R|READ|" + + "S|SET|" + + "TC|TCOMMIT|" + + "TRE|TRESTART|" + + "TRO|TROLLBACK|" + + "TS|TSTART|" + + "U|USE|" + + "V|VIEW|" + + "W|WRITE|" + + "X|XECUTE" + val intrinsicVariables = "D|DEVICE|" + + "EC|ECODE|" + + "ES|ESTACK|" + + "ET|ETRAP|" + + "H|HOROLOG|" + + "I|IO|" + + "J|JOB|" + + "K|KEY|" + + "P|PRINCIPAL|" + + "Q|QUIT|" + + "ST|STACK|" + + "S|STORAGE|" + + "SY|SYSTEM|" + + "T|TEST|" + + "TL|TLEVEL|" + + "TR|TRESTART|" + + "X|" + + "Y|" + + "Z[A-Z]*|" + val intrinsicFunctions = "A|ASCII|" + + "C|CHAR|" + + "D|DATA|" + + "E|EXTRACT|" + + "F|FIND|" + + "FN|FNUMBER|" + + "G|GET|" + + "J|JUSTIFY|" + + "L|LENGTH|" + + "NA|NAME|" + + "O|ORDER|" + + "P|PIECE|" + + "QL|QLENGTH|" + + "QS|QSUBSCRIPT|" + + "Q|QUERY|" + + "R|RANDOM|" + + "RE|REVERSE|" + + "S|SELECT|" + + "ST|STACK|" + + "T|TEXT|" + + "TR|TRANSLATE|" + + "V|VIEW|" + + "Z[A-Z]*|" + val intrinsic = intrinsicVariables + intrinsicFunctions + + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\t\n\r \\xA0]+"), null + ) + // A double or single quoted, possibly multi-line, string. + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^(?:\"(?:[^\"]|\\\\.)*\")"), + null + ) + + // A line comment that starts with ; + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^;[^\\r\\n]*"), + null + ) + // Add intrinsic variables and functions as declarations, there not really but it mean + // they will hilighted differently from commands. + fallthroughStylePatterns.new( + Prettify.PR_DECLARATION, Regex( + "^(?:\\$(?:$intrinsic))\\b", RegexOption.IGNORE_CASE + ), null + ) + // Add commands as keywords + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, Regex( + "^(?:[^\\$]$commands)\\b", RegexOption.IGNORE_CASE + ), null + ) + // A number is a decimal real literal or in scientific notation. + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^[+-]?(?:(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:E[+\\-]?\\d+)?)", RegexOption.IGNORE_CASE) + ) + // An identifier + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[a-z][a-zA-Z0-9]*", RegexOption.IGNORE_CASE) + ) + // Exclude $ % and ^ + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[^\\w\\t\\n\\r\\xA0\\\"\\$;%\\^]|_") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangN.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangN.kt new file mode 100644 index 0000000..4917c13 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangN.kt @@ -0,0 +1,132 @@ +// Copyright (C) 2011 Zimin A.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-n.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for the Nemerle language. + * http://nemerle.org + * @author Zimin A.V. + */ +class LangN : Lang() { + companion object { + private var keywords = ("abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|" + + "fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|" + + "null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|" + + "syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|" + + "assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|" + + "otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield") + val fileExtensions: List + get() = listOf("n", "nemerle") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^(?:\\'(?:[^\\\\\\'\\r\\n]|\\\\.)*\\'|\\\"(?:[^\\\\\\\"\\r\\n]|\\\\.)*(?:\\\"|$))"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\\b|[^\\r\\n]*)"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^\\s+"), null + ) + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^@\\\"(?:[^\\\"]|\\\"\\\")*(?:\\\"|$)"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^<#(?:[^#>])*(?:#>|$)"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_STRING, + Regex("^<(?:(?:(?:\\.\\.\\/)*|\\/?)(?:[\\w-]+(?:\\/[\\w-]+)+)?[\\w-]+\\.h|[a-z]\\w*)>"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^\\/\\/[^\\r\\n]*"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^\\/\\*[\\s\\S]*?(?:\\*\\/|$)"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:$keywords)\\\\b"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex("^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\\b"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^@[a-z_$][a-z_$@0-9]*", RegexOption.IGNORE_CASE), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex("^@[A-Z]+[a-z][A-Za-z_$@0-9]*"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^'?[A-Za-z_$][a-z_$@0-9]*", RegexOption.IGNORE_CASE), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex( + "^(?:" // A hex number + + "0x[a-f0-9]+" // or an octal or decimal number, + + "|(?:\\\\d(?:_\\\\d+)*\\\\d*(?:\\\\.\\\\d*)?|\\\\.\\\\d\\\\+)" // possibly in scientific notation + + "(?:e[+\\\\-]?\\\\d+)?" + + ")" // with an optional modifier like UL for unsigned long + + "[a-z]*", RegexOption.IGNORE_CASE + ), null + ) + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^.[^\\s\\w\\.$@\\'\\\"\\`\\/\\#]*"), + null + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangPascal.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangPascal.kt new file mode 100644 index 0000000..d360893 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangPascal.kt @@ -0,0 +1,105 @@ +// Copyright (C) 2009 Onno Hommes. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-appollo.js in JavaScript Prettify. + * + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * + * Registers a language handler for the AGC/AEA Assembly Language as described + * at http://virtualagc.googlecode.com + * + * + * This file could be used by goodle code to allow syntax highlight for + * Virtual AGC SVN repository or if you don't want to commonize + * the header for the agc/aea html assembly listing. + * + * @author ohommes@alumni.cmu.edu + */ +class LangPascal : Lang() { + companion object { + val fileExtensions: List + get() = listOf(("pascal")) + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + // 'single-line-string' + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^(?:\\'(?:[^\\\\\\'\\r\\n]|\\\\.)*(?:\\'|$))"), + null + ) + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^\\s+"), null + ) + + // A cStyleComments comment (* *) or {} + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^\\(\\*[\\s\\S]*?(?:\\*\\)|$)|^\\{[\\s\\S]*?(?:\\}|$)"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex( + "^(?:ABSOLUTE|AND|ARRAY|ASM|ASSEMBLER|BEGIN|CASE|CONST|CONSTRUCTOR|DESTRUCTOR|DIV|DO|DOWNTO|ELSE|END|EXTERNAL|FOR|FORWARD|FUNCTION|GOTO|IF|IMPLEMENTATION|IN|INLINE|INTERFACE|INTERRUPT|LABEL|MOD|NOT|OBJECT|OF|OR|PACKED|PROCEDURE|PROGRAM|RECORD|REPEAT|SET|SHL|SHR|THEN|TO|TYPE|UNIT|UNTIL|USES|VAR|VIRTUAL|WHILE|WITH|XOR)\\b", + RegexOption.IGNORE_CASE + ), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^(?:true|false|self|nil)", RegexOption.IGNORE_CASE), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[a-z][a-z0-9]*", RegexOption.IGNORE_CASE), + null + ) + // Literals .0, 0, 0.0 0E13 + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex( + "^(?:\\$[a-f0-9]+|(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+\\-]?\\d+)?)", + RegexOption.IGNORE_CASE + ), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^.[^\\s\\w\\.$@\\'\\/]*"), + null + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangProto.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangProto.kt new file mode 100644 index 0000000..3ad3086 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangProto.kt @@ -0,0 +1,21 @@ +// Copyright (C) 2010 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify + +/** + * It is included directly in the [Prettify]. + */ +class LangProto \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangR.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangR.kt new file mode 100644 index 0000000..864a1b4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangR.kt @@ -0,0 +1,96 @@ +// Copyright (C) 2012 Jeffrey B. Arnold +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-r.js in JavaScript Prettify. + * + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *
 code 
+ * + * + * Language definition from + * http://cran.r-project.org/doc/manuals/R-lang.html. + * Many of the regexes are shared with the pygments SLexer, + * http://pygments.org/. + * + * + * Original: https://raw.github.com/jrnold/prettify-lang-r-bugs/master/lang-r.js + * + * @author jeffrey.arnold@gmail.com + */ +class LangR : Lang() { + companion object { + val fileExtensions: List + get() = listOf("r", "s", "R", "S", "Splus") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\\t\\n\\r \\xA0]+"), null + ) + shortcutStylePatterns.new( + Prettify.PR_STRING, Regex("^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)"), null + ) + shortcutStylePatterns.new( + Prettify.PR_STRING, Regex("^\\'(?:[^\\'\\\\]|\\\\[\\s\\S])*(?:\\'|$)"), null + ) + fallthroughStylePatterns.new(Prettify.PR_COMMENT, Regex("^#.*")) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:if|else|for|while|repeat|in|next|break|return|switch|function)(?![A-Za-z0-9_.])") + ) + // hex numbes + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex("^0[xX][a-fA-F0-9]+([pP][0-9]+)?[Li]?") + ) + // Decimal numbers + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex("^[+-]?([0-9]+(\\.[0-9]+)?|\\.[0-9]+)([eE][+-]?[0-9]+)?[Li]?") + ) + // builtin symbols + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^(?:NULL|NA(?:_(?:integer|real|complex|character)_)?|Inf|TRUE|FALSE|NaN|\\.\\.(?:\\.|[0-9]+))(?![A-Za-z0-9_.])") + ) + // assignment, operators, and parens, etc. + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^(?:<>?|-|==|<=|>=|<|>|&&?|!=|\\|\\|?|\\*|\\+|\\^|\\/|!|%.*?%|=|~|\\$|@|:{1,3}|[\\[\\](){};,?])") + ) + // valid variable names + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, Regex("^(?:[A-Za-z]+[A-Za-z0-9_.]*|\\.[a-zA-Z_][0-9a-zA-Z\\._]*)(?![A-Za-z0-9_.])") + ) + // string backtick + fallthroughStylePatterns.new(Prettify.PR_STRING, Regex("^`.+`")) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangRd.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangRd.kt new file mode 100644 index 0000000..d0d04cc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangRd.kt @@ -0,0 +1,87 @@ +// Copyright (C) 2012 Jeffrey Arnold +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-rd.js in JavaScript Prettify. + * + * + * Support for R documentation (Rd) files + * + * + * Minimal highlighting or Rd files, basically just highlighting + * macros. It does not try to identify verbatim or R-like regions of + * macros as that is too complicated for a lexer. Descriptions of the + * Rd format can be found + * http://cran.r-project.org/doc/manuals/R-exts.html and + * http://developer.r-project.org/parseRd.pdf. + * + * @author Jeffrey Arnold + */ +class LangRd : Lang() { + companion object { + val fileExtensions: List + get() = listOf("Rd", "rd") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + // whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\\t\\n\\r \\xA0]+"), null + ) + // all comments begin with '%' + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^%[^\\r\\n]*"), + null + ) + + // special macros with no args + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^\\\\(?:cr|l?dots|R|tab)\\b") + ) + // macros + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^\\\\[a-zA-Z@]+") + ) + // highlighted as macros, since technically they are + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^#(?:ifn?def|endif)") + ) + // catch escaped brackets + fallthroughStylePatterns.new(Prettify.PR_PLAIN, Regex("^\\\\[{}]")) + // punctuation + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[{}()\\[\\]]+") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangScala.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangScala.kt new file mode 100644 index 0000000..2551694 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangScala.kt @@ -0,0 +1,105 @@ +// Copyright (C) 2010 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-scala.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for Scala. + * + * Derived from http://lampsvn.epfl.ch/svn-repos/scala/scala-documentation/trunk/src/reference/SyntaxSummary.tex + * + * @author mikesamuel@gmail.com + */ +class LangScala : Lang() { + companion object { + val fileExtensions: List + get() = listOf("scala") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\\t\\n\\r \\xA0]+"), null + ) + // A double or single quoted string + // or a triple double-quoted multi-line string. + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^(?:\"(?:(?:\"\"(?:\"\"?(?!\")|[^\\\\\"]|\\\\.)*\"{0,3})|(?:[^\"\\r\\n\\\\]|\\\\.)*\"?))"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_LITERAL, Regex("^`(?:[^\\r\\n\\\\`]|\\\\.)*`?"), null + ) + shortcutStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[!#%&()*+,\\-:;<=>?@\\[\\\\\\]^{|}~]+"), + null + ) + // A symbol literal is a single quote followed by an identifier with no + // single quote following + // A character literal has single quotes on either side + fallthroughStylePatterns.new( + Prettify.PR_STRING, Regex("^'(?:[^\\r\\n\\\\']|\\\\(?:'|[^\\r\\n']+))'") + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex("^'[a-zA-Z_$][\\w$]*(?!['$\\w])") + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|object|override|package|private|protected|requires|return|sealed|super|throw|trait|try|type|val|var|while|with|yield)\\b") + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex("^(?:true|false|null|this)\\b") + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex( + "^(?:(?:0(?:[0-7]+|X[0-9A-F]+))L?|(?:(?:0|[1-9][0-9]*)(?:(?:\\.[0-9]+)?(?:E[+\\-]?[0-9]+)?F?|L?))|\\\\.[0-9]+(?:E[+\\-]?[0-9]+)?F?)", + RegexOption.IGNORE_CASE + ) + ) + // Treat upper camel case identifiers as types. + fallthroughStylePatterns.new( + Prettify.PR_TYPE, Regex("^[\$_]*[A-Z][_\$A-Z0-9]*[a-z][\\w$]*") + ) + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\$a-zA-Z_][\\w$]*") + ) + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, Regex("^\\/(?:\\/.*|\\*(?:\\/|\\**[^*/])*(?:\\*+\\/?)?)") + ) + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, Regex("^(?:\\.+|\\/)") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangSql.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangSql.kt new file mode 100644 index 0000000..57c8308 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangSql.kt @@ -0,0 +1,100 @@ +// Copyright (C) 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-sql.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for SQL. + * + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *
(my SQL code)
+ * + * + * http://savage.net.au/SQL/sql-99.bnf.html is the basis for the grammar, and + * http://msdn.microsoft.com/en-us/library/aa238507(SQL.80).aspx and + * http://meta.stackoverflow.com/q/92352/137403 as the bases for the keyword + * list. + * + * @author mikesamuel@gmail.com + */ +class LangSql : Lang() { + companion object { + val fileExtensions: List + get() = listOf(("sql")) + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\\t\\n\\r \\xA0]+"), null + ) + // A double or single quoted, possibly multi-line, string. + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^(?:\"(?:[^\\\"\\\\]|\\\\.)*\"|'(?:[^\\'\\\\]|\\\\.)*')"), + null + ) + // A comment is either a line comment that starts with two dashes, or + // two dashes preceding a long bracketed block. + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^(?:--[^\\r\\n]*|\\/\\*[\\s\\S]*?(?:\\*\\/|$))") + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, Regex( + "^(?:ADD|ALL|ALTER|AND|ANY|APPLY|AS|ASC|AUTHORIZATION|BACKUP|BEGIN|BETWEEN|BREAK|BROWSE|BULK|BY|CASCADE|CASE|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMN|COMMIT|COMPUTE|CONNECT|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATABASE|DBCC|DEALLOCATE|DECLARE|DEFAULT|DELETE|DENY|DESC|DISK|DISTINCT|DISTRIBUTED|DOUBLE|DROP|DUMMY|DUMP|ELSE|END|ERRLVL|ESCAPE|EXCEPT|EXEC|EXECUTE|EXISTS|EXIT|FETCH|FILE|FILLFACTOR|FOLLOWING|FOR|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GOTO|GRANT|GROUP|HAVING|HOLDLOCK|IDENTITY|IDENTITYCOL|IDENTITY_INSERT|IF|IN|INDEX|INNER|INSERT|INTERSECT|INTO|IS|JOIN|KEY|KILL|LEFT|LIKE|LINENO|LOAD|MATCH|MERGE|NATIONAL|NOCHECK|NONCLUSTERED|NOT|NULL|NULLIF|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPENXML|OPTION|OR|ORDER|OUTER|OVER|PERCENT|PLAN|PRECEDING|PRECISION|PRIMARY|PRINT|PROC|PROCEDURE|PUBLIC|RAISERROR|READ|READTEXT|RECONFIGURE|REFERENCES|REPLICATION|RESTORE|RESTRICT|RETURN|REVOKE|RIGHT|ROLLBACK|ROWCOUNT|ROWGUIDCOL|ROWS?|RULE|SAVE|SCHEMA|SELECT|SESSION_USER|SET|SETUSER|SHUTDOWN|SOME|STATISTICS|SYSTEM_USER|TABLE|TEXTSIZE|THEN|TO|TOP|TRAN|TRANSACTION|TRIGGER|TRUNCATE|TSEQUAL|UNBOUNDED|UNION|UNIQUE|UPDATE|UPDATETEXT|USE|USER|USING|VALUES|VARYING|VIEW|WAITFOR|WHEN|WHERE|WHILE|WITH|WRITETEXT)(?=[^\\w-]|$)", + RegexOption.IGNORE_CASE + ), null + ) + // A number is a hex integer literal, a decimal real literal, or in + // scientific notation. + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex( + "^[+-]?(?:0x[\\da-f]+|(?:(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:e[+\\-]?\\d+)?))", + RegexOption.IGNORE_CASE + ) + ) + // An identifier + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[a-z_][\\w-]*", RegexOption.IGNORE_CASE) + ) + // A run of punctuation + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[^\\w\\t\\n\\r \\xA0\\\"\\'][^\\w\\t\\n\\r \\xA0+\\-\\\"\\']*") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangSwift.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangSwift.kt new file mode 100644 index 0000000..d1e36ad --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangSwift.kt @@ -0,0 +1,75 @@ +package com.wakaztahir.codeeditor.prettify.lang + + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +class LangSwift : Lang() { + + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + companion object { + val fileExtensions = listOf("swift") + } + + override fun getFileExtensions(): List = fileExtensions + + init { + shortcutStylePatterns.new( + Prettify.PR_PLAIN, + Regex("^[ \\n\\r\\t\\v\\u000c\\\u0000]+"), + null + ) + shortcutStylePatterns.new( + Prettify.PR_STRING, + Regex("^\"(?:[^\"\\\\]|(?:\\\\.)|(?:\\\\\\((?:[^\"\\\\)]|\\\\.)*\\)))*\""), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^(?:(?:0x[\\da-fA-F][\\da-fA-F_]*\\.[\\da-fA-F][\\da-fA-F_]*[pP]?)|(?:\\d[\\d_]*\\.\\d[\\d_]*[eE]?))[+-]?\\d[\\d_]*"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^-?(?:(?:0(?:(?:b[01][01_]*)|(?:o[0-7][0-7_]*)|(?:x[\\da-fA-F][\\da-fA-F_]*)))|(?:\\d[\\d_]*))"), + null + + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex("^(?:_|Any|true|false|nil)\\b"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^\\b(?:__COLUMN__|__FILE__|__FUNCTION__|__LINE__|#available|#colorLiteral|#column|#else|#elseif|#endif|#file|#fileLiteral|#function|#if|#imageLiteral|#line|#selector|#sourceLocation|arch|arm|arm64|associatedtype|associativity|as|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic|dynamicType|else|enum|extension|fallthrough|fileprivate|final|for|func|get|guard|import|indirect|infix|init|inout|internal|i386|if|in|iOS|iOSApplicationExtension|is|lazy|left|let|mutating|none|nonmutating|open|operator|optional|OSX|OSXApplicationExtension|override|postfix|precedence|prefix|private|protocol|Protocol|public|repeat|required|rethrows|return|right|safe|Self|self|set|static|struct|subscript|super|switch|throw|throws|try|Type|typealias|unowned|unsafe|var|weak|watchOS|where|while|willSet|x86_64)\\b"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^\\/\\/.*?[\\n\\r]"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^\\/\\*[\\s\\S]*?(?:\\*\\/|$)"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^<<=|<=|<<|>>=|>=|>>|===|==|\\.\\.\\.|&&=|\\.\\.<|!==|!=|&=|~=|~|\\(|\\)|\\[|\\]|\\{|}|@|#|;|\\.|,|:|\\|\\|=|\\?\\?|\\|\\||&&|&\\*|&\\+|&-|&=|\\+=|-=|\\/=|\\*=|\\^=|%=|\\|=|->|`|==|\\+\\+|--|\\/|\\+|!|\\*|%|<|>|&|\\||\\^|\\?|=|-|_"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_TYPE, + Regex("^\\b(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\\w+_t\\b)"), + null + ) + + + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangTcl.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangTcl.kt new file mode 100644 index 0000000..250cc26 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangTcl.kt @@ -0,0 +1,95 @@ +// Copyright (C) 2012 Pyrios. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-tcl.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *
proc foo {} {puts bar}
+ * + * I copy-pasted lang-lisp.js, so this is probably not 100% accurate. + * I used http://wiki.tcl.tk/1019 for the keywords, but tried to only + * include as keywords that had more impact on the program flow + * rather than providing convenience. For example, I included 'if' + * since that provides branching, but left off 'open' since that is more + * like a proc. Add more if it makes sense. + * + * @author pyrios@gmail.com + */ +class LangTcl : Lang() { + companion object { + val fileExtensions: List + get() = listOf("tcl") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + + shortcutStylePatterns.new("opn", Regex("^\\{+"), null) + shortcutStylePatterns.new("clo", Regex("^\\}+"), null) + // A line comment that starts with ; + shortcutStylePatterns.new( + Prettify.PR_COMMENT, Regex("^#[^\\r\\n]*"), null + ) + // Whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\\t\\n\\r \\xA0]+"), null + ) + // A double quoted, possibly multi-line, string. + shortcutStylePatterns.new( + Prettify.PR_STRING, Regex("^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)"), null + ) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^(?:after|append|apply|array|break|case|catch|continue|error|eval|exec|exit|expr|for|foreach|if|incr|info|proc|return|set|switch|trace|uplevel|upvar|while)\\b"), + null + ) + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex( + "^[+\\-]?(?:[0#]x[0-9a-f]+|\\d+\\/\\d+|(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:[ed][+\\-]?\\d+)?)", + RegexOption.IGNORE_CASE + ) + ) + // A single quote possibly followed by a word that optionally ends with + // = ! or ?. + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, Regex("^\\'(?:-*(?:\\w|\\\\[\\x21-\\x7e])(?:[\\w-]*|\\\\[\\x21-\\x7e])[=!?]?)?") + ) + // A word that optionally ends with = ! or ?. + fallthroughStylePatterns.new( + Prettify.PR_PLAIN, Regex("^-*(?:[a-z_]|\\\\[\\x21-\\x7e])(?:[\\w-]*|\\\\[\\x21-\\x7e])[=!?]?") + ) + // A printable non-space non-special character + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, Regex("^[^\\w\\t\\n\\r \\xA0()\\\"\\\\\\';]+") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangTex.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangTex.kt new file mode 100644 index 0000000..1b5a360 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangTex.kt @@ -0,0 +1,86 @@ +// Copyright (C) 2011 Martin S. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-tex.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Support for tex highlighting as discussed on + * [meta.tex.stackexchange.com](http://meta.tex.stackexchange.com/questions/872/text-immediate-following-double-backslashes-is-highlighted-as-macro-inside-a-code/876#876). + * + * @author Martin S. + */ +class LangTex : Lang() { + companion object { + val fileExtensions: List + get() = listOf("latex", "tex") + } + + override val fallthroughStylePatterns = ArrayList() + override val shortcutStylePatterns = ArrayList() + + init { + + + + // whitespace + shortcutStylePatterns.new( + Prettify.PR_PLAIN, Regex("^[\\t\\n\\r \\xA0]+"), null + ) + // all comments begin with '%' + shortcutStylePatterns.new( + Prettify.PR_COMMENT, + Regex("^%[^\\r\\n]*"), + null + ) + //[PR['PR_DECLARATION'], /^\\([egx]?def|(new|renew|provide)(command|environment))\b/], + // any command starting with a \ and contains + // either only letters (a-z,A-Z), '@' (internal macros) + fallthroughStylePatterns.new( + Prettify.PR_KEYWORD, + Regex("^\\\\[a-zA-Z@]+") + ) + // or contains only one character + fallthroughStylePatterns.new(Prettify.PR_KEYWORD, Regex("^\\\\.")) + // Highlight dollar for math mode and ampersam for tabular + fallthroughStylePatterns.new(Prettify.PR_TYPE, Regex("^[$&]")) + // numeric measurement values with attached units + fallthroughStylePatterns.new( + Prettify.PR_LITERAL, + Regex( + "[+-]?(?:\\.\\d+|\\d+(?:\\.\\d*)?)(cm|em|ex|in|pc|pt|bp|mm)", + RegexOption.IGNORE_CASE + ) + ) + // punctuation usually occurring within commands + fallthroughStylePatterns.new( + Prettify.PR_PUNCTUATION, + Regex("^[{}()\\[\\]=]+") + ) + + + } + + override fun getFileExtensions(): List { + return fileExtensions + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangVb.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangVb.kt new file mode 100644 index 0000000..afffff4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangVb.kt @@ -0,0 +1,122 @@ +// Copyright (C) 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.wakaztahir.codeeditor.prettify.lang + +import com.wakaztahir.codeeditor.prettify.parser.Prettify +import com.wakaztahir.codeeditor.prettify.parser.StylePattern +import com.wakaztahir.codeeditor.utils.new + +/** + * This is similar to the lang-vb.js in JavaScript Prettify. + * + * All comments are adapted from the JavaScript Prettify. + * + * + * + * Registers a language handler for various flavors of basic. + * + * + * To use, include prettify.js and this file in your HTML page. + * Then put your code in an HTML tag like + *

+ *
+ *
+ * http://msdn.microsoft.com/en-us/library/aa711638(VS.71).aspx defines the
+ * visual basic grammar lexical grammar.
+ *
+ * @author mikesamuel@gmail.com
+ */
+class LangVb : Lang() {
+    companion object {
+        val fileExtensions: List
+            get() = listOf("vb", "vbs")
+    }
+
+    override val fallthroughStylePatterns = ArrayList()
+    override val shortcutStylePatterns = ArrayList()
+
+    init {
+
+
+
+        // Whitespace
+        shortcutStylePatterns.new(
+            Prettify.PR_PLAIN,
+            Regex("^[\\t\\n\\r \\xA0\\u2028\\u2029]+"),
+            null
+        )
+        // A double quoted string with quotes escaped by doubling them.
+        // A single character can be suffixed with C.
+        shortcutStylePatterns.new(
+            Prettify.PR_STRING, Regex(
+                "^(?:[\\\"\\u201C\\u201D](?:[^\\\"\\u201C\\u201D]|[\\\"\\u201C\\u201D]{2})(?:[\\\"\\u201C\\u201D]c|$)|[\\\"\\u201C\\u201D](?:[^\\\"\\u201C\\u201D]|[\\\"\\u201C\\u201D]{2})*(?:[\\\"\\u201C\\u201D]|$))",
+                RegexOption.IGNORE_CASE
+            ), null
+        )
+        // A comment starts with a single quote and runs until the end of the line.
+        // VB6 apparently allows _ as an escape sequence for newlines though
+        // this is not a documented feature of VB.net.
+        // http://meta.stackoverflow.com/q/121497/137403
+        shortcutStylePatterns.new(
+            Prettify.PR_COMMENT,
+            Regex("^[\\'\\u2018\\u2019](?:_(?:\r\n?|[^\r]?)|[^\\r\\n_\\u2028\\u2029])*"),
+            null
+        )
+        fallthroughStylePatterns.new(
+            Prettify.PR_KEYWORD,
+            Regex(
+                "^(?:AddHandler|AddressOf|Alias|And|AndAlso|Ansi|As|Assembly|Auto|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|CBool|CByte|CChar|CDate|CDbl|CDec|Char|CInt|Class|CLng|CObj|Const|CShort|CSng|CStr|CType|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else|ElseIf|End|EndIf|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get|GetType|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|Let|Lib|Like|Long|Loop|Me|Mod|Module|MustInherit|MustOverride|MyBase|MyClass|Namespace|New|Next|Not|NotInheritable|NotOverridable|Object|On|Option|Optional|Or|OrElse|Overloads|Overridable|Overrides|ParamArray|Preserve|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|Select|Set|Shadows|Shared|Short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TypeOf|Unicode|Until|Variant|Wend|When|While|With|WithEvents|WriteOnly|Xor|EndIf|GoSub|Let|Variant|Wend)\\b",
+                RegexOption.IGNORE_CASE
+            ),
+            null
+        )
+        // A second comment form
+        fallthroughStylePatterns.new(
+            Prettify.PR_COMMENT,
+            Regex("^REM\\b[^\\r\\n\\u2028\\u2029]*", RegexOption.IGNORE_CASE)
+        )
+        // A boolean, numeric, or date literal.
+        fallthroughStylePatterns.new(
+            Prettify.PR_LITERAL,
+            Regex(
+                "^(?:True\\b|False\\b|Nothing\\b|\\d+(?:E[+\\-]?\\d+[FRD]?|[FRDSIL])?|(?:&H[0-9A-F]+|&O[0-7]+)[SIL]?|\\d*\\.\\d+(?:E[+\\-]?\\d+)?[FRD]?|#\\s+(?:\\d+[\\-\\/]\\d+[\\-\\/]\\d+(?:\\s+\\d+:\\d+(?::\\d+)?(\\s*(?:AM|PM))?)?|\\d+:\\d+(?::\\d+)?(\\s*(?:AM|PM))?)\\s+#)",
+                RegexOption.IGNORE_CASE
+            )
+        )
+        // An identifier.  Keywords can be turned into identifers
+        // with square brackets, and there may be optional type
+        // characters after a normal identifier in square brackets.
+        fallthroughStylePatterns.new(
+            Prettify.PR_PLAIN, Regex(
+                "^(?:(?:[a-z]|_\\w)\\w*(?:\\[[%&@!#]+\\])?|\\[(?:[a-z]|_\\w)\\w*\\])",
+                RegexOption.IGNORE_CASE
+            )
+        )
+        // A run of punctuation
+        fallthroughStylePatterns.new(
+            Prettify.PR_PUNCTUATION,
+            Regex("^[^\\w\\t\\n\\r \\\"\\'\\[\\]\\xA0\\u2018\\u2019\\u201C\\u201D\\u2028\\u2029]+")
+        )
+        // Square brackets
+        fallthroughStylePatterns.new(
+            Prettify.PR_PUNCTUATION, Regex("^(?:\\[|\\])")
+        )
+
+
+    }
+
+    override fun getFileExtensions(): List {
+        return fileExtensions
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangVhdl.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangVhdl.kt
new file mode 100644
index 0000000..aa3cff4
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangVhdl.kt
@@ -0,0 +1,101 @@
+// Copyright (C) 2010 benoit@ryder.fr
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.wakaztahir.codeeditor.prettify.lang
+
+import com.wakaztahir.codeeditor.prettify.parser.Prettify
+import com.wakaztahir.codeeditor.prettify.parser.StylePattern
+import com.wakaztahir.codeeditor.utils.new
+
+/**
+ * This is similar to the lang-vhdl.js in JavaScript Prettify.
+ *
+ * All comments are adapted from the JavaScript Prettify.
+ *
+ *
+ *
+ * Registers a language handler for VHDL '93.
+ *
+ * Based on the lexical grammar and keywords at
+ * http://www.iis.ee.ethz.ch/~zimmi/download/vhdl93_syntax.html
+ *
+ * @author benoit@ryder.fr
+ */
+class LangVhdl : Lang() {
+    companion object {
+        val fileExtensions: List
+            get() = listOf("vhdl", "vhd")
+    }
+
+    override val fallthroughStylePatterns = ArrayList()
+    override val shortcutStylePatterns = ArrayList()
+
+    init {
+
+
+
+        // Whitespace
+        shortcutStylePatterns.new(
+            Prettify.PR_PLAIN, Regex("^[\\t\\n\\r \\xA0]+"), null
+        )
+        // String, character or bit string
+        fallthroughStylePatterns.new(
+            Prettify.PR_STRING, Regex("^(?:[BOX]?\"(?:[^\\\"]|\"\")*\"|'.')", RegexOption.IGNORE_CASE)
+        )
+        // Comment, from two dashes until end of line.
+        fallthroughStylePatterns.new(
+            Prettify.PR_COMMENT, Regex("^--[^\\r\\n]*")
+        )
+        fallthroughStylePatterns.new(
+            Prettify.PR_KEYWORD, Regex(
+                "^(?:abs|access|after|alias|all|and|architecture|array|assert|attribute|begin|block|body|buffer|bus|case|component|configuration|constant|disconnect|downto|else|elsif|end|entity|exit|file|for|function|generate|generic|group|guarded|if|impure|in|inertial|inout|is|label|library|linkage|literal|loop|map|mod|nand|new|next|nor|not|null|of|on|open|or|others|out|package|port|postponed|procedure|process|pure|range|record|register|reject|rem|report|return|rol|ror|select|severity|shared|signal|sla|sll|sra|srl|subtype|then|to|transport|type|unaffected|units|until|use|variable|wait|when|while|with|xnor|xor)(?=[^\\w-]|$)",
+                RegexOption.IGNORE_CASE
+            ), null
+        )
+        // Type, predefined or standard
+        fallthroughStylePatterns.new(
+            Prettify.PR_TYPE, Regex(
+                "^(?:bit|bit_vector|character|boolean|integer|real|time|string|severity_level|positive|natural|signed|unsigned|line|text|std_u?logic(?:_vector)?)(?=[^\\w-]|$)",
+                RegexOption.IGNORE_CASE
+            ), null
+        )
+        // Predefined attributes
+        fallthroughStylePatterns.new(
+            Prettify.PR_TYPE, Regex(
+                "^\\'(?:ACTIVE|ASCENDING|BASE|DELAYED|DRIVING|DRIVING_VALUE|EVENT|HIGH|IMAGE|INSTANCE_NAME|LAST_ACTIVE|LAST_EVENT|LAST_VALUE|LEFT|LEFTOF|LENGTH|LOW|PATH_NAME|POS|PRED|QUIET|RANGE|REVERSE_RANGE|RIGHT|RIGHTOF|SIMPLE_NAME|STABLE|SUCC|TRANSACTION|VAL|VALUE)(?=[^\\w-]|$)",
+                RegexOption.IGNORE_CASE
+            ), null
+        )
+        // Number, decimal or based literal
+        fallthroughStylePatterns.new(
+            Prettify.PR_LITERAL, Regex(
+                "^\\d+(?:_\\d+)*(?:#[\\w\\\\.]+#(?:[+\\-]?\\d+(?:_\\d+)*)?|(?:\\.\\d+(?:_\\d+)*)?(?:E[+\\-]?\\d+(?:_\\d+)*)?)",
+                RegexOption.IGNORE_CASE
+            )
+        )
+        // Identifier, basic or extended
+        fallthroughStylePatterns.new(
+            Prettify.PR_PLAIN, Regex("^(?:[a-z]\\w*|\\\\[^\\\\]*\\\\)", RegexOption.IGNORE_CASE)
+        )
+        // Punctuation
+        fallthroughStylePatterns.new(
+            Prettify.PR_PUNCTUATION, Regex("^[^\\w\\t\\n\\r \\xA0\\\"\\'][^\\w\\t\\n\\r \\xA0\\-\\\"\\']*")
+        )
+
+
+    }
+
+    override fun getFileExtensions(): List {
+        return fileExtensions
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangWiki.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangWiki.kt
new file mode 100644
index 0000000..61d2b52
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangWiki.kt
@@ -0,0 +1,112 @@
+// Copyright (C) 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.wakaztahir.codeeditor.prettify.lang
+
+import com.wakaztahir.codeeditor.prettify.parser.Prettify
+import com.wakaztahir.codeeditor.prettify.parser.StylePattern
+import com.wakaztahir.codeeditor.utils.new
+
+/**
+ * This is similar to the lang-wiki.js in JavaScript Prettify.
+ *
+ * All comments are adapted from the JavaScript Prettify.
+ *
+ *
+ *
+ * Registers a language handler for Wiki pages.
+ *
+ * Based on WikiSyntax at http://code.google.com/p/support/wiki/WikiSyntax
+ *
+ * @author mikesamuel@gmail.com
+ */
+class LangWiki : Lang() {
+
+    override val fallthroughStylePatterns = ArrayList()
+    override val shortcutStylePatterns = ArrayList()
+
+
+    companion object {
+        val fileExtensions: List
+            get() = listOf(("wiki"))
+    }
+
+    init {
+        // Whitespace
+        shortcutStylePatterns.new(
+            Prettify.PR_PLAIN,
+            Regex("^[\\t \\xA0a-gi-z0-9]+"),
+            null
+        )
+        // Wiki formatting
+        shortcutStylePatterns.new(
+            Prettify.PR_PUNCTUATION,
+            Regex("^[=*~\\^\\[\\]]+"),
+            null
+        )
+        // Meta-info like #summary, #labels, etc.
+        fallthroughStylePatterns.new(
+            "lang-wiki.meta",
+            Regex("(?:^^|\r\n?|\n)(#[a-z]+)\\b")
+        )
+        // A WikiWord
+        fallthroughStylePatterns.new(
+            Prettify.PR_LITERAL,
+            Regex("^(?:[A-Z][a-z][a-z0-9]+[A-Z][a-z][a-zA-Z0-9]+)\\b")
+        )
+        // A preformatted block in an unknown language
+        fallthroughStylePatterns.new(
+            "lang-",
+            Regex("^\\{\\{\\{([\\s\\S]+?)\\}\\}\\}")
+        )
+        // A block of source code in an unknown language
+        fallthroughStylePatterns.new("lang-", Regex("^`([^\r\n`]+)`"))
+        // An inline URL.
+        fallthroughStylePatterns.new(
+            Prettify.PR_STRING,
+            Regex(
+                "^https?:\\/\\/[^\\/?#\\s]*(?:\\/[^?#\\s]*)?(?:\\?[^#\\s]*)?(?:#\\S*)?",
+                RegexOption.IGNORE_CASE
+            )
+        )
+        fallthroughStylePatterns.new(
+            Prettify.PR_PLAIN,
+            Regex("^(?:\r\n|[\\s\\S])[^#=*~^A-Zh\\{`\\[\r\n]*")
+        )
+
+    }
+
+    override fun getFileExtensions(): List = fileExtensions
+
+    internal class LangWikiMeta : Lang() {
+        companion object {
+            val fileExtensions: List
+                get() = listOf(("wiki.meta"))
+        }
+
+        override val fallthroughStylePatterns = ArrayList()
+        override val shortcutStylePatterns = ArrayList()
+
+        init {
+            shortcutStylePatterns.new(
+                Prettify.PR_KEYWORD,
+                Regex("^#[a-z]+", RegexOption.IGNORE_CASE),
+                null
+            )
+        }
+
+        override fun getFileExtensions(): List {
+            return fileExtensions
+        }
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangXq.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangXq.kt
new file mode 100644
index 0000000..97ab805
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangXq.kt
@@ -0,0 +1,113 @@
+// Copyright (C) 2011 Patrick Wied
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.wakaztahir.codeeditor.prettify.lang
+
+import com.wakaztahir.codeeditor.prettify.parser.Prettify
+import com.wakaztahir.codeeditor.prettify.parser.StylePattern
+import com.wakaztahir.codeeditor.utils.new
+
+/**
+ * This is similar to the lang-xq.js in JavaScript Prettify.
+ *
+ * All comments are adapted from the JavaScript Prettify.
+ *
+ *
+ *
+ * Registers a language handler for XQuery.
+ *
+ * To use, include prettify.js and this file in your HTML page.
+ * Then put your code in an HTML tag like
+ * 

+ *
+ *
+ * @author Patrick Wied ( patpa7p@live.de )
+ * @version 2010-09-28
+ */
+class LangXq : Lang() {
+    companion object {
+        const val PR_FUNCTION = "fun"
+        const val PR_VARIABLE = "var"
+        val fileExtensions: List
+            get() = listOf("xq", "xquery")
+    }
+
+    override val fallthroughStylePatterns = ArrayList()
+    override val shortcutStylePatterns = ArrayList()
+
+    init {
+
+
+
+        // Matching $var-ia_bles
+        shortcutStylePatterns.new(
+            PR_VARIABLE, Regex("^\\$[A-Za-z0-9_\\-]+"), null
+        )
+        // Matching lt and gt operators
+        // Not the best matching solution but you have to differentiate between the gt operator and the tag closing char
+        fallthroughStylePatterns.new(
+            Prettify.PR_PLAIN, Regex("^[\\s=][<>][\\s=]")
+        )
+        // Matching @Attributes
+        fallthroughStylePatterns.new(Prettify.PR_LITERAL, Regex("^\\@[\\w-]+"))
+        // Matching xml tags
+        fallthroughStylePatterns.new(
+            Prettify.PR_TAG, Regex("^<\\/?[a-z](?:[\\w.:-]*\\w)?|\\/?>$", RegexOption.IGNORE_CASE)
+        )
+        // Matching single or multiline xquery comments -> (:  :)
+        fallthroughStylePatterns.new(
+            Prettify.PR_COMMENT, Regex("^\\(:[\\s\\S]*?:\\)")
+        )
+        // Tokenizing /{}:=;*,[]() as plain
+        fallthroughStylePatterns.new(
+            Prettify.PR_PLAIN, Regex("^[\\/\\{\\};,\\[\\]\\(\\)]$")
+        )
+        // Matching a double or single quoted, possibly multi-line, string.
+        // with the special condition that a { in a string changes to xquery context 
+        fallthroughStylePatterns.new(
+            Prettify.PR_STRING, Regex(
+                "^(?:\\\"(?:[^\\\"\\\\\\{]|\\\\[\\s\\S])*(?:\\\"|$)|\\'(?:[^\\'\\\\\\{]|\\\\[\\s\\S])*(?:\\'|$))",
+                RegexOption.IGNORE_CASE
+            ), null
+        )
+        // Matching standard xquery keywords
+        fallthroughStylePatterns.new(
+            Prettify.PR_KEYWORD,
+            Regex("^(?:xquery|where|version|variable|union|typeswitch|treat|to|then|text|stable|sortby|some|self|schema|satisfies|returns|return|ref|processing-instruction|preceding-sibling|preceding|precedes|parent|only|of|node|namespace|module|let|item|intersect|instance|in|import|if|function|for|follows|following-sibling|following|external|except|every|else|element|descending|descendant-or-self|descendant|define|default|declare|comment|child|cast|case|before|attribute|assert|ascending|as|ancestor-or-self|ancestor|after|eq|order|by|or|and|schema-element|document-node|node|at)\\b")
+        )
+        // Matching standard xquery types
+        fallthroughStylePatterns.new(
+            Prettify.PR_TYPE,
+            Regex("^(?:xs:yearMonthDuration|xs:unsignedLong|xs:time|xs:string|xs:short|xs:QName|xs:Name|xs:long|xs:integer|xs:int|xs:gYearMonth|xs:gYear|xs:gMonthDay|xs:gDay|xs:float|xs:duration|xs:double|xs:decimal|xs:dayTimeDuration|xs:dateTime|xs:date|xs:byte|xs:boolean|xs:anyURI|xf:yearMonthDuration)\\b"),
+            null
+        )
+        // Matching standard xquery functions
+        fallthroughStylePatterns.new(
+            PR_FUNCTION, Regex(
+                "^(?:xp:dereference|xinc:node-expand|xinc:link-references|xinc:link-expand|xhtml:restructure|xhtml:clean|xhtml:add-lists|xdmp:zip-manifest|xdmp:zip-get|xdmp:zip-create|xdmp:xquery-version|xdmp:word-convert|xdmp:with-namespaces|xdmp:version|xdmp:value|xdmp:user-roles|xdmp:user-last-login|xdmp:user|xdmp:url-encode|xdmp:url-decode|xdmp:uri-is-file|xdmp:uri-format|xdmp:uri-content-type|xdmp:unquote|xdmp:unpath|xdmp:triggers-database|xdmp:trace|xdmp:to-json|xdmp:tidy|xdmp:subbinary|xdmp:strftime|xdmp:spawn-in|xdmp:spawn|xdmp:sleep|xdmp:shutdown|xdmp:set-session-field|xdmp:set-response-encoding|xdmp:set-response-content-type|xdmp:set-response-code|xdmp:set-request-time-limit|xdmp:set|xdmp:servers|xdmp:server-status|xdmp:server-name|xdmp:server|xdmp:security-database|xdmp:security-assert|xdmp:schema-database|xdmp:save|xdmp:role-roles|xdmp:role|xdmp:rethrow|xdmp:restart|xdmp:request-timestamp|xdmp:request-status|xdmp:request-cancel|xdmp:request|xdmp:redirect-response|xdmp:random|xdmp:quote|xdmp:query-trace|xdmp:query-meters|xdmp:product-edition|xdmp:privilege-roles|xdmp:privilege|xdmp:pretty-print|xdmp:powerpoint-convert|xdmp:platform|xdmp:permission|xdmp:pdf-convert|xdmp:path|xdmp:octal-to-integer|xdmp:node-uri|xdmp:node-replace|xdmp:node-kind|xdmp:node-insert-child|xdmp:node-insert-before|xdmp:node-insert-after|xdmp:node-delete|xdmp:node-database|xdmp:mul64|xdmp:modules-root|xdmp:modules-database|xdmp:merging|xdmp:merge-cancel|xdmp:merge|xdmp:md5|xdmp:logout|xdmp:login|xdmp:log-level|xdmp:log|xdmp:lock-release|xdmp:lock-acquire|xdmp:load|xdmp:invoke-in|xdmp:invoke|xdmp:integer-to-octal|xdmp:integer-to-hex|xdmp:http-put|xdmp:http-post|xdmp:http-options|xdmp:http-head|xdmp:http-get|xdmp:http-delete|xdmp:hosts|xdmp:host-status|xdmp:host-name|xdmp:host|xdmp:hex-to-integer|xdmp:hash64|xdmp:hash32|xdmp:has-privilege|xdmp:groups|xdmp:group-serves|xdmp:group-servers|xdmp:group-name|xdmp:group-hosts|xdmp:group|xdmp:get-session-field-names|xdmp:get-session-field|xdmp:get-response-encoding|xdmp:get-response-code|xdmp:get-request-username|xdmp:get-request-user|xdmp:get-request-url|xdmp:get-request-protocol|xdmp:get-request-path|xdmp:get-request-method|xdmp:get-request-header-names|xdmp:get-request-header|xdmp:get-request-field-names|xdmp:get-request-field-filename|xdmp:get-request-field-content-type|xdmp:get-request-field|xdmp:get-request-client-certificate|xdmp:get-request-client-address|xdmp:get-request-body|xdmp:get-current-user|xdmp:get-current-roles|xdmp:get|xdmp:function-name|xdmp:function-module|xdmp:function|xdmp:from-json|xdmp:forests|xdmp:forest-status|xdmp:forest-restore|xdmp:forest-restart|xdmp:forest-name|xdmp:forest-delete|xdmp:forest-databases|xdmp:forest-counts|xdmp:forest-clear|xdmp:forest-backup|xdmp:forest|xdmp:filesystem-file|xdmp:filesystem-directory|xdmp:exists|xdmp:excel-convert|xdmp:eval-in|xdmp:eval|xdmp:estimate|xdmp:email|xdmp:element-content-type|xdmp:elapsed-time|xdmp:document-set-quality|xdmp:document-set-property|xdmp:document-set-properties|xdmp:document-set-permissions|xdmp:document-set-collections|xdmp:document-remove-properties|xdmp:document-remove-permissions|xdmp:document-remove-collections|xdmp:document-properties|xdmp:document-locks|xdmp:document-load|xdmp:document-insert|xdmp:document-get-quality|xdmp:document-get-properties|xdmp:document-get-permissions|xdmp:document-get-collections|xdmp:document-get|xdmp:document-forest|xdmp:document-delete|xdmp:document-add-properties|xdmp:document-add-permissions|xdmp:document-add-collections|xdmp:directory-properties|xdmp:directory-locks|xdmp:directory-delete|xdmp:directory-create|xdmp:directory|xdmp:diacritic-less|xdmp:describe|xdmp:default-permissions|xdmp:default-collections|xdmp:databases|xdmp:database-restore-validate|xdmp:database-restore-status|xdmp:database-restore-cancel|xdmp:database-restore|xdmp:database-name|xdmp:database-forests|xdmp:database-backup-validate|xdmp:database-backup-status|xdmp:database-backup-purge|xdmp:database-backup-cancel|xdmp:database-backup|xdmp:database|xdmp:collection-properties|xdmp:collection-locks|xdmp:collection-delete|xdmp:collation-canonical-uri|xdmp:castable-as|xdmp:can-grant-roles|xdmp:base64-encode|xdmp:base64-decode|xdmp:architecture|xdmp:apply|xdmp:amp-roles|xdmp:amp|xdmp:add64|xdmp:add-response-header|xdmp:access|trgr:trigger-set-recursive|trgr:trigger-set-permissions|trgr:trigger-set-name|trgr:trigger-set-module|trgr:trigger-set-event|trgr:trigger-set-description|trgr:trigger-remove-permissions|trgr:trigger-module|trgr:trigger-get-permissions|trgr:trigger-enable|trgr:trigger-disable|trgr:trigger-database-online-event|trgr:trigger-data-event|trgr:trigger-add-permissions|trgr:remove-trigger|trgr:property-content|trgr:pre-commit|trgr:post-commit|trgr:get-trigger-by-id|trgr:get-trigger|trgr:document-scope|trgr:document-content|trgr:directory-scope|trgr:create-trigger|trgr:collection-scope|trgr:any-property-content|thsr:set-entry|thsr:remove-term|thsr:remove-synonym|thsr:remove-entry|thsr:query-lookup|thsr:lookup|thsr:load|thsr:insert|thsr:expand|thsr:add-synonym|spell:suggest-detailed|spell:suggest|spell:remove-word|spell:make-dictionary|spell:load|spell:levenshtein-distance|spell:is-correct|spell:insert|spell:double-metaphone|spell:add-word|sec:users-collection|sec:user-set-roles|sec:user-set-password|sec:user-set-name|sec:user-set-description|sec:user-set-default-permissions|sec:user-set-default-collections|sec:user-remove-roles|sec:user-privileges|sec:user-get-roles|sec:user-get-description|sec:user-get-default-permissions|sec:user-get-default-collections|sec:user-doc-permissions|sec:user-doc-collections|sec:user-add-roles|sec:unprotect-collection|sec:uid-for-name|sec:set-realm|sec:security-version|sec:security-namespace|sec:security-installed|sec:security-collection|sec:roles-collection|sec:role-set-roles|sec:role-set-name|sec:role-set-description|sec:role-set-default-permissions|sec:role-set-default-collections|sec:role-remove-roles|sec:role-privileges|sec:role-get-roles|sec:role-get-description|sec:role-get-default-permissions|sec:role-get-default-collections|sec:role-doc-permissions|sec:role-doc-collections|sec:role-add-roles|sec:remove-user|sec:remove-role-from-users|sec:remove-role-from-role|sec:remove-role-from-privileges|sec:remove-role-from-amps|sec:remove-role|sec:remove-privilege|sec:remove-amp|sec:protect-collection|sec:privileges-collection|sec:privilege-set-roles|sec:privilege-set-name|sec:privilege-remove-roles|sec:privilege-get-roles|sec:privilege-add-roles|sec:priv-doc-permissions|sec:priv-doc-collections|sec:get-user-names|sec:get-unique-elem-id|sec:get-role-names|sec:get-role-ids|sec:get-privilege|sec:get-distinct-permissions|sec:get-collection|sec:get-amp|sec:create-user-with-role|sec:create-user|sec:create-role|sec:create-privilege|sec:create-amp|sec:collections-collection|sec:collection-set-permissions|sec:collection-remove-permissions|sec:collection-get-permissions|sec:collection-add-permissions|sec:check-admin|sec:amps-collection|sec:amp-set-roles|sec:amp-remove-roles|sec:amp-get-roles|sec:amp-doc-permissions|sec:amp-doc-collections|sec:amp-add-roles|search:unparse|search:suggest|search:snippet|search:search|search:resolve-nodes|search:resolve|search:remove-constraint|search:parse|search:get-default-options|search:estimate|search:check-options|prof:value|prof:reset|prof:report|prof:invoke|prof:eval|prof:enable|prof:disable|prof:allowed|ppt:clean|pki:template-set-request|pki:template-set-name|pki:template-set-key-type|pki:template-set-key-options|pki:template-set-description|pki:template-in-use|pki:template-get-version|pki:template-get-request|pki:template-get-name|pki:template-get-key-type|pki:template-get-key-options|pki:template-get-id|pki:template-get-description|pki:need-certificate|pki:is-temporary|pki:insert-trusted-certificates|pki:insert-template|pki:insert-signed-certificates|pki:insert-certificate-revocation-list|pki:get-trusted-certificate-ids|pki:get-template-ids|pki:get-template-certificate-authority|pki:get-template-by-name|pki:get-template|pki:get-pending-certificate-requests-xml|pki:get-pending-certificate-requests-pem|pki:get-pending-certificate-request|pki:get-certificates-for-template-xml|pki:get-certificates-for-template|pki:get-certificates|pki:get-certificate-xml|pki:get-certificate-pem|pki:get-certificate|pki:generate-temporary-certificate-if-necessary|pki:generate-temporary-certificate|pki:generate-template-certificate-authority|pki:generate-certificate-request|pki:delete-template|pki:delete-certificate|pki:create-template|pdf:make-toc|pdf:insert-toc-headers|pdf:get-toc|pdf:clean|p:status-transition|p:state-transition|p:remove|p:pipelines|p:insert|p:get-by-id|p:get|p:execute|p:create|p:condition|p:collection|p:action|ooxml:runs-merge|ooxml:package-uris|ooxml:package-parts-insert|ooxml:package-parts|msword:clean|mcgm:polygon|mcgm:point|mcgm:geospatial-query-from-elements|mcgm:geospatial-query|mcgm:circle|math:tanh|math:tan|math:sqrt|math:sinh|math:sin|math:pow|math:modf|math:log10|math:log|math:ldexp|math:frexp|math:fmod|math:floor|math:fabs|math:exp|math:cosh|math:cos|math:ceil|math:atan2|math:atan|math:asin|math:acos|map:put|map:map|map:keys|map:get|map:delete|map:count|map:clear|lnk:to|lnk:remove|lnk:insert|lnk:get|lnk:from|lnk:create|kml:polygon|kml:point|kml:interior-polygon|kml:geospatial-query-from-elements|kml:geospatial-query|kml:circle|kml:box|gml:polygon|gml:point|gml:interior-polygon|gml:geospatial-query-from-elements|gml:geospatial-query|gml:circle|gml:box|georss:point|georss:geospatial-query|georss:circle|geo:polygon|geo:point|geo:interior-polygon|geo:geospatial-query-from-elements|geo:geospatial-query|geo:circle|geo:box|fn:zero-or-one|fn:years-from-duration|fn:year-from-dateTime|fn:year-from-date|fn:upper-case|fn:unordered|fn:true|fn:translate|fn:trace|fn:tokenize|fn:timezone-from-time|fn:timezone-from-dateTime|fn:timezone-from-date|fn:sum|fn:subtract-dateTimes-yielding-yearMonthDuration|fn:subtract-dateTimes-yielding-dayTimeDuration|fn:substring-before|fn:substring-after|fn:substring|fn:subsequence|fn:string-to-codepoints|fn:string-pad|fn:string-length|fn:string-join|fn:string|fn:static-base-uri|fn:starts-with|fn:seconds-from-time|fn:seconds-from-duration|fn:seconds-from-dateTime|fn:round-half-to-even|fn:round|fn:root|fn:reverse|fn:resolve-uri|fn:resolve-QName|fn:replace|fn:remove|fn:QName|fn:prefix-from-QName|fn:position|fn:one-or-more|fn:number|fn:not|fn:normalize-unicode|fn:normalize-space|fn:node-name|fn:node-kind|fn:nilled|fn:namespace-uri-from-QName|fn:namespace-uri-for-prefix|fn:namespace-uri|fn:name|fn:months-from-duration|fn:month-from-dateTime|fn:month-from-date|fn:minutes-from-time|fn:minutes-from-duration|fn:minutes-from-dateTime|fn:min|fn:max|fn:matches|fn:lower-case|fn:local-name-from-QName|fn:local-name|fn:last|fn:lang|fn:iri-to-uri|fn:insert-before|fn:index-of|fn:in-scope-prefixes|fn:implicit-timezone|fn:idref|fn:id|fn:hours-from-time|fn:hours-from-duration|fn:hours-from-dateTime|fn:floor|fn:false|fn:expanded-QName|fn:exists|fn:exactly-one|fn:escape-uri|fn:escape-html-uri|fn:error|fn:ends-with|fn:encode-for-uri|fn:empty|fn:document-uri|fn:doc-available|fn:doc|fn:distinct-values|fn:distinct-nodes|fn:default-collation|fn:deep-equal|fn:days-from-duration|fn:day-from-dateTime|fn:day-from-date|fn:data|fn:current-time|fn:current-dateTime|fn:current-date|fn:count|fn:contains|fn:concat|fn:compare|fn:collection|fn:codepoints-to-string|fn:codepoint-equal|fn:ceiling|fn:boolean|fn:base-uri|fn:avg|fn:adjust-time-to-timezone|fn:adjust-dateTime-to-timezone|fn:adjust-date-to-timezone|fn:abs|feed:unsubscribe|feed:subscription|feed:subscribe|feed:request|feed:item|feed:description|excel:clean|entity:enrich|dom:set-pipelines|dom:set-permissions|dom:set-name|dom:set-evaluation-context|dom:set-domain-scope|dom:set-description|dom:remove-pipeline|dom:remove-permissions|dom:remove|dom:get|dom:evaluation-context|dom:domains|dom:domain-scope|dom:create|dom:configuration-set-restart-user|dom:configuration-set-permissions|dom:configuration-set-evaluation-context|dom:configuration-set-default-domain|dom:configuration-get|dom:configuration-create|dom:collection|dom:add-pipeline|dom:add-permissions|dls:retention-rules|dls:retention-rule-remove|dls:retention-rule-insert|dls:retention-rule|dls:purge|dls:node-expand|dls:link-references|dls:link-expand|dls:documents-query|dls:document-versions-query|dls:document-version-uri|dls:document-version-query|dls:document-version-delete|dls:document-version-as-of|dls:document-version|dls:document-update|dls:document-unmanage|dls:document-set-quality|dls:document-set-property|dls:document-set-properties|dls:document-set-permissions|dls:document-set-collections|dls:document-retention-rules|dls:document-remove-properties|dls:document-remove-permissions|dls:document-remove-collections|dls:document-purge|dls:document-manage|dls:document-is-managed|dls:document-insert-and-manage|dls:document-include-query|dls:document-history|dls:document-get-permissions|dls:document-extract-part|dls:document-delete|dls:document-checkout-status|dls:document-checkout|dls:document-checkin|dls:document-add-properties|dls:document-add-permissions|dls:document-add-collections|dls:break-checkout|dls:author-query|dls:as-of-query|dbk:convert|dbg:wait|dbg:value|dbg:stopped|dbg:stop|dbg:step|dbg:status|dbg:stack|dbg:out|dbg:next|dbg:line|dbg:invoke|dbg:function|dbg:finish|dbg:expr|dbg:eval|dbg:disconnect|dbg:detach|dbg:continue|dbg:connect|dbg:clear|dbg:breakpoints|dbg:break|dbg:attached|dbg:attach|cvt:save-converted-documents|cvt:part-uri|cvt:destination-uri|cvt:basepath|cvt:basename|cts:words|cts:word-query-weight|cts:word-query-text|cts:word-query-options|cts:word-query|cts:word-match|cts:walk|cts:uris|cts:uri-match|cts:train|cts:tokenize|cts:thresholds|cts:stem|cts:similar-query-weight|cts:similar-query-nodes|cts:similar-query|cts:shortest-distance|cts:search|cts:score|cts:reverse-query-weight|cts:reverse-query-nodes|cts:reverse-query|cts:remainder|cts:registered-query-weight|cts:registered-query-options|cts:registered-query-ids|cts:registered-query|cts:register|cts:query|cts:quality|cts:properties-query-query|cts:properties-query|cts:polygon-vertices|cts:polygon|cts:point-longitude|cts:point-latitude|cts:point|cts:or-query-queries|cts:or-query|cts:not-query-weight|cts:not-query-query|cts:not-query|cts:near-query-weight|cts:near-query-queries|cts:near-query-options|cts:near-query-distance|cts:near-query|cts:highlight|cts:geospatial-co-occurrences|cts:frequency|cts:fitness|cts:field-words|cts:field-word-query-weight|cts:field-word-query-text|cts:field-word-query-options|cts:field-word-query-field-name|cts:field-word-query|cts:field-word-match|cts:entity-highlight|cts:element-words|cts:element-word-query-weight|cts:element-word-query-text|cts:element-word-query-options|cts:element-word-query-element-name|cts:element-word-query|cts:element-word-match|cts:element-values|cts:element-value-ranges|cts:element-value-query-weight|cts:element-value-query-text|cts:element-value-query-options|cts:element-value-query-element-name|cts:element-value-query|cts:element-value-match|cts:element-value-geospatial-co-occurrences|cts:element-value-co-occurrences|cts:element-range-query-weight|cts:element-range-query-value|cts:element-range-query-options|cts:element-range-query-operator|cts:element-range-query-element-name|cts:element-range-query|cts:element-query-query|cts:element-query-element-name|cts:element-query|cts:element-pair-geospatial-values|cts:element-pair-geospatial-value-match|cts:element-pair-geospatial-query-weight|cts:element-pair-geospatial-query-region|cts:element-pair-geospatial-query-options|cts:element-pair-geospatial-query-longitude-name|cts:element-pair-geospatial-query-latitude-name|cts:element-pair-geospatial-query-element-name|cts:element-pair-geospatial-query|cts:element-pair-geospatial-boxes|cts:element-geospatial-values|cts:element-geospatial-value-match|cts:element-geospatial-query-weight|cts:element-geospatial-query-region|cts:element-geospatial-query-options|cts:element-geospatial-query-element-name|cts:element-geospatial-query|cts:element-geospatial-boxes|cts:element-child-geospatial-values|cts:element-child-geospatial-value-match|cts:element-child-geospatial-query-weight|cts:element-child-geospatial-query-region|cts:element-child-geospatial-query-options|cts:element-child-geospatial-query-element-name|cts:element-child-geospatial-query-child-name|cts:element-child-geospatial-query|cts:element-child-geospatial-boxes|cts:element-attribute-words|cts:element-attribute-word-query-weight|cts:element-attribute-word-query-text|cts:element-attribute-word-query-options|cts:element-attribute-word-query-element-name|cts:element-attribute-word-query-attribute-name|cts:element-attribute-word-query|cts:element-attribute-word-match|cts:element-attribute-values|cts:element-attribute-value-ranges|cts:element-attribute-value-query-weight|cts:element-attribute-value-query-text|cts:element-attribute-value-query-options|cts:element-attribute-value-query-element-name|cts:element-attribute-value-query-attribute-name|cts:element-attribute-value-query|cts:element-attribute-value-match|cts:element-attribute-value-geospatial-co-occurrences|cts:element-attribute-value-co-occurrences|cts:element-attribute-range-query-weight|cts:element-attribute-range-query-value|cts:element-attribute-range-query-options|cts:element-attribute-range-query-operator|cts:element-attribute-range-query-element-name|cts:element-attribute-range-query-attribute-name|cts:element-attribute-range-query|cts:element-attribute-pair-geospatial-values|cts:element-attribute-pair-geospatial-value-match|cts:element-attribute-pair-geospatial-query-weight|cts:element-attribute-pair-geospatial-query-region|cts:element-attribute-pair-geospatial-query-options|cts:element-attribute-pair-geospatial-query-longitude-name|cts:element-attribute-pair-geospatial-query-latitude-name|cts:element-attribute-pair-geospatial-query-element-name|cts:element-attribute-pair-geospatial-query|cts:element-attribute-pair-geospatial-boxes|cts:document-query-uris|cts:document-query|cts:distance|cts:directory-query-uris|cts:directory-query-depth|cts:directory-query|cts:destination|cts:deregister|cts:contains|cts:confidence|cts:collections|cts:collection-query-uris|cts:collection-query|cts:collection-match|cts:classify|cts:circle-radius|cts:circle-center|cts:circle|cts:box-west|cts:box-south|cts:box-north|cts:box-east|cts:box|cts:bearing|cts:arc-intersection|cts:and-query-queries|cts:and-query-options|cts:and-query|cts:and-not-query-positive-query|cts:and-not-query-negative-query|cts:and-not-query|css:get|css:convert|cpf:success|cpf:failure|cpf:document-set-state|cpf:document-set-processing-status|cpf:document-set-last-updated|cpf:document-set-error|cpf:document-get-state|cpf:document-get-processing-status|cpf:document-get-last-updated|cpf:document-get-error|cpf:check-transition|alert:spawn-matching-actions|alert:rule-user-id-query|alert:rule-set-user-id|alert:rule-set-query|alert:rule-set-options|alert:rule-set-name|alert:rule-set-description|alert:rule-set-action|alert:rule-remove|alert:rule-name-query|alert:rule-insert|alert:rule-id-query|alert:rule-get-user-id|alert:rule-get-query|alert:rule-get-options|alert:rule-get-name|alert:rule-get-id|alert:rule-get-description|alert:rule-get-action|alert:rule-action-query|alert:remove-triggers|alert:make-rule|alert:make-log-action|alert:make-config|alert:make-action|alert:invoke-matching-actions|alert:get-my-rules|alert:get-all-rules|alert:get-actions|alert:find-matching-rules|alert:create-triggers|alert:config-set-uri|alert:config-set-trigger-ids|alert:config-set-options|alert:config-set-name|alert:config-set-description|alert:config-set-cpf-domain-names|alert:config-set-cpf-domain-ids|alert:config-insert|alert:config-get-uri|alert:config-get-trigger-ids|alert:config-get-options|alert:config-get-name|alert:config-get-id|alert:config-get-description|alert:config-get-cpf-domain-names|alert:config-get-cpf-domain-ids|alert:config-get|alert:config-delete|alert:action-set-options|alert:action-set-name|alert:action-set-module-root|alert:action-set-module-db|alert:action-set-module|alert:action-set-description|alert:action-remove|alert:action-insert|alert:action-get-options|alert:action-get-name|alert:action-get-module-root|alert:action-get-module-db|alert:action-get-module|alert:action-get-description|zero-or-one|years-from-duration|year-from-dateTime|year-from-date|upper-case|unordered|true|translate|trace|tokenize|timezone-from-time|timezone-from-dateTime|timezone-from-date|sum|subtract-dateTimes-yielding-yearMonthDuration|subtract-dateTimes-yielding-dayTimeDuration|substring-before|substring-after|substring|subsequence|string-to-codepoints|string-pad|string-length|string-join|string|static-base-uri|starts-with|seconds-from-time|seconds-from-duration|seconds-from-dateTime|round-half-to-even|round|root|reverse|resolve-uri|resolve-QName|replace|remove|QName|prefix-from-QName|position|one-or-more|number|not|normalize-unicode|normalize-space|node-name|node-kind|nilled|namespace-uri-from-QName|namespace-uri-for-prefix|namespace-uri|name|months-from-duration|month-from-dateTime|month-from-date|minutes-from-time|minutes-from-duration|minutes-from-dateTime|min|max|matches|lower-case|local-name-from-QName|local-name|last|lang|iri-to-uri|insert-before|index-of|in-scope-prefixes|implicit-timezone|idref|id|hours-from-time|hours-from-duration|hours-from-dateTime|floor|false|expanded-QName|exists|exactly-one|escape-uri|escape-html-uri|error|ends-with|encode-for-uri|empty|document-uri|doc-available|doc|distinct-values|distinct-nodes|default-collation|deep-equal|days-from-duration|day-from-dateTime|day-from-date|data|current-time|current-dateTime|current-date|count|contains|concat|compare|collection|codepoints-to-string|codepoint-equal|ceiling|boolean|base-uri|avg|adjust-time-to-timezone|adjust-dateTime-to-timezone|adjust-date-to-timezone|abs)\\b"
+            )
+        )
+        // Matching normal words if none of the previous regular expressions matched
+        fallthroughStylePatterns.new(
+            Prettify.PR_PLAIN, Regex("^[A-Za-z0-9_\\-\\:]+")
+        )
+        // Matching whitespaces
+        fallthroughStylePatterns.new(
+            Prettify.PR_PLAIN, Regex("^[\\t\\n\\r \\xA0]+")
+        )
+
+
+    }
+
+    override fun getFileExtensions(): List = fileExtensions
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangYaml.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangYaml.kt
new file mode 100644
index 0000000..b91ca5f
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/lang/LangYaml.kt
@@ -0,0 +1,98 @@
+// Copyright (C) 2010 ribrdb @ code.google.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.wakaztahir.codeeditor.prettify.lang
+
+import com.wakaztahir.codeeditor.prettify.parser.Prettify
+import com.wakaztahir.codeeditor.prettify.parser.StylePattern
+import com.wakaztahir.codeeditor.utils.new
+
+/**
+ * This is similar to the lang-yaml.js in JavaScript Prettify.
+ *
+ * All comments are adapted from the JavaScript Prettify.
+ *
+ *
+ *
+ * Registers a language handler for YAML.
+ *
+ * @author ribrdb
+ */
+class LangYaml : Lang() {
+    companion object {
+        val fileExtensions: List
+            get() = listOf("yaml", "yml")
+    }
+
+    override val fallthroughStylePatterns = ArrayList()
+    override val shortcutStylePatterns = ArrayList()
+
+    init {
+        shortcutStylePatterns.new(
+            Prettify.PR_PUNCTUATION,
+            Regex("^[:|>?]+"),
+            null
+        )
+        shortcutStylePatterns.new(
+            Prettify.PR_DECLARATION,
+            Regex("^%(?:YAML|TAG)[^#\\r\\n]+"),
+            null
+        )
+        shortcutStylePatterns.new(
+            Prettify.PR_TYPE,
+            Regex("^[&]\\S+"),
+            null
+        )
+        shortcutStylePatterns.new(
+            Prettify.PR_TYPE,
+            Regex("^!\\S*"),
+            null
+        )
+        shortcutStylePatterns.new(
+            Prettify.PR_STRING,
+            Regex("^\"(?:[^\\\\\"]|\\\\.)*(?:\"|$)"),
+            null
+        )
+        shortcutStylePatterns.new(
+            Prettify.PR_STRING,
+            Regex("^'(?:[^']|'')*(?:'|$)"),
+            null
+        )
+        shortcutStylePatterns.new(
+            Prettify.PR_COMMENT,
+            Regex("^#[^\\r\\n]*"),
+            null
+        )
+        shortcutStylePatterns.new(
+            Prettify.PR_PLAIN,
+            Regex("^\\s+"),
+            null
+        )
+        fallthroughStylePatterns.new(
+            Prettify.PR_DECLARATION,
+            Regex("^(?:---|\\.\\.\\.)(?:[\\r\\n]|$)")
+        )
+        fallthroughStylePatterns.new(Prettify.PR_PUNCTUATION, Regex("^-"))
+        fallthroughStylePatterns.new(
+            Prettify.PR_KEYWORD,
+            Regex("^\\w+:[ \\r\\n]")
+        )
+        fallthroughStylePatterns.new(Prettify.PR_PLAIN, Regex("^\\w+"))
+
+
+    }
+
+    override fun getFileExtensions(): List {
+        return fileExtensions
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/CombinePrefixPattern.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/CombinePrefixPattern.kt
new file mode 100644
index 0000000..5d7f06d
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/CombinePrefixPattern.kt
@@ -0,0 +1,365 @@
+// Copyright (C) 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.wakaztahir.codeeditor.prettify.parser
+
+import com.wakaztahir.codeeditor.prettify.parser.Util.join
+import kotlin.Char.Companion.MIN_HIGH_SURROGATE
+import kotlin.Char.Companion.MIN_LOW_SURROGATE
+import kotlin.math.abs
+
+/**
+ * This is similar to the combinePrefixPattern.js in JavaScript Prettify.
+ *
+ * All comments are adapted from the JavaScript Prettify.
+ *
+ * @author mikesamuel@gmail.com
+ */
+class CombinePrefixPattern {
+    private var capturedGroupIndex = 0
+    private var needToFoldCase = false
+
+    private val escapeCharToCodeUnit: MutableMap = HashMap().apply {
+        this['b'] = 8
+        this['t'] = 9
+        this['n'] = 0xa
+        this['v'] = 0xb
+        this['f'] = 0xc
+        this['r'] = 0xf
+    }
+
+    /**
+     * Given a group of [Regex]s, returns a `RegExp` that globally
+     * matches the union of the sets of strings matched by the input RegExp.
+     * Since it matches globally, if the input strings have a start-of-input
+     * anchor (/^.../), it is ignored for the purposes of unioning.
+     * @param regexs non multiline, non-global regexs.
+     * @return Pattern a global regex.
+     */
+    @Throws(Exception::class)
+    fun combinePrefixPattern(regexs: List): Regex {
+        var ignoreCase = false
+        run {
+            var i: Int = 0
+            val n: Int = regexs.size
+            while (i < n) {
+                val regex: Regex = regexs[i]
+                if (regex.options.contains(RegexOption.IGNORE_CASE)) {
+                    ignoreCase = true
+                } else if (Util.test(
+                        Regex("[a-z]", RegexOption.IGNORE_CASE),
+                        regex.pattern.replace("\\\\[Uu][0-9A-Fa-f]{4}|\\\\[Xx][0-9A-Fa-f]{2}|\\\\[^UuXx]".toRegex(), "")
+                    )
+                ) {
+                    needToFoldCase = true
+                    ignoreCase = false
+                    break
+                }
+                ++i
+            }
+        }
+        val rewritten: MutableList = ArrayList()
+        var i = 0
+        val n = regexs.size
+        while (i < n) {
+            val regex = regexs[i]
+            if (regex.options.contains(RegexOption.MULTILINE)) {
+                throw Exception("Multiline Regex : " + regex.pattern)
+            }
+            rewritten.add("(?:" + allowAnywhereFoldCaseAndRenumberGroups(regex) + ")")
+            ++i
+        }
+        return if (ignoreCase) Regex(
+            join(rewritten, "|"),
+            RegexOption.IGNORE_CASE
+        ) else Regex(
+            join(rewritten, "|")
+        )
+    }
+
+    private fun decodeEscape(charsetPart: String): Int {
+        val cc0: Int = charsetPart[0].code
+        if (cc0 != 92  /* \\ */) {
+            return cc0
+        }
+        val c1 = charsetPart[1]
+        val charCode = escapeCharToCodeUnit[c1]
+        return when {
+            charCode != null -> charCode
+            c1 in '0'..'7' -> charsetPart.substring(1).toInt(8)
+            c1 == 'u' || c1 == 'x' -> charsetPart.substring(2).toInt(16)
+            else -> charsetPart[1].code
+        }
+    }
+
+    private fun encodeEscape(charCode: Int): String {
+        if (charCode < 0x20) {
+            return (if (charCode < 0x10) "\\x0" else "\\x") + charCode.toString(16)
+        }
+//        val ch = String(Character.toChars(charCode))
+        val ch = toChars(charCode).joinToString("")
+        return if (((charCode == '\\'.code) || (charCode == '-'.code) || (charCode == ']'.code) || (charCode == '^'.code))) "\\" + ch else ch
+    }
+
+    private fun toChars(codePoint: Int): CharArray {
+        return if (codePoint ushr 16 == 0) {
+            charArrayOf(codePoint.toChar())
+        } else if (codePoint ushr 16 < 0X10FFFF + 1 ushr 16) {
+            val charArray = CharArray(2)
+            charArray[1] = ((codePoint and 0x3ff) + MIN_LOW_SURROGATE.code).toChar()
+            charArray[0] =
+                ((codePoint ushr 10) + (MIN_HIGH_SURROGATE.code - (0x010000 ushr 10))).toChar()
+            charArray
+        } else {
+            throw IllegalArgumentException("Not a valid unicode code point")
+        }
+    }
+
+    private fun caseFoldCharset(charSet: String?): String {
+        val charsetParts = Util.match(
+            Regex(
+                ("\\\\u[0-9A-Fa-f]{4}"
+                        + "|\\\\x[0-9A-Fa-f]{2}"
+                        + "|\\\\[0-3][0-7]{0,2}"
+                        + "|\\\\[0-7]{1,2}"
+                        + "|\\\\[\\s\\S]"
+                        + "|-"
+                        + "|[^-\\\\]")
+            ), charSet!!.substring(1, charSet.length - 1), true
+        )
+        val ranges: MutableList> = ArrayList()
+        val inverse = charsetParts[0] == "^"
+        val out: MutableList = ArrayList(listOf("["))
+        if (inverse) {
+            out.add("^")
+        }
+        run {
+            var i: Int = if (inverse) 1 else 0
+            val n: Int = charsetParts.size
+            while (i < n) {
+                val p: String = charsetParts[i]
+                if (Util.test(Regex("\\\\[bdsw]", RegexOption.IGNORE_CASE), p)) {  // Don't muck with named groups.
+                    out.add(p)
+                } else {
+                    val start: Int = decodeEscape(p)
+                    var end: Int
+                    if (i + 2 < n && ("-" == charsetParts[i + 1])) {
+                        end = decodeEscape(charsetParts[i + 2])
+                        i += 2
+                    } else {
+                        end = start
+                    }
+                    ranges.add(mutableListOf(start, end))
+                    // If the range might intersect letters, then expand it.
+                    // This case handling is too simplistic.
+                    // It does not deal with non-latin case folding.
+                    // It works for latin source code identifiers though.
+                    if (!(end < 65 || start > 122)) {
+                        if (!(end < 65 || start > 90)) {
+                            ranges.add(mutableListOf(65.coerceAtLeast(start) or 32, end.coerceAtMost(90) or 32))
+                        }
+                        if (!(end < 97 || start > 122)) {
+                            ranges.add(
+                                mutableListOf(
+                                    97.coerceAtLeast(start) and 32.inv(), end.coerceAtMost(122) and 32.inv()
+                                )
+                            )
+                        }
+                    }
+                }
+                ++i
+            }
+        }
+
+        // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
+        // -> [[1, 12], [14, 14], [16, 17]]
+        ranges.sortWith { a, b -> if (a[0] != b[0]) (a[0] - b[0]) else (b[1] - a[1]) }
+//        Collections.sort(ranges, Comparator { a, b -> if (a[0] !== b[0]) (a[0] - b[0]) else (b[1] - a[1]) })
+        val consolidatedRanges: MutableList> = ArrayList()
+        //        List lastRange = listOf(new Integer[]{0, 0});
+        var lastRange: MutableList = ArrayList(listOf(0, 0))
+        for (i in ranges.indices) {
+            val range = ranges[i]
+            if (range[0] <= lastRange[1] + 1) {
+                lastRange[1] = (lastRange[1]).coerceAtLeast(range[1])
+            } else {
+                // reference of lastRange is added
+                consolidatedRanges.add(range.also { lastRange = it })
+            }
+        }
+        for (i in consolidatedRanges.indices) {
+            val range = consolidatedRanges[i]
+            out.add(encodeEscape(range[0]))
+            if (range[1] > range[0]) {
+                if (range[1] + 1 > range[0]) {
+                    out.add("-")
+                }
+                out.add(encodeEscape(range[1]))
+            }
+        }
+        out.add("]")
+        return join(out)
+    }
+
+    private fun allowAnywhereFoldCaseAndRenumberGroups(regex: Regex): String {
+        // Split into character sets, escape sequences, punctuation strings
+        // like ('(', '(?:', ')', '^'), and runs of characters that do not
+        // include any of the above.
+        val parts = Util.match(
+            Regex(
+                ("(?:"
+                        + "\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]" // a character set
+                        + "|\\\\u[A-Fa-f0-9]{4}" // a unicode escape
+                        + "|\\\\x[A-Fa-f0-9]{2}" // a hex escape
+                        + "|\\\\[0-9]+" // a back-reference or octal escape
+                        + "|\\\\[^ux0-9]" // other escape sequence
+                        + "|\\(\\?[:!=]" // start of a non-capturing group
+                        + "|[\\(\\)\\^]" // start/end of a group, or line start
+                        + "|[^\\x5B\\x5C\\(\\)\\^]+" // run of other characters
+                        + ")")
+            ), regex.pattern, true
+        )
+        val n = parts.size
+
+        // Maps captured group numbers to the number they will occupy in
+        // the output or to -1 if that has not been determined, or to
+        // undefined if they need not be capturing in the output.
+        val capturedGroups: MutableMap = HashMap()
+
+        // Walk over and identify back references to build the capturedGroups
+        // mapping.
+        run {
+            var i: Int = 0
+            var groupIndex: Int = 0
+            while (i < n) {
+                val p: String = parts[i]
+                if ((p == "(")) {
+                    // groups are 1-indexed, so max group index is count of '('
+                    ++groupIndex
+                } else if ('\\' == p[0]) {
+                    try {
+                        val decimalValue: Int = abs(p.substring(1).toInt())
+                        if (decimalValue <= groupIndex) {
+                            capturedGroups[decimalValue] = -1
+                        } else {
+                            // Replace with an unambiguous escape sequence so that
+                            // an octal escape sequence does not turn into a backreference
+                            // to a capturing group from an earlier regex.
+                            parts[i] = encodeEscape(decimalValue)
+                        }
+                    } catch (ex: NumberFormatException) {
+                    }
+                }
+                ++i
+            }
+        }
+
+        // Renumber groups and reduce capturing groups to non-capturing groups
+        // where possible.
+        for (i: Int in capturedGroups.keys) {
+            if (-1 == capturedGroups[i]) {
+                capturedGroups[i] = ++capturedGroupIndex
+            }
+        }
+        run {
+            var i: Int = 0
+            var groupIndex: Int = 0
+            while (i < n) {
+                val p: String = parts[i]
+                if ((p == "(")) {
+                    ++groupIndex
+                    if (capturedGroups[groupIndex] == null) {
+                        parts[i] = "(?:"
+                    }
+                } else if ('\\' == p[0]) {
+                    try {
+                        val decimalValue: Int = abs(p.substring(1).toInt())
+                        if (decimalValue <= groupIndex) {
+                            parts[i] = "\\" + capturedGroups[decimalValue]
+                        }
+                    } catch (ex: NumberFormatException) {
+                    }
+                }
+                ++i
+            }
+        }
+
+        // Remove any prefix anchors so that the output will match anywhere.
+        // ^^ really does mean an anchored match though.
+        for (i in 0 until n) {
+            if (("^" == parts[i]) && "^" != parts[i + 1]) {
+                parts[i] = ""
+            }
+        }
+
+        // Expand letters to groups to handle mixing of case-sensitive and
+        // case-insensitive patterns if necessary.
+        if (regex.options.contains(RegexOption.IGNORE_CASE) && needToFoldCase) {
+            for (i in 0 until n) {
+                val p = parts[i]
+                val ch0: Char = if (p.isNotEmpty()) p[0] else '0'
+                if (p.length >= 2 && ch0 == '[') {
+                    parts[i] = caseFoldCharset(p)
+                } else if (ch0 != '\\') {
+                    // TODO: handle letters in numeric escapes.
+
+
+//                    val sb = StringBuilder()
+//                    val _matcher = Pattern.compile("[a-zA-Z]").matcher(p)
+//                    while (_matcher.find()) {
+//                        val cc = _matcher.group(0).codePointAt(0)
+//                        _matcher.appendReplacement(sb, "")
+//                        sb.append("[").append((cc and 32.inv()).toChar().toString()).append(
+//                            (cc or 32).toChar().toString()
+//                        ).append("]")
+//                    }
+//                    _matcher.appendTail(sb)
+
+                    //This code replaces the above commented code
+                    val mySb = StringBuilder()
+                    val regEx = Regex("[a-zA-Z]")
+                    var startIndex = 0
+                    var matchResult = regEx.find(p, startIndex)
+                    if (matchResult == null) {
+                        mySb.append(p.substring(startIndex, p.length))
+                    }
+                    var appendIndex = 0
+                    while (matchResult != null) {
+                        val cc = matchResult.groups[0]?.value?.get(0)?.code
+                        mySb.append(p.substring(appendIndex,matchResult.range.first))
+                        appendIndex = matchResult.range.last
+                        if (cc != null) {
+                            mySb.append("[").append((cc and 32.inv()).toChar().toString()).append(
+                                (cc or 32).toChar().toString()
+                            ).append("]")
+                        }
+                        startIndex = matchResult.range.last + 1
+                        matchResult = regEx.find(p, startIndex)
+                        if (matchResult == null) {
+                            if (startIndex < p.length) {
+                                mySb.append(p.substring(startIndex, p.length))
+                            }
+                        }
+                    }
+
+//                    println("-----------------")
+//                    println("finding in : $p")
+//                    println("His String : $sb")
+//                    println("Myy String : $mySb")
+                    parts[i] = mySb.toString()
+                }
+            }
+        }
+        return join(parts)
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/Job.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/Job.kt
new file mode 100644
index 0000000..95b90e4
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/Job.kt
@@ -0,0 +1,108 @@
+// Copyright (C) 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.wakaztahir.codeeditor.prettify.parser
+
+/**
+ * This is the job object that similar to those in JavaScript Prettify.
+ *
+ * @author Chan Wai Shing @gmail.com>
+ */
+class Job constructor(basePos: Int = 0, sourceCode: String? = "") {
+    /**
+     * Set the starting point of the source code.
+     *
+     * @return the position
+     */
+    /**
+     * Set the starting point of the source code.
+     *
+     * @param basePos the position
+     */
+    /**
+     * The starting point of the source code.
+     */
+    var basePos: Int
+
+    /**
+     * The source code.
+     */
+    private var sourceCode: String
+
+    /**
+     * The parsed results. nth items are starting position position,
+     * n+1th items are the three-letter style keyword, where n start
+     * from 0.
+     */
+    internal var decorations: List
+
+    /**
+     * Get the source code.
+     *
+     * @return the source code
+     */
+    fun getSourceCode(): String {
+        return sourceCode
+    }
+
+    /**
+     * Set the source code.
+     *
+     * @param sourceCode the source code
+     */
+    fun setSourceCode(sourceCode: String?) {
+        if (sourceCode == null) {
+            throw NullPointerException("argument 'sourceCode' cannot be null")
+        }
+        this.sourceCode = sourceCode
+    }
+
+    /**
+     * Get the parsed results. see [.decorations].
+     *
+     * @return the parsed results
+     */
+    fun getDecorations(): List {
+        return ArrayList(decorations)
+    }
+
+    /**
+     * Set the parsed results. see [.decorations].
+     *
+     * @param decorations the parsed results
+     */
+    fun setDecorations(decorations: List?) {
+        if (decorations == null) {
+            this.decorations = ArrayList()
+            return
+        }
+        this.decorations = ArrayList(decorations)
+    }
+    /**
+     * Constructor.
+     *
+     * @param basePos the starting point of the source code
+     * @param sourceCode the source code
+     */
+    /**
+     * Constructor.
+     */
+    init {
+        if (sourceCode == null) {
+            throw NullPointerException("argument 'sourceCode' cannot be null")
+        }
+        this.basePos = basePos
+        this.sourceCode = sourceCode
+        decorations = ArrayList()
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/Prettify.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/Prettify.kt
new file mode 100644
index 0000000..bd2427b
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/Prettify.kt
@@ -0,0 +1,978 @@
+// Copyright (C) 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.wakaztahir.codeeditor.prettify.parser
+
+import com.wakaztahir.codeeditor.prettify.lang.*
+import com.wakaztahir.codeeditor.utils.new
+
+open class StylePattern(
+    val tokenStyle: String,
+    val regExp: Regex,
+    val shortcutChars: String? = null
+)
+
+/**
+ * This is similar to the prettify.js in JavaScript Prettify.
+ *
+ * All comments are adapted from the JavaScript Prettify.
+ *
+ *
+ *
+ * Some functions for browser-side pretty printing of code contained in html.
+ *
+ *
+ *
+ *
+ * For a fairly comprehensive set of languages see the
+ * [README](http://google-code-prettify.googlecode.com/svn/trunk/README.html#langs)
+ * file that came with this source.  At a minimum, the lexer should work on a
+ * number of languages including C and friends, Java, Python, Bash, SQL, HTML,
+ * XML, CSS, Javascript, and Makefiles.  It works passably on Ruby, PHP and Awk
+ * and a subset of Perl, but, because of commenting conventions, doesn't work on
+ * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class.
+ *
+ *
+ * Usage:
+ *  1.  include this source file in an html page via
+ * ``
+ *  1.  define style rules.  See the example page for examples.
+ *  1.  mark the `
` and `` tags in your source with
+ * `class=prettyprint.`
+ * You can also use the (html deprecated) `` tag, but the pretty
+ * printer needs to do more substantial DOM manipulations to support that, so
+ * some css styles may not be preserved.
+ *
+ * That's it.  I wanted to keep the API as simple as possible, so there's no
+ * need to specify which language the code is in, but if you wish, you can add
+ * another class to the `<pre>` or `<code>` element to specify the
+ * language, as in `<pre class="prettyprint lang-java">`.  Any class that
+ * starts with "lang-" followed by a file extension, specifies the file type.
+ * See the "lang-*.js" files in this directory for code that implements
+ * per-language file handlers.
+ *
+ *
+ * Change log:<br></br>
+ * cbeust, 2006/08/22
+ * <blockquote>
+ * Java annotations (start with "@") are now captured as literals ("lit")
+</blockquote> *
+ */
+class Prettify {
+
+    fun interface LangProvider {
+        fun provide(): Lang
+    }
+
+    /**
+     * maps extensions of languages to their lang providers
+     * lang providers are objects that provide language instances for extensions
+     */
+    private val extensionMap by lazy {
+        hashMapOf<String, LangProvider>().apply {
+            // registering extensions for available languages
+            registerExtensions(LangAppollo.fileExtensions) { LangAppollo() }
+            registerExtensions(LangBasic.fileExtensions) { LangBasic() }
+            registerExtensions(LangClj.fileExtensions) { LangClj() }
+            registerExtensions(LangCss.fileExtensions) { LangCss() }
+            registerExtensions(LangDart.fileExtensions) { LangDart() }
+            registerExtensions(LangErlang.fileExtensions) { LangErlang() }
+            registerExtensions(LangGo.fileExtensions) { LangGo() }
+            registerExtensions(LangHs.fileExtensions) { LangHs() }
+            registerExtensions(LangLisp.fileExtensions) { LangLisp() }
+            registerExtensions(LangLlvm.fileExtensions) { LangLlvm() }
+            registerExtensions(LangLua.fileExtensions) { LangLua() }
+            registerExtensions(LangMatlab.fileExtensions) { LangMatlab() }
+            registerExtensions(LangMd.fileExtensions) { LangMd() }
+            registerExtensions(LangMl.fileExtensions) { LangMl() }
+            registerExtensions(LangMumps.fileExtensions) { LangMumps() }
+            registerExtensions(LangN.fileExtensions) { LangN() }
+            registerExtensions(LangPascal.fileExtensions) { LangPascal() }
+            registerExtensions(LangR.fileExtensions) { LangR() }
+            registerExtensions(LangRd.fileExtensions) { LangRd() }
+            registerExtensions(LangScala.fileExtensions) { LangScala() }
+            registerExtensions(LangSql.fileExtensions) { LangSql() }
+            registerExtensions(LangTex.fileExtensions) { LangTex() }
+            registerExtensions(LangVb.fileExtensions) { LangVb() }
+            registerExtensions(LangVhdl.fileExtensions) { LangVhdl() }
+            registerExtensions(LangTcl.fileExtensions) { LangTcl() }
+            registerExtensions(LangWiki.fileExtensions) { LangWiki() }
+            registerExtensions(LangXq.fileExtensions) { LangXq() }
+            registerExtensions(LangYaml.fileExtensions) { LangYaml() }
+            registerExtensions(LangEx.fileExtensions) { LangEx() }
+            registerExtensions(LangKotlin.fileExtensions) { LangKotlin() }
+            registerExtensions(LangLasso.fileExtensions) { LangLasso() }
+            registerExtensions(LangLogtalk.fileExtensions) { LangLogtalk() }
+            registerExtensions(LangSwift.fileExtensions) { LangSwift() }
+            registerExtensions(LangCss.LangCssKeyword.fileExtensions) { LangCss.LangCssKeyword() }
+            registerExtensions(LangCss.LangCssString.fileExtensions) { LangCss.LangCssString() }
+            registerExtensions(LangMatlab.LangMatlabIdentifier.fileExtensions) { LangMatlab.LangMatlabIdentifier() }
+            registerExtensions(LangMatlab.LangMatlabOperator.fileExtensions) { LangMatlab.LangMatlabOperator() }
+            registerExtensions(LangWiki.LangWikiMeta.fileExtensions) { LangWiki.LangWikiMeta() }
+        }
+    }
+
+    // registers extensions for languages
+    private fun MutableMap<String, LangProvider>.registerExtensions(extensions: List<String>, langCreator: () -> Lang) {
+        val provider = object : LangProvider {
+            val lang by lazy { langCreator() }
+            override fun provide(): Lang = lang
+        }
+        for (extension in extensions){
+            this[extension] = provider
+        }
+    }
+
+    private fun getLangFromExtension(extension: String): Lang = extensionMap[extension]?.provide() ?: run {
+        throw IllegalArgumentException("Missing language for extension : $extension")
+    }
+    fun isSupport(extension:String): Boolean {
+        return extensionMap.containsKey(extension)
+    }
+    /** Maps language-specific file extensions to handlers.  */
+    private val langHandlerRegistry: MutableMap<String, CreateSimpleLexer?> = HashMap()
+
+    /** returns a function that produces a list of decorations from source text.
+     *
+     * This code treats ", ', and ` as string delimiters, and \ as a string
+     * escape.  It does not recognize perl's qq() style strings.
+     * It has no special handling for double delimiter escapes as in basic, or
+     * the tripled delimiters used in python, but should work on those regardless
+     * although in those cases a single string literal may be broken up into
+     * multiple adjacent string literals.
+     *
+     * It recognizes C, C++, and shell style comments.
+     *
+     * @param options a set of optional parameters.
+     * @return a function that examines the source code
+     * in the input job and builds the decoration list.
+     */
+    @Throws(Exception::class)
+    internal fun sourceDecorator(options: Map<String?, Any?>): CreateSimpleLexer {
+        val shortcutStylePatterns: MutableList<StylePattern> = ArrayList()
+        val fallthroughStylePatterns: MutableList<StylePattern> = ArrayList()
+        if (Util.getVariableValueAsBoolean(options["tripleQuotedStrings"])) {
+            // '''multi-line-string''', 'single-line-string', and double-quoted
+            shortcutStylePatterns.new(
+                tokenStyle = PR_STRING,
+                regExp = Regex("^(?:'''(?:[^'\\\\]|\\\\[\\s\\S]|'{1,2}(?=[^']))*(?:'''|$)|\"\"\"(?:[^\"\\\\]|\\\\[\\s\\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|'(?:[^\\\\']|\\\\[\\s\\S])*(?:'|$)|\"(?:[^\\\\\"]|\\\\[\\s\\S])*(?:\"|$))"),
+                shortcutChars = null
+            )
+        } else if (Util.getVariableValueAsBoolean(options["multiLineStrings"])) {
+            // 'multi-line-string', "multi-line-string"
+            shortcutStylePatterns.new(
+                PR_STRING,
+                Regex("^(?:'(?:[^\\\\']|\\\\[\\s\\S])*(?:'|$)|\"(?:[^\\\\\"]|\\\\[\\s\\S])*(?:\"|$)|`(?:[^\\\\`]|\\\\[\\s\\S])*(?:`|$))"),
+                null
+            )
+        } else {
+            // 'single-line-string', "single-line-string"
+            shortcutStylePatterns.new(
+                PR_STRING,
+                Regex("^(?:'(?:[^\\\\'\r\n]|\\\\.)*(?:'|$)|\"(?:[^\\\\\"\r\n]|\\\\.)*(?:\"|$))"),
+                null
+            )
+        }
+        if (Util.getVariableValueAsBoolean(options["verbatimStrings"])) {
+            // verbatim-string-literal production from the C# grammar.  See issue 93.
+            fallthroughStylePatterns.new(
+                PR_STRING,
+                Regex("^@\"(?:[^\"]|\"\")*(?:\"|$)"),
+                null
+            )
+        }
+        val hc = options["hashComments"]
+        if (Util.getVariableValueAsBoolean(hc)) {
+            if (Util.getVariableValueAsBoolean(options["cStyleComments"])) {
+                if (hc is Int && hc > 1) {  // multiline hash comments
+                    shortcutStylePatterns.new(
+                        PR_COMMENT,
+                        Regex("^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)"),
+                        null
+                    )
+                } else {
+                    // Stop C preprocessor declarations at an unclosed open comment
+                    shortcutStylePatterns.new(
+                        PR_COMMENT,
+                        Regex("^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\\b|[^\r\n]*)"),
+                        null
+                    )
+                }
+                // #include <stdio.h>
+                fallthroughStylePatterns.new(
+                    PR_STRING,
+                    Regex("^<(?:(?:(?:\\.\\./)*|/?)(?:[\\w-]+(?:/[\\w-]+)+)?[\\w-]+\\.h(?:h|pp|\\+\\+)?|[a-z]\\w*)>"),
+                    null
+                )
+            } else {
+                shortcutStylePatterns.new(
+                    PR_COMMENT,
+                    Regex("^#[^\r\n]*"),
+                    null
+                )
+            }
+        }
+        if (Util.getVariableValueAsBoolean(options["cStyleComments"])) {
+            fallthroughStylePatterns.new(
+                PR_COMMENT,
+                Regex("^//[^\r\n]*"),
+                null
+            )
+            fallthroughStylePatterns.new(
+                PR_COMMENT,
+                Regex("^/\\*[\\s\\S]*?(?:\\*/|$)"),
+                null
+            )
+        }
+        val regexLiterals = options["regexLiterals"]
+        if (Util.getVariableValueAsBoolean(regexLiterals)) {
+            /**
+             * @const
+             */
+            // Javascript treat true as 1
+            val regexExcls = if (Util.getVariableValueAsInteger(regexLiterals) > 1) "" // Multiline regex literals
+            else "\n\r"
+
+            /**
+             * @const
+             */
+            val regexAny = if (regexExcls.isNotEmpty()) "." else "[\\S\\s]"
+
+            /**
+             * @const
+             */
+            val REGEX_LITERAL =  // A regular expression literal starts with a slash that is
+            // not followed by * or / so that it is not confused with
+                // comments.
+                ("/(?=[^/*" + regexExcls + "])" // and then contains any number of raw characters,
+                        + "(?:[^/\\x5B\\x5C" + regexExcls + "]" // escape sequences (\x5C),
+                        + "|\\x5C" + regexAny // or non-nesting character sets (\x5B\x5D);
+                        + "|\\x5B(?:[^\\x5C\\x5D" + regexExcls + "]"
+                        + "|\\x5C" + regexAny + ")*(?:\\x5D|$))+" // finally closed by a /.
+                        + "/")
+            fallthroughStylePatterns.new(
+                "lang-regex",
+                Regex("^$REGEXP_PRECEDES_PATTERN($REGEX_LITERAL)")
+            )
+        }
+        val types = options["types"] as? Regex
+        if (Util.getVariableValueAsBoolean(types) && types != null) {
+            fallthroughStylePatterns.new(PR_TYPE, types)
+        }
+        var keywords = options["keywords"] as String?
+        if (keywords != null) {
+            keywords = keywords.replace("^ | $".toRegex(), "")
+            if (keywords.isNotEmpty()) {
+                fallthroughStylePatterns.new(
+                    PR_KEYWORD,
+                    Regex("^(?:" + keywords.replace("[\\s,]+".toRegex(), "|") + ")\\b"),
+                    null
+                )
+            }
+        }
+
+        shortcutStylePatterns.new(
+            PR_PLAIN,
+            Regex("^\\s+"),
+            null
+        )
+
+        // TODO(mikesamuel): recognize non-latin letters and numerals in idents
+        fallthroughStylePatterns.new(
+            PR_LITERAL,
+            Regex("^@[a-z_$][a-z_$@0-9]*", RegexOption.IGNORE_CASE),
+            null
+        )
+        fallthroughStylePatterns.new(
+            PR_TYPE,
+            Regex("^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\\w+_t\\b)"),
+            null
+        )
+        fallthroughStylePatterns.new(
+            PR_PLAIN,
+            Regex("^[a-z_$][a-z_$@0-9]*", RegexOption.IGNORE_CASE),
+            null
+        )
+        fallthroughStylePatterns.new(
+            PR_LITERAL,
+            Regex(
+                "^(?:" // A hex number
+                        + "0x[a-f0-9]+" // or an octal or decimal number,
+                        + "|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)" // possibly in scientific notation
+                        + "(?:e[+\\-]?\\d+)?"
+                        + ')' // with an optional modifier like UL for unsigned long
+                        + "[a-z]*", RegexOption.IGNORE_CASE
+            ),
+            null
+        )
+        // Don't treat escaped quotes in bash as starting strings.
+        // See issue 144.
+        fallthroughStylePatterns.new(
+            PR_PLAIN,
+            Regex("^\\\\[\\s\\S]?"),
+            null
+        )
+
+        // The Bash man page says
+
+        // A word is a sequence of characters considered as a single
+        // unit by GRUB. Words are separated by metacharacters,
+        // which are the following plus space, tab, and newline: { }
+        // | & $ ; < >
+        // ...
+
+        // A word beginning with # causes that word and all remaining
+        // characters on that line to be ignored.
+
+        // which means that only a '#' after /(?:^|[{}|&$;<>\s])/ starts a
+        // comment but empirically
+        // $ echo {#}
+        // {#}
+        // $ echo \$#
+        // $#
+        // $ echo }#
+        // }#
+
+        // so /(?:^|[|&;<>\s])/ is more appropriate.
+
+        // http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC3
+        // suggests that this definition is compatible with a
+        // default mode that tries to use a single token definition
+        // to recognize both bash/python style comments and C
+        // preprocessor directives.
+
+        // This definition of punctuation does not include # in the list of
+        // follow-on exclusions, so # will not be broken before if preceeded
+        // by a punctuation character.  We could try to exclude # after
+        // [|&;<>] but that doesn't seem to cause many major problems.
+        // If that does turn out to be a problem, we should change the below
+        // when hc is truthy to include # in the run of punctuation characters
+        // only when not followint [|&;<>].
+        var punctuation = "^.[^\\s\\w.$@'\"`/\\\\]*"
+        if (Util.getVariableValueAsBoolean(options["regexLiterals"])) {
+            punctuation += "(?!\\s*/)"
+        }
+        fallthroughStylePatterns.new(
+            PR_PUNCTUATION,
+            Regex(punctuation),
+            null
+        )
+        return CreateSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns)
+    }
+
+    /** Register a language handler for the given file extensions.
+     * @param handler a function from source code to a list
+     * of decorations.  Takes a single argument job which describes the
+     * state of the computation.   The single parameter has the form
+     * `{
+     * sourceCode: {string} as plain text.
+     * decorations: {Array.<number|string>} an array of style classes
+     * preceded by the position at which they start in
+     * job.sourceCode in order.
+     * The language handler should assigned this field.
+     * basePos: {int} the position of source in the larger source chunk.
+     * All positions in the output decorations array are relative
+     * to the larger source chunk.
+     * } `
+     * @param fileExtensions
+     */
+    @Throws(Exception::class)
+    internal fun registerLangHandler(handler: CreateSimpleLexer, fileExtensions: List<String>) {
+        for(extension in fileExtensions){
+            if (langHandlerRegistry[extension] == null) {
+                langHandlerRegistry[extension] = handler
+            } else {
+                throw Exception("cannot override language handler for extension $extension")
+            }
+        }
+    }
+
+    /**
+     * Get the parser for the extension specified.
+     * @param extension the file extension, if null, default parser will be returned
+     * @return the parser
+     */
+    fun getLexerForExtension(extension: String): CreateSimpleLexer {
+        val lang = getLangFromExtension(extension)
+        return langHandlerRegistry[extension] ?: getLexerForLanguage(lang)
+    }
+
+    fun getLexerForLanguageProvider(langProvider: LangProvider): CreateSimpleLexer {
+        val lang = langProvider.provide()
+        var lexer: CreateSimpleLexer? = null
+        for (extension in lang.getFileExtensions()) {
+            lexer = langHandlerRegistry[extension]
+            if (lexer != null) break
+        }
+        return lexer ?: getLexerForLanguage(lang)
+    }
+
+
+    private fun getLexerForLanguage(lang: Lang): CreateSimpleLexer {
+        return CreateSimpleLexer(lang.shortcutStylePatterns, lang.fallthroughStylePatterns).also { lexer ->
+            lang.getFileExtensions().forEach {
+                langHandlerRegistry[it] = lexer
+            }
+        }
+    }
+
+    // CAVEAT: this does not properly handle the case where a regular
+    // expression immediately follows another since a regular expression may
+    // have flags for case-sensitivity and the like.  Having regexp tokens
+    // adjacent is not valid in any language I'm aware of, so I'm punting.
+    // TODO: maybe style special characters inside a regexp as punctuation.
+    init {
+        try {
+            var decorateSourceMap: MutableMap<String?, Any?> = HashMap()
+            decorateSourceMap["keywords"] = ALL_KEYWORDS
+            decorateSourceMap["hashComments"] = true
+            decorateSourceMap["cStyleComments"] = true
+            decorateSourceMap["multiLineStrings"] = true
+            decorateSourceMap["regexLiterals"] = true
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("default-code"))
+            var shortcutStylePatterns: MutableList<StylePattern> = ArrayList()
+            var fallthroughStylePatterns: MutableList<StylePattern> = ArrayList()
+            fallthroughStylePatterns.new(PR_PLAIN, Regex("^[^<?]+"))
+            fallthroughStylePatterns.new(
+                PR_DECLARATION, Regex("^<!\\w[^>]*(?:>|$)")
+            )
+            fallthroughStylePatterns.new(
+                PR_COMMENT, Regex("^<!--[\\s\\S]*?(?:-->|$)")
+            )
+            // Unescaped content in an unknown language
+            fallthroughStylePatterns.new(
+                "lang-", Regex("^<\\?([\\s\\S]+?)(?:\\?>|$)")
+            )
+            fallthroughStylePatterns.new(
+                "lang-", Regex("^<%([\\s\\S]+?)(?:%>|$)")
+            )
+            fallthroughStylePatterns.new(
+                PR_PUNCTUATION, Regex("^(?:<[%?]|[%?]>)")
+            )
+            fallthroughStylePatterns.new(
+                "lang-", Regex("^<xmp\\b[^>]*>([\\s\\S]+?)</xmp\\b[^>]*>", RegexOption.IGNORE_CASE)
+            )
+            // Unescaped content in javascript.  (Or possibly vbscript).
+            fallthroughStylePatterns.new(
+                "lang-js",
+                Regex("^<script\\b[^>]*>([\\s\\S]*?)(</script\\b[^>]*>)", RegexOption.IGNORE_CASE)
+            )
+            // Contains unescaped stylesheet content
+            fallthroughStylePatterns.new(
+                "lang-css",
+                Regex("^<style\\b[^>]*>([\\s\\S]*?)(</style\\b[^>]*>)", RegexOption.IGNORE_CASE)
+            )
+            fallthroughStylePatterns.new(
+                "lang-in.tag", Regex("^(</?[a-z][^<>]*>)", RegexOption.IGNORE_CASE)
+            )
+            registerLangHandler(
+                CreateSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns),
+                listOf("default-markup", "htm", "html", "mxml", "xhtml", "xml", "xsl")
+            )
+            shortcutStylePatterns = ArrayList()
+            fallthroughStylePatterns = ArrayList()
+            shortcutStylePatterns.new(
+                PR_PLAIN,
+                Regex("^[\\s]+"),
+                null
+            )
+            shortcutStylePatterns.new(
+                PR_ATTRIB_VALUE,
+                Regex("^(?:\"[^\"]*\"?|'[^']*'?)"),
+                null
+            )
+            fallthroughStylePatterns.new(
+                PR_TAG, Regex("^^</?[a-z](?:[\\w.:-]*\\w)?|/?>$", RegexOption.IGNORE_CASE)
+            )
+            fallthroughStylePatterns.new(
+                PR_ATTRIB_NAME,
+                Regex("^(?!style[\\s=]|on)[a-z](?:[\\w:-]*\\w)?", RegexOption.IGNORE_CASE)
+            )
+            fallthroughStylePatterns.new(
+                "lang-uq.val", Regex(
+                    "^=\\s*([^>'\"\\s]*(?:[^>'\"\\s/]|/(?=\\s)))",
+                    RegexOption.IGNORE_CASE
+                )
+            )
+            fallthroughStylePatterns.new(PR_PUNCTUATION, Regex("^[=<>/]+"))
+            fallthroughStylePatterns.new(
+                "lang-js", Regex("^on\\w+\\s*=\\s*\"([^\"]+)\"", RegexOption.IGNORE_CASE)
+            )
+            fallthroughStylePatterns.new(
+                "lang-js", Regex("^on\\w+\\s*=\\s*'([^']+)'", RegexOption.IGNORE_CASE)
+            )
+            fallthroughStylePatterns.new(
+                "lang-js", Regex("^on\\w+\\s*=\\s*([^\"'>\\s]+)", RegexOption.IGNORE_CASE)
+            )
+            fallthroughStylePatterns.new(
+                "lang-css", Regex("^style\\s*=\\s*\"([^\"]+)\"", RegexOption.IGNORE_CASE)
+            )
+            fallthroughStylePatterns.new(
+                "lang-css", Regex("^style\\s*=\\s*'([^']+)'", RegexOption.IGNORE_CASE)
+            )
+            fallthroughStylePatterns.new(
+                "lang-css", Regex("^style\\s*=\\s\\*([^\"'>\\s]+)", RegexOption.IGNORE_CASE)
+            )
+            registerLangHandler(
+                CreateSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns),
+                listOf("in.tag")
+            )
+            shortcutStylePatterns = ArrayList()
+            fallthroughStylePatterns = ArrayList()
+            fallthroughStylePatterns.new(PR_ATTRIB_VALUE, Regex("^[\\s\\S]+"))
+            registerLangHandler(
+                CreateSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns),
+                listOf("uq.val")
+            )
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = CPP_KEYWORDS
+            decorateSourceMap["hashComments"] = true
+            decorateSourceMap["cStyleComments"] = true
+            decorateSourceMap["types"] = C_TYPES
+            registerLangHandler(
+                sourceDecorator(decorateSourceMap),
+                listOf("c", "cc", "cpp", "cxx", "cyc", "m")
+            )
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = "null,true,false"
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("json"))
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = CSHARP_KEYWORDS
+            decorateSourceMap["hashComments"] = true
+            decorateSourceMap["cStyleComments"] = true
+            decorateSourceMap["verbatimStrings"] = true
+            decorateSourceMap["types"] = C_TYPES
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("cs"))
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = JAVA_KEYWORDS
+            decorateSourceMap["cStyleComments"] = true
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("java"))
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = SH_KEYWORDS
+            decorateSourceMap["hashComments"] = true
+            decorateSourceMap["multiLineStrings"] = true
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("bash", "bsh", "csh", "sh"))
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = PYTHON_KEYWORDS
+            decorateSourceMap["hashComments"] = true
+            decorateSourceMap["multiLineStrings"] = true
+            decorateSourceMap["tripleQuotedStrings"] = true
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("cv", "py", "python"))
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = PERL_KEYWORDS
+            decorateSourceMap["hashComments"] = true
+            decorateSourceMap["multiLineStrings"] = true
+            decorateSourceMap["regexLiterals"] = 2 // multiline regex literals
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("perl", "pl", "pm"))
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = RUBY_KEYWORDS
+            decorateSourceMap["hashComments"] = true
+            decorateSourceMap["multiLineStrings"] = true
+            decorateSourceMap["regexLiterals"] = true
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("rb", "ruby"))
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = JSCRIPT_KEYWORDS
+            decorateSourceMap["cStyleComments"] = true
+            decorateSourceMap["regexLiterals"] = true
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("javascript", "js"))
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = COFFEE_KEYWORDS
+            decorateSourceMap["hashComments"] = 3 // ### style block comments
+            decorateSourceMap["cStyleComments"] = true
+            decorateSourceMap["multilineStrings"] = true
+            decorateSourceMap["tripleQuotedStrings"] = true
+            decorateSourceMap["regexLiterals"] = true
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("coffee"))
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = RUST_KEYWORDS
+            decorateSourceMap["cStyleComments"] = true
+            decorateSourceMap["multilineStrings"] = true
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("rc", "rs", "rust"))
+            shortcutStylePatterns = ArrayList()
+            fallthroughStylePatterns = ArrayList()
+            fallthroughStylePatterns.new(PR_STRING, Regex("^[\\s\\S]+"))
+            registerLangHandler(
+                CreateSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns),
+                listOf("regex")
+            )
+            /**
+             * Registers a language handler for Protocol Buffers as described at
+             * http://code.google.com/p/protobuf/.
+             *
+             * Based on the lexical grammar at
+             * http://research.microsoft.com/fsharp/manual/spec2.aspx#_Toc202383715
+             *
+             * @author mikesamuel@gmail.com
+             */
+            decorateSourceMap = HashMap()
+            decorateSourceMap["keywords"] = ("bytes,default,double,enum,extend,extensions,false,"
+                    + "group,import,max,message,option,"
+                    + "optional,package,repeated,required,returns,rpc,service,"
+                    + "syntax,to,true")
+            decorateSourceMap["types"] = Regex("^(bool|(double|s?fixed|[su]?int)(32|64)|float|string)\\b")
+            decorateSourceMap["cStyleComments"] = true
+            registerLangHandler(sourceDecorator(decorateSourceMap), listOf("proto"))
+        } catch (ex: Exception) {
+            throw ex
+        }
+    }
+
+    inner class CreateSimpleLexer(
+        shortcutStylePatterns: List<StylePattern>,
+        private var fallthroughStylePatterns: List<StylePattern>
+    ) {
+        private var shortcuts: MutableMap<Char, StylePattern> = HashMap()
+        private var tokenizer: Regex
+        private var nPatterns: Int
+
+        /** Given triples of [style, pattern, context] returns a lexing function,
+         * The lexing function interprets the patterns to find token boundaries and
+         * returns a decoration list of the form
+         * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
+         * where index_n is an index into the sourceCode, and style_n is a style
+         * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
+         * all characters in sourceCode[index_n-1:index_n].
+         *
+         * The stylePatterns is a list whose elements have the form
+         * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
+         *
+         * Style is a style constant like PR_PLAIN, or can be a string of the
+         * form 'lang-FOO', where FOO is a language extension describing the
+         * language of the portion of the token in $1 after pattern executes.
+         * E.g., if style is 'lang-lisp', and group 1 contains the text
+         * '(hello (world))', then that portion of the token will be passed to the
+         * registered lisp handler for formatting.
+         * The text before and after group 1 will be restyled using this decorator
+         * so decorators should take care that this doesn't result in infinite
+         * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
+         * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
+         * '<script>foo()<\/script>', which would cause the current decorator to
+        be called with '<script>' which would not match the same rule since
+        group 1 must not be empty, so it would be instead styled as PR_TAG by
+        the generic tag rule.  The handler registered for the 'js' extension would
+        then be called with 'foo()', and finally, the current decorator would
+        be called with '<\/script>' which would not match the original rule and
+        so the generic tag rule would identify it as a tag.
+
+        Pattern must only match prefixes, and if it matches a prefix, then that
+        match is considered a token with the same style.
+
+        Context is applied to the last non-whitespace, non-comment token
+        recognized.
+
+        Shortcut is an optional string of characters, any of which, if the first
+        character, gurantee that this pattern and only this pattern matches.
+
+        @param shortcutStylePatterns patterns that always start with
+        a known character.  Must have a shortcut string.
+        @param fallthroughStylePatterns patterns that will be tried in
+        order if the shortcut ones fail.  May have shortcuts.
+        </script> */
+        init {
+            val allPatterns: MutableList<StylePattern> = shortcutStylePatterns.toMutableList()
+            allPatterns.addAll(fallthroughStylePatterns)
+            val allRegexs: MutableList<Regex> = ArrayList()
+            val regexKeys: MutableMap<String, Any?> = HashMap()
+            allPatterns.forEach { pattern ->
+                val shortcutChars = pattern.shortcutChars
+                if (shortcutChars != null) {
+                    var c = shortcutChars.length
+                    while (--c >= 0) {
+                        shortcuts[shortcutChars[c]] = pattern
+                    }
+                }
+                val regex = pattern.regExp
+                val k = regex.pattern
+                if (regexKeys[k] == null) {
+                    allRegexs.add(regex)
+                    regexKeys[k] = Any()
+                }
+            }
+            allRegexs.add("[\u0000-\\uffff]".toRegex())
+            tokenizer = CombinePrefixPattern().combinePrefixPattern(allRegexs) //todo change this function
+            nPatterns = fallthroughStylePatterns.size
+        }
+
+        /**
+         * Lexes job.sourceCode and produces an output array job.decorations of
+         * style classes preceded by the position at which they start in
+         * job.sourceCode in order.
+         *
+         * @param job an object like <pre>{
+         * sourceCode: {string} sourceText plain text,
+         * basePos: {int} position of job.sourceCode in the larger chunk of
+         * sourceCode.
+         * }</pre>
+         */
+        fun decorate(job: Job) {
+            val sourceCode = job.getSourceCode()
+            val basePos = job.basePos
+
+            /** Even entries are positions in source in ascending order.  Odd enties
+             * are style markers (e.g., PR_COMMENT) that run from that position until
+             * the end.
+             * @type {Array.<number></number>|string>}
+             */
+            val decorations: MutableList<Any> = ArrayList(listOf(basePos, PR_PLAIN))
+            var pos = 0 // index into sourceCode
+            val tokens = Util.match(tokenizer, sourceCode, true)
+            val styleCache: MutableMap<String, String?> = HashMap()
+            var ti = 0
+            val nTokens = tokens.size
+            while (ti < nTokens) {
+                val token = tokens[ti]
+                var style = styleCache[token]
+                var match: List<String>? = null
+                var isEmbedded: Boolean
+                if (style != null) {
+                    isEmbedded = false
+                } else {
+                    var patternParts = shortcuts[token[0]]
+                    if (patternParts != null) {
+                        match = Util.match(patternParts.regExp, token, false)
+                        style = patternParts.tokenStyle
+                    } else {
+                        for (i in 0 until nPatterns) {
+                            patternParts = fallthroughStylePatterns[i]
+                            match = Util.match(patternParts.regExp, token, false)
+                            if (match.isNotEmpty()) {
+                                style = patternParts.tokenStyle
+                                break
+                            }
+                        }
+                        if (match!!.isEmpty()) {  // make sure that we make progress
+                            style = PR_PLAIN
+                        }
+                    }
+                    isEmbedded = style != null && style.length >= 5 && style.startsWith("lang-")
+                    if (isEmbedded && match.size <= 1) {
+                        isEmbedded = false
+                        style = PR_SOURCE
+                    }
+                    if (!isEmbedded) {
+                        styleCache[token] = style
+                    }
+                }
+                val tokenStart = pos
+                pos += token.length
+                if (!isEmbedded) {
+                    decorations.add(basePos + tokenStart)
+                    if (style != null) {
+                        decorations.add(style)
+                    }
+                } else {  // Treat group 1 as an embedded block of source code.
+                    val embeddedSource = match!![1]
+                    var embeddedSourceStart = token.indexOf(embeddedSource)
+                    var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length
+                    if (match.size > 2) {
+                        // If embeddedSource can be blank, then it would match at the
+                        // beginning which would cause us to infinitely recurse on the
+                        // entire token, so we catch the right context in match[2].
+                        embeddedSourceEnd = token.length - match[2].length
+                        embeddedSourceStart = embeddedSourceEnd - embeddedSource.length
+                    }
+                    val lang = style!!.substring(5)
+                    // Decorate the left of the embedded source
+                    appendDecorations(
+                        basePos + tokenStart,
+                        token.substring(0, embeddedSourceStart),
+                        this, decorations
+                    )
+                    // Decorate the embedded source
+                    appendDecorations(
+                        basePos + tokenStart + embeddedSourceStart,
+                        embeddedSource,
+                        getLexerForExtension(lang),
+                        decorations
+                    )
+                    // Decorate the right of the embedded section
+                    appendDecorations(
+                        basePos + tokenStart + embeddedSourceEnd,
+                        token.substring(embeddedSourceEnd),
+                        this, decorations
+                    )
+                }
+                ++ti
+            }
+            job.setDecorations(Util.removeDuplicates(decorations, job.getSourceCode()))
+        }
+    }
+
+    companion object {
+
+        // Keyword lists for various languages.
+        private const val FLOW_CONTROL_KEYWORDS = "break,continue,do,else,for,if,return,while"
+        private const val C_KEYWORDS = (FLOW_CONTROL_KEYWORDS + "," + "auto,case,char,const,default,"
+                + "double,enum,extern,float,goto,inline,int,long,register,short,signed,"
+                + "sizeof,static,struct,switch,typedef,union,unsigned,void,volatile")
+        private const val COMMON_KEYWORDS = (C_KEYWORDS + "," + "catch,class,delete,false,import,"
+                + "new,operator,private,protected,public,this,throw,true,try,typeof")
+        const val CPP_KEYWORDS = (COMMON_KEYWORDS + "," + "alignof,align_union,asm,axiom,bool,"
+                + "concept,concept_map,const_cast,constexpr,decltype,delegate,"
+                + "dynamic_cast,explicit,export,friend,generic,late_check,"
+                + "mutable,namespace,nullptr,property,reinterpret_cast,static_assert,"
+                + "static_cast,template,typeid,typename,using,virtual,where")
+        const val JAVA_KEYWORDS = (COMMON_KEYWORDS + ","
+                + "abstract,assert,boolean,byte,extends,final,finally,implements,import,"
+                + "instanceof,interface,null,native,package,strictfp,super,synchronized,"
+                + "throws,transient")
+        private const val KOTLIN_KEYWORDS = (JAVA_KEYWORDS + ","
+                + "as,as?,fun,in,!in,object,typealias,val,var,when,by,constructor,delegate,dynamic,field,"
+                + "file,get,init,set,value,where,actual,annotation,companion,crossinline,data,enum,expect,"
+                + "external,field,infix,inline,inner,internal,it,lateinit,noinline,open,operator,out,override,"
+                + "reified,sealed,suspend,tailrec,vararg")
+        const val RUST_KEYWORDS = (FLOW_CONTROL_KEYWORDS + "," + "as,assert,const,copy,drop,"
+                + "enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,"
+                + "pub,pure,ref,self,static,struct,true,trait,type,unsafe,use")
+        const val CSHARP_KEYWORDS = (JAVA_KEYWORDS + ","
+                + "as,base,by,checked,decimal,delegate,descending,dynamic,event,"
+                + "fixed,foreach,from,group,implicit,in,internal,into,is,let,"
+                + "lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,"
+                + "sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,"
+                + "var,virtual,where")
+        const val COFFEE_KEYWORDS = ("all,and,by,catch,class,else,extends,false,finally,"
+                + "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,"
+                + "throw,true,try,unless,until,when,while,yes")
+        const val JSCRIPT_KEYWORDS = (COMMON_KEYWORDS + ","
+                + "debugger,eval,export,function,get,null,set,undefined,var,with,"
+                + "Infinity,NaN")
+        const val PERL_KEYWORDS = ("caller,delete,die,do,dump,elsif,eval,exit,foreach,for,"
+                + "goto,if,import,last,local,my,next,no,our,print,package,redo,require,"
+                + "sub,undef,unless,until,use,wantarray,while,BEGIN,END")
+        const val PYTHON_KEYWORDS = (FLOW_CONTROL_KEYWORDS + "," + "and,as,assert,class,def,del,"
+                + "elif,except,exec,finally,from,global,import,in,is,lambda,"
+                + "nonlocal,not,or,pass,print,raise,try,with,yield,"
+                + "False,True,None")
+        const val RUBY_KEYWORDS = (FLOW_CONTROL_KEYWORDS + "," + "alias,and,begin,case,class,"
+                + "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,"
+                + "rescue,retry,self,super,then,true,undef,unless,until,when,yield,"
+                + "BEGIN,END")
+        const val SH_KEYWORDS = (FLOW_CONTROL_KEYWORDS + "," + "case,done,elif,esac,eval,fi,"
+                + "function,in,local,set,then,until")
+        const val ALL_KEYWORDS = (CPP_KEYWORDS + "," + KOTLIN_KEYWORDS + "," + CSHARP_KEYWORDS
+                + "," + JSCRIPT_KEYWORDS + "," + PERL_KEYWORDS + "," + PYTHON_KEYWORDS + "," + RUBY_KEYWORDS
+                + "," + SH_KEYWORDS)
+        val C_TYPES =
+            Regex("^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\\d*)\\b")
+        // token style names.  correspond to css classes
+        /**
+         * token style for a string literal
+         */
+        const val PR_STRING = "str"
+
+        /**
+         * token style for a keyword
+         */
+        const val PR_KEYWORD = "kwd"
+
+        /**
+         * token style for a comment
+         */
+        const val PR_COMMENT = "com"
+
+        /**
+         * token style for a type
+         */
+        const val PR_TYPE = "typ"
+
+        /**
+         * token style for a literal value.  e.g. 1, null, true.
+         */
+        const val PR_LITERAL = "lit"
+
+        /**
+         * token style for a punctuation string.
+         */
+        const val PR_PUNCTUATION = "pun"
+
+        /**
+         * token style for a plain text.
+         */
+        const val PR_PLAIN = "pln"
+
+        /**
+         * token style for an sgml tag.
+         */
+        const val PR_TAG = "tag"
+
+        /**
+         * token style for a markup declaration such as a DOCTYPE.
+         */
+        const val PR_DECLARATION = "dec"
+
+        /**
+         * token style for embedded source.
+         */
+        const val PR_SOURCE = "src"
+
+        /**
+         * token style for an sgml attribute name.
+         */
+        const val PR_ATTRIB_NAME = "atn"
+
+        /**
+         * token style for an sgml attribute value.
+         */
+        const val PR_ATTRIB_VALUE = "atv"
+
+        /**
+         * A class that indicates a section of markup that is not code, e.g. to allow
+         * embedding of line numbers within code listings.
+         */
+        const val PR_NOCODE = "nocode"
+
+        /**
+         * A set of tokens that can precede a regular expression literal in
+         * javascript
+         * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html
+         * has the full list, but I've removed ones that might be problematic when
+         * seen in languages that don't support regular expression literals.
+         *
+         *
+         * Specifically, I've removed any keywords that can't precede a regexp
+         * literal in a syntactically legal javascript program, and I've removed the
+         * "in" keyword since it's not a keyword in many languages, and might be used
+         * as a count of inches.
+         *
+         *
+         * The link above does not accurately describe EcmaScript rules since
+         * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
+         * very well in practice.
+         */
+        private const val REGEXP_PRECEDES_PATTERN =
+            "(?:^^\\.?|[+-]|[!=]=?=?|#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|\\{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*"
+
+        /**
+         * Apply the given language handler to sourceCode and add the resulting
+         * decorations to out.
+         * @param basePos the index of sourceCode within the chunk of source
+         * whose decorations are already present on out.
+         */
+        private fun appendDecorations(
+            basePos: Int,
+            sourceCode: String?,
+            langHandler: CreateSimpleLexer?,
+            out: MutableList<Any>
+        ) {
+            if (sourceCode == null) {
+                throw NullPointerException("argument 'sourceCode' cannot be null")
+            }
+            val job = Job()
+            job.setSourceCode(sourceCode)
+            job.basePos = basePos
+            langHandler!!.decorate(job)
+            out.addAll(job.getDecorations())
+        }
+    }
+
+}
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/Util.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/Util.kt
new file mode 100644
index 0000000..4a0413e
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/prettify/parser/Util.kt
@@ -0,0 +1,244 @@
+// Copyright (C) 2011 Chan Wai Shing
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.wakaztahir.codeeditor.prettify.parser
+
+/**
+ * Common Utilities.
+ * Some of the functions are port from JavaScript.
+ *
+ * @author Chan Wai Shing <cws1989></cws1989>@gmail.com>
+ */
+object Util {
+    /**
+     * Treat a variable as an boolean in JavaScript style. Note this function can
+     * only handle string, integer and boolean currently. All other data type, if
+     * null, return false, not null return true.
+     *
+     * @param var the variable to get value from
+     * @return the boolean value
+     */
+    fun getVariableValueAsBoolean(`var`: Any?): Boolean {
+        return if (`var` == null) {
+            false
+        } else if (`var` is String) {
+            `var`.isNotEmpty()
+        } else if (`var` is Int) {
+            `var` != 0
+        } else if (`var` is Boolean) {
+            `var`
+        } else {
+            true
+        }
+    }
+
+    /**
+     * Treat a variable as an integer in JavaScript style. Note this function can
+     * only handle integer and boolean currently.
+     *
+     * @param var the variable to get value from
+     * @return the integer value
+     * @throws IllegalArgumentException the data type of `var` is neither
+     * integer nor boolean.
+     */
+    fun getVariableValueAsInteger(`var`: Any?): Int {
+        if (`var` == null) {
+            throw NullPointerException("argument 'var' cannot be null")
+        }
+        var returnResult: Int? = -1
+        returnResult = if (`var` is Int) {
+            `var`
+        } else if (`var` is Boolean) {
+            // Javascript treat true as 1
+            if (`var`) 1 else 0
+        } else {
+            throw IllegalArgumentException("'var' is neither integer nor boolean")
+        }
+        return returnResult
+    }
+
+    /**
+     * Get all the matches for `string` compiled by `pattern`. If
+     * `isGlobal` is true, the return results will only include the
+     * group 0 matches. It is similar to string.match(regexp) in JavaScript.
+     *
+     * @param regex the regexp
+     * @param string the string
+     * @param isGlobal similar to JavaScript /g flag
+     *
+     * @return all matches
+     */
+    fun match(regex: Regex, string: String, isGlobal: Boolean): MutableList<String> {
+//        val matcher = Pattern.compile(regex.pattern).matcher(string)
+//        val matchesList = mutableListOf<String>()
+//        while (matcher.find()) {
+//            matchesList.add(matcher.group(0))
+//            if (!isGlobal) {
+//                var i : Int = 1
+//                val iEnd : Int = matcher.groupCount()
+//                while (i <= iEnd) {
+//                    matchesList.add(matcher.group(i))
+//                    i++
+//                }
+//            }
+//        }
+//        println("His Matches : ${matchesList.joinToString(",")}")
+//        println("Myy Matches : ${myMatchesList.joinToString(",")}")
+
+        //This code replaces the above commented code
+        val matchesList = mutableListOf<String>()
+        var startIndex = 0
+        var matchResult = regex.find(string, startIndex)
+        while (matchResult != null) {
+            matchResult.groups[0]?.value?.let { matchesList.add(it) }
+            if (!isGlobal) {
+                var i = 1
+                while (i < matchResult.groups.size) {
+                    matchResult.groups[i]?.value?.let { matchesList.add(it) }
+                    i++
+                }
+            }
+            startIndex = matchResult.range.last + 1
+            matchResult = regex.find(string, startIndex)
+        }
+        return matchesList
+    }
+
+    /**
+     * Test whether the `string` has at least one match by
+     * `pattern`.
+     *
+     * @param pattern the regexp
+     * @param string the string to test
+     *
+     * @return true if at least one match, false if no match
+     */
+    fun test(pattern: Regex, string: String): Boolean {
+        return pattern.find(string) != null
+    }
+
+    /**
+     * Join the `strings` into one string.
+     *
+     * @param strings the string list to join
+     *
+     * @return the joined string
+     */
+    fun join(strings: List<String>): String {
+        return join(strings.toTypedArray())
+    }
+
+    /**
+     * Join the `strings` into one string with `delimiter` in
+     * between.
+     *
+     * @param strings the string list to join
+     * @param delimiter the delimiter
+     *
+     * @return the joined string
+     */
+    fun join(strings: List<String>?, delimiter: String?): String {
+        if (strings == null) {
+            throw NullPointerException("argument 'strings' cannot be null")
+        }
+        return join(strings.toTypedArray(), delimiter)
+    }
+    /**
+     * Join the `strings` into one string with `delimiter` in
+     * between. It is similar to RegExpObject.test(string) in JavaScript.
+     *
+     * @param strings the string list to join
+     * @param delimiter the delimiter
+     *
+     * @return the joined string
+     */
+    /**
+     * Join the `strings` into one string.
+     *
+     * @param strings the string list to join
+     *
+     * @return the joined string
+     */
+    fun join(strings: Array<String>?, delimiter: String? = null): String {
+        if (strings == null) {
+            throw NullPointerException("argument 'strings' cannot be null")
+        }
+        val sb = StringBuilder()
+        if (strings.isNotEmpty()) {
+            sb.append(strings[0])
+            var i = 1
+            val iEnd = strings.size
+            while (i < iEnd) {
+                if (delimiter != null) {
+                    sb.append(delimiter)
+                }
+                sb.append(strings[i])
+                i++
+            }
+        }
+        return sb.toString()
+    }
+
+    /**
+     * Remove identical adjacent tags from `decorations`.
+     *
+     * @param decorations see [prettify.parser.Job.decorations]
+     * @param source the source code
+     *
+     * @return the `decorations` after treatment
+     *
+     * @throws IllegalArgumentException the size of `decoration` is not
+     * a multiple of 2
+     */
+    fun removeDuplicates(decorations: List<Any>?, source: String?): List<Any> {
+        if (decorations == null) {
+            throw NullPointerException("argument 'decorations' cannot be null")
+        }
+        if (source == null) {
+            throw NullPointerException("argument 'source' cannot be null")
+        }
+        require(decorations.size and 0x1 == 0) { "the size of argument 'decorations' should be a multiple of 2" }
+        val returnList: MutableList<Any> = ArrayList()
+
+        // use TreeMap to remove entrys with same pos
+        val orderedMap: MutableMap<Int, Any> = mutableMapOf()
+        var i = 0
+        val iEnd = decorations.size
+        while (i < iEnd) {
+            orderedMap[decorations[i] as Int] = decorations[i + 1]
+            i += 2
+        }
+
+        // remove adjacent style
+        var previousStyle: String? = null
+        for (pos in orderedMap.keys) {
+            val style = orderedMap[pos] as String?
+            if (previousStyle != null && previousStyle == style) {
+                continue
+            }
+            returnList.add(pos)
+            if (style != null) {
+                returnList.add(style)
+            }
+            previousStyle = style
+        }
+
+        // remove last zero length tag
+        val returnListSize = returnList.size
+        if (returnListSize >= 4 && returnList[returnListSize - 2] == source.length) {
+            returnList.removeAt(returnListSize - 2)
+            returnList.removeAt(returnListSize - 2)
+        }
+        return returnList
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/theme/CodeTheme.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/theme/CodeTheme.kt
new file mode 100644
index 0000000..9cae120
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/theme/CodeTheme.kt
@@ -0,0 +1,48 @@
+package com.wakaztahir.codeeditor.theme
+
+import androidx.compose.ui.text.SpanStyle
+import com.wakaztahir.codeeditor.parser.ParseResult
+import com.wakaztahir.codeeditor.prettify.parser.Prettify
+
+abstract class CodeTheme(private val colors: SyntaxColors) {
+
+    private val colorMap = hashMapOf(
+        Prettify.PR_TYPE to colors.type,
+        Prettify.PR_KEYWORD to colors.keyword,
+        Prettify.PR_LITERAL to colors.literal,
+        Prettify.PR_COMMENT to colors.comment,
+        Prettify.PR_STRING to colors.string,
+        Prettify.PR_PUNCTUATION to colors.punctuation,
+        Prettify.PR_PLAIN to colors.plain,
+        Prettify.PR_TAG to colors.tag,
+        Prettify.PR_DECLARATION to colors.declaration,
+        Prettify.PR_SOURCE to colors.source,
+        Prettify.PR_ATTRIB_NAME to colors.attrName,
+        Prettify.PR_ATTRIB_VALUE to colors.attrValue,
+        Prettify.PR_NOCODE to colors.nocode
+    )
+
+    open infix fun toSpanStyle(result: ParseResult): SpanStyle {
+        return SpanStyle(color = colorMap[result.styleKeysString] ?: kotlin.run {
+            Throwable("Missing color value for style key : ${result.styleKeysString}").printStackTrace()
+            colors.nocode
+        })
+    }
+}
+
+enum class CodeThemeType {
+    Default {
+        override fun getTheme(): CodeTheme = DefaultTheme()
+    },
+    Monokai {
+        override fun getTheme(): CodeTheme = MonokaiTheme()
+    };
+
+    internal abstract fun getTheme() : CodeTheme
+    private var _theme : CodeTheme? = null
+    val theme : CodeTheme
+        get() {
+            if(_theme == null) _theme = getTheme()
+            return _theme!!
+        }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/theme/EditorThemes.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/theme/EditorThemes.kt
new file mode 100644
index 0000000..db04b3d
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/theme/EditorThemes.kt
@@ -0,0 +1,39 @@
+package com.wakaztahir.codeeditor.theme
+
+import androidx.compose.ui.graphics.Color
+
+class DefaultTheme : CodeTheme(
+    colors = SyntaxColors(
+        type = Color(0xFF859900),
+        keyword = Color(0xFF268BD2),
+        literal = Color(0xFF269186),
+        comment = Color(0xFF93A1A1),
+        string = Color(0xFF269186),
+        punctuation = Color(0xFF586E75),
+        plain = Color(0xFF586E75),
+        tag = Color(0xFF859900),
+        declaration = Color(0xFF268BD2),
+        source = Color(0xFF586E75),
+        attrName = Color(0xFF268BD2),
+        attrValue = Color(0xFF269186),
+        nocode = Color(0xFF586E75),
+    )
+)
+
+class MonokaiTheme : CodeTheme(
+    colors = SyntaxColors(
+        type = Color(0xFFA7E22E),
+        keyword = Color(0xFFFA2772),
+        literal = Color(0xFF66D9EE),
+        comment = Color(0xFF76715E),
+        string = Color(0xFFE6DB74),
+        punctuation = Color(0xFFC1C1C1),
+        plain = Color(0xFFF8F8F0),
+        tag = Color(0xFFF92672),
+        declaration = Color(0xFFFA2772),
+        source = Color(0xFFF8F8F0),
+        attrName = Color(0xFFA6E22E),
+        attrValue = Color(0xFFE6DB74),
+        nocode = Color(0xFFF8F8F0),
+    )
+)
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/theme/SyntaxColors.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/theme/SyntaxColors.kt
new file mode 100644
index 0000000..514f8ef
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/theme/SyntaxColors.kt
@@ -0,0 +1,19 @@
+package com.wakaztahir.codeeditor.theme
+
+import androidx.compose.ui.graphics.Color
+
+class SyntaxColors(
+    val type: Color,
+    val keyword: Color,
+    val literal: Color,
+    val comment: Color,
+    val string: Color,
+    val punctuation: Color,
+    val plain: Color,
+    val tag: Color,
+    val declaration: Color,
+    val source: Color,
+    val attrName: Color,
+    val attrValue: Color,
+    val nocode: Color,
+)
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/utils/Annotation.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/utils/Annotation.kt
new file mode 100644
index 0000000..4c04c72
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/utils/Annotation.kt
@@ -0,0 +1,35 @@
+package com.wakaztahir.codeeditor.utils
+
+import androidx.compose.ui.text.AnnotatedString
+import com.wakaztahir.codeeditor.model.CodeLang
+import com.wakaztahir.codeeditor.parser.ParseResult
+import com.wakaztahir.codeeditor.prettify.PrettifyParser
+import com.wakaztahir.codeeditor.theme.CodeTheme
+
+fun List<ParseResult>.toAnnotatedString(theme: CodeTheme, source: String): AnnotatedString = AnnotatedString(
+    text = source,
+    spanStyles = map {
+        AnnotatedString.Range(theme toSpanStyle it, it.offset, it.offset + it.length)
+    }
+)
+
+fun parseCodeAsAnnotatedString(
+    parser: PrettifyParser,
+    theme: CodeTheme,
+    lang: String,
+    code: String
+): AnnotatedString = parser.parse(lang, code).toAnnotatedString(theme, code)
+
+fun parseCodeAsAnnotatedString(
+    parser: PrettifyParser,
+    theme: CodeTheme,
+    lang: CodeLang,
+    code: String
+): AnnotatedString = lang.langProvider?.let {
+    parser.parse(it, code).toAnnotatedString(theme, code)
+} ?: parseCodeAsAnnotatedString(
+    parser = parser,
+    theme = theme,
+    lang = lang.value.first(),
+    code = code,
+)
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/utils/utils.kt b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/utils/utils.kt
new file mode 100644
index 0000000..586dd52
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/wakaztahir/codeeditor/utils/utils.kt
@@ -0,0 +1,15 @@
+package com.wakaztahir.codeeditor.utils
+
+import com.wakaztahir.codeeditor.prettify.parser.StylePattern
+
+internal fun MutableList<StylePattern>.new(
+    tokenStyle: String,
+    regExp: Regex,
+    shortcutChars: String? = null,
+) = add(
+    StylePattern(
+        tokenStyle = tokenStyle,
+        regExp = regExp,
+        shortcutChars = shortcutChars
+    )
+)
diff --git a/composeApp/src/commonMain/resources/compose-multiplatform.xml b/composeApp/src/commonMain/resources/compose-multiplatform.xml
new file mode 100755
index 0000000..d7bf795
--- /dev/null
+++ b/composeApp/src/commonMain/resources/compose-multiplatform.xml
@@ -0,0 +1,36 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="600dp"
+    android:height="600dp"
+    android:viewportWidth="600"
+    android:viewportHeight="600">
+  <path
+      android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z"
+      android:fillColor="#041619"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z"
+      android:fillColor="#37BF6E"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z"
+      android:fillColor="#3870B2"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
+      android:strokeWidth="10"
+      android:fillColor="#00000000"
+      android:strokeColor="#083042"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
+      android:strokeWidth="10"
+      android:fillColor="#00000000"
+      android:strokeColor="#083042"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
+      android:strokeWidth="10"
+      android:fillColor="#00000000"
+      android:strokeColor="#083042"
+      android:fillType="nonZero"/>
+</vector>
diff --git a/composeApp/src/commonMain/resources/images/ic_chrome_close.png b/composeApp/src/commonMain/resources/images/ic_chrome_close.png
new file mode 100644
index 0000000..1949491
Binary files /dev/null and b/composeApp/src/commonMain/resources/images/ic_chrome_close.png differ
diff --git a/composeApp/src/commonMain/resources/images/ic_chrome_maximize.png b/composeApp/src/commonMain/resources/images/ic_chrome_maximize.png
new file mode 100644
index 0000000..672e9e4
Binary files /dev/null and b/composeApp/src/commonMain/resources/images/ic_chrome_maximize.png differ
diff --git a/composeApp/src/commonMain/resources/images/ic_chrome_minimize.png b/composeApp/src/commonMain/resources/images/ic_chrome_minimize.png
new file mode 100644
index 0000000..05534b4
Binary files /dev/null and b/composeApp/src/commonMain/resources/images/ic_chrome_minimize.png differ
diff --git a/composeApp/src/commonMain/resources/images/ic_chrome_unmaximize.png b/composeApp/src/commonMain/resources/images/ic_chrome_unmaximize.png
new file mode 100644
index 0000000..52b8f4e
Binary files /dev/null and b/composeApp/src/commonMain/resources/images/ic_chrome_unmaximize.png differ
diff --git a/composeApp/src/commonMain/resources/logo.png b/composeApp/src/commonMain/resources/logo.png
new file mode 100644
index 0000000..029df08
Binary files /dev/null and b/composeApp/src/commonMain/resources/logo.png differ
diff --git a/composeApp/src/commonMain/sqldelight/com/github/springeye/memosc/db/model/create_table.sq b/composeApp/src/commonMain/sqldelight/com/github/springeye/memosc/db/model/create_table.sq
new file mode 100644
index 0000000..e95efe8
--- /dev/null
+++ b/composeApp/src/commonMain/sqldelight/com/github/springeye/memosc/db/model/create_table.sq
@@ -0,0 +1,34 @@
+import kotlin.Boolean;
+import com.github.springeye.memosc.model.MemosRowStatus;
+import com.github.springeye.memosc.model.MemosVisibility;
+
+
+CREATE TABLE  IF NOT EXISTS Memo (
+    id INTEGER PRIMARY KEY,
+    createdTs INTEGER  NOT NULL,
+    creatorId INTEGER  NOT NULL,
+    creatorName text,
+    content text NOT NULL,
+    pinned INTEGER AS Boolean NOT NULL ,
+    rowStatus text AS MemosRowStatus NOT NULL,
+    visibility text AS MemosVisibility NOT NULL,
+    updatedTs INTEGER  NOT NULL
+);
+CREATE TABLE  IF NOT EXISTS RemoteKey (
+    creatorId INTEGER,
+    rowStatus TEXT AS MemosRowStatus,
+    visibility TEXT AS MemosVisibility
+);
+CREATE TABLE  IF NOT EXISTS Resource (
+    id INTEGER PRIMARY KEY ,
+    createdTs INTEGER  NOT NULL,
+    creatorId INTEGER  NOT NULL,
+    filename TEXT NOT NULL,
+    size INTEGER  NOT NULL,
+    type TEXT  NOT NULL,
+    updatedTs INTEGER  NOT NULL,
+    externalLink TEXT,
+    publicId TEXT,
+    memoId INTEGER
+);
+
diff --git a/composeApp/src/commonMain/sqldelight/com/github/springeye/memosc/db/model/query.sq b/composeApp/src/commonMain/sqldelight/com/github/springeye/memosc/db/model/query.sq
new file mode 100644
index 0000000..4f42664
--- /dev/null
+++ b/composeApp/src/commonMain/sqldelight/com/github/springeye/memosc/db/model/query.sq
@@ -0,0 +1,41 @@
+
+selectAllMemo:
+SELECT *
+FROM Memo;
+
+
+insertMemo:
+INSERT OR REPLACE INTO Memo(id,createdTs,creatorId,creatorName,content,pinned,rowStatus,visibility,updatedTs)
+VALUES ?;
+
+insertResource:
+INSERT OR REPLACE INTO Resource(id,createdTs,creatorId,filename,size,type,updatedTs,externalLink,publicId,memoId)
+VALUES ?;
+
+selectResourceByMemoId:
+SELECT *
+FROM Resource
+WHERE memoId = ?;
+
+
+deleteAllMemo:
+DELETE FROM Memo;
+
+deleteAllResource:
+DELETE FROM Resource;
+
+countMemos:
+SELECT count(*) FROM Memo;
+
+memos:
+SELECT *
+FROM Memo
+LIMIT :limit OFFSET :offset;
+
+insertOrReplaceRemoteKey:
+INSERT OR REPLACE INTO RemoteKey(creatorId,rowStatus,visibility)
+VALUES?;
+selectRemoteKeyByQuery:
+SELECT * FROM RemoteKey WHERE creatorId =? AND rowStatus=? AND visibility=?;
+deleteRemoteKeyByQuery:
+DELETE FROM RemoteKey WHERE creatorId =? AND rowStatus=? AND visibility=?;
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/Bootstrap.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/Bootstrap.kt
new file mode 100644
index 0000000..3019e17
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/Bootstrap.kt
@@ -0,0 +1,46 @@
+package com.github.springeye.memosc
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import com.github.springeye.memosc.core.Base64ImageFetcher
+import com.github.springeye.memosc.di.appModule
+import com.github.springeye.memosc.di.homeModule
+
+import io.kamel.core.config.KamelConfig
+import io.kamel.core.config.httpFetcher
+import io.kamel.core.config.takeFrom
+import io.kamel.image.config.Default
+import io.kamel.image.config.LocalKamelConfig
+import io.ktor.client.plugins.cookies.HttpCookies
+import org.koin.compose.KoinApplication
+import org.koin.compose.getKoin
+
+@Composable
+fun Bootstrap() {
+
+    KoinApplication(application = {
+        modules(appModule, homeModule)
+    }) {
+        val koin=getKoin()
+        val kamelConfig=KamelConfig {
+            takeFrom(KamelConfig.Default)
+            fetcher(Base64ImageFetcher())
+            httpFetcher {
+                install(HttpCookies){
+                    this.storage=koin.get()
+                }
+                httpCache(1024 * 1024 * 1024  /* 1024 MiB */)
+            }
+            // 100 by default
+            imageBitmapCacheSize = 500
+            // 100 by default
+            imageVectorCacheSize = 300
+            // 100 by default
+            svgCacheSize = 200
+        }
+        CompositionLocalProvider(LocalKamelConfig provides kamelConfig) {
+            DesktopApp()
+        }
+    }
+}
+
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/DataStorePreferences.desktop.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/DataStorePreferences.desktop.kt
new file mode 100644
index 0000000..b757958
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/DataStorePreferences.desktop.kt
@@ -0,0 +1,24 @@
+package com.github.springeye.memosc
+
+import androidx.datastore.core.DataMigration
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
+import androidx.datastore.preferences.core.Preferences
+import kotlinx.coroutines.CoroutineScope
+import java.io.File
+
+actual fun dataStorePreferences(
+    corruptionHandler: ReplaceFileCorruptionHandler<Preferences>?,
+    coroutineScope: CoroutineScope,
+    migrations: List<DataMigration<Preferences>>
+): DataStore<Preferences> = createDataStoreWithDefaults(
+    corruptionHandler = corruptionHandler,
+    migrations = migrations,
+    coroutineScope = coroutineScope,
+    path = { "${getPath()}/${SETTINGS_PREFERENCES}.preferences_pb" }
+)
+fun getPath():String{
+    val file = File(System.getProperty("user.home"), ".memosc")
+    if(!file.exists())file.mkdirs()
+    return file.toString()
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/DesktopApp.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/DesktopApp.kt
new file mode 100644
index 0000000..318f661
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/DesktopApp.kt
@@ -0,0 +1,57 @@
+package com.github.springeye.memosc
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import cafe.adriel.voyager.navigator.Navigator
+import cafe.adriel.voyager.transitions.ScaleTransition
+import com.github.springeye.memosc.ui.app.AppScreen
+import org.koin.compose.getKoin
+
+
+@Composable
+fun DesktopApp() {
+     val colors = lightColorScheme(
+         primary = Color(11, 107, 203),
+//         onPrimary = Color.White,
+         background = Color(244, 244, 245),
+//         onBackground = Color.White,
+         surface=Color(244, 244, 245),
+//         onSurface = Color.White,
+
+    )
+    val toastState = remember { mutableStateOf<ToastState>(ToastState.Hidden) }
+    val notification= createPopupNotification(toastState)
+    val koin=getKoin()
+    CompositionLocalProvider(
+    ){
+    MaterialTheme(colorScheme = colors) {
+        Surface(
+            modifier = Modifier.fillMaxSize()
+        ) {
+            CompositionLocalProvider(
+                LocalNotification provides notification
+            ){
+                Navigator(AppScreen){ navigator->
+                    ScaleTransition(navigator)
+                }
+            }
+            Toast(toastState)
+        }
+    }
+    }
+}
+ val LocalNotification = staticCompositionLocalOf<PopupNotification> {
+    noLocalProvidedFor("LocalNotification")
+}
+private fun noLocalProvidedFor(name: String): Nothing {
+    error("CompositionLocal $name not present")
+}
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/Platform.jvm.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/Platform.jvm.kt
new file mode 100644
index 0000000..e24c646
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/Platform.jvm.kt
@@ -0,0 +1,7 @@
+package com.github.springeye.memosc
+
+class JVMPlatform: Platform {
+    override val name: String = "Java ${System.getProperty("java.version")}"
+}
+
+actual fun getPlatform(): Platform = JVMPlatform()
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/PopupNotification.desktop.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/PopupNotification.desktop.kt
new file mode 100644
index 0000000..690f914
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/PopupNotification.desktop.kt
@@ -0,0 +1,20 @@
+package com.github.springeye.memosc
+
+
+import androidx.compose.runtime.MutableState
+
+actual fun createPopupNotification(state: MutableState<ToastState>): PopupNotification {
+    return object: PopupNotification {
+        override fun showPopUpMessage(text: String) {
+            state.value= ToastState.Shown(text)
+        }
+
+        override fun showLoading(text: String) {
+            state.value= ToastState.Loading(text)
+        }
+
+        override fun hideLoading() {
+            state.value= ToastState.Hidden
+        }
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Card.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Card.kt
new file mode 100644
index 0000000..0fb316f
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Card.kt
@@ -0,0 +1,71 @@
+package com.github.springeye.memosc.components
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Scaffold
+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.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.onPointerEvent
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+@Preview
+@Composable
+fun PreviewCard() {
+    Scaffold {
+        Column(modifier = Modifier.fillMaxSize().background(Color.Gray)) {
+            CardItem(radius = 10.dp,
+                hoverColor = Color.Blue,
+
+                ) {
+                Text("asdfasfas")
+            }
+        }
+    }
+}
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun  CardItem(
+    radius: Dp =15.dp,
+    color: Color=Color.White,
+    hoverColor: Color=color,
+    borderColor: Color=color,
+    hoverBorderColor: Color=color,
+    borderWidth:Dp=0.dp,
+    paddingValues: PaddingValues= PaddingValues(10.dp),
+    modifier:Modifier = Modifier, content: @Composable() (BoxScope.() -> Unit)) {
+    var hover by remember {
+        mutableStateOf(false)
+    }
+    var background = Modifier.clip(RoundedCornerShape(radius))
+        .onPointerEvent(PointerEventType.Enter) {
+            hover = true;
+        }
+        .onPointerEvent(PointerEventType.Exit) {
+            hover = false;
+        }
+        .background(if (hover) hoverColor else color)
+
+    Box(modifier = background
+        .then(modifier)
+        .border(borderWidth,if (hover) hoverBorderColor else borderColor,shape = RoundedCornerShape(radius))
+        .padding(paddingValues),
+        content = content)
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/EditMemo.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/EditMemo.kt
new file mode 100644
index 0000000..0bcac8a
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/EditMemo.kt
@@ -0,0 +1,355 @@
+package com.github.springeye.memosc.components
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.Icon
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Attachment
+import androidx.compose.material.icons.filled.Cancel
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.ExpandLess
+import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material.icons.filled.Lock
+import androidx.compose.material.icons.filled.Photo
+import androidx.compose.material.icons.filled.Send
+import androidx.compose.material.icons.filled.Tag
+import androidx.compose.material3.Button
+import androidx.compose.material3.Divider
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.input.pointer.PointerIcon
+import androidx.compose.ui.input.pointer.pointerHoverIcon
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import com.darkrockstudios.libraries.mpfilepicker.FilePicker
+import com.github.springeye.memosc.LocalNotification
+import com.github.springeye.memosc.model.Memo
+import com.github.springeye.memosc.model.MemosVisibility
+import com.github.springeye.memosc.model.Resource
+import io.kamel.image.KamelImage
+import io.kamel.image.asyncPainterResource
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import javax.swing.Icon
+
+abstract class EditMemoCallback {
+    open suspend fun onSubmit(state:EditMemoState,editId:Long,content: String, resources: List<Resource>, visibility: MemosVisibility){}
+    open suspend fun onUpload(state:EditMemoState,path: String){}
+    open suspend fun onContentChange(state:EditMemoState,text:String){}
+    open suspend fun onRemoveResourced(state:EditMemoState, resource:Resource){}
+    open suspend fun onCancle(state:EditMemoState){}
+    companion object{
+        val DEFAULT = object:EditMemoCallback(){}
+    }
+}
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
+@Preview
+@Composable
+fun EditMemo(
+    state: EditMemoState = rememberEditMemoState(host = ""),
+    ) {
+    LaunchedEffect(state){
+        snapshotFlow{
+            state.content
+        }.collect{
+            state.callback.onContentChange(state,it)
+        }
+
+    }
+    val scope = state.scope
+    val notification = LocalNotification.current
+    CardItem(
+        radius = 10.dp,
+        borderColor = Color(0xccCCCCCC),
+        borderWidth = 1.dp,
+        paddingValues = PaddingValues(start = 10.dp, end = 10.dp, bottom = 10.dp),
+        hoverBorderColor = Color(0xccCCCCCC),
+    ) {
+        Column(Modifier.fillMaxWidth().wrapContentHeight()) {
+            ITextField(
+                state.content,
+                onValueChange = state::updateContent,
+                minLines = 2,
+                maxLines = 10,
+                placeholder = {
+                    Text(
+                        "任何想法",
+                        style = TextStyle.Default.copy(color = Color(156, 163, 175))
+                    )
+                },
+                modifier = Modifier
+                    .fillMaxWidth(), // Here I have decreased the height
+                shape = RoundedCornerShape(0.dp),
+                colors = TextFieldDefaults.colors(
+                    focusedContainerColor = Color.White,
+                    unfocusedContainerColor = Color.White,
+                    disabledContainerColor = Color.White,
+                    disabledIndicatorColor = Color.Transparent,
+                    focusedIndicatorColor = Color.Transparent,
+                    unfocusedIndicatorColor = Color.Transparent,
+
+                    ),
+
+                )
+            var showFilePicker by remember { mutableStateOf(false) }
+            Row {
+                Icon(Icons.Default.Tag, "")
+                Icon(Icons.Default.Photo, "", Modifier.padding(start = 10.dp).clickable {
+                    showFilePicker = true
+                })
+                Icon(Icons.Default.Attachment, "", Modifier.padding(start = 10.dp))
+            }
+            Row {
+
+
+                val fileType = listOf("jpg", "png")
+                FilePicker(show = showFilePicker, fileExtensions = fileType) { file ->
+                    showFilePicker = false
+//                        println(file?.path)
+                    file?.path?.let {
+                        scope.launch {
+                            state.callback.onUpload(state,it)
+                        }
+                    }
+                    // do something with the file
+                }
+            }
+            Divider(Modifier.padding(top = 10.dp, bottom = 10.dp))
+
+            FlowRow() {
+                state.resources.forEach { item ->
+                    AnimatedContent(targetState = item) {
+                        ResourceItem(item, state.host) {
+                            state.removeResource(it)
+                            scope.launch {
+                                state.callback.onRemoveResourced(state, it)
+                            }
+                        }
+                    }
+                }
+            }
+            Row(Modifier.padding(vertical = 10.dp)) {
+                VisibilityButton()
+                Spacer(Modifier.weight(1f))
+                if(state.editId>0){
+                    SubmitButton("取消",Icons.Default.Cancel,true,Modifier.padding(end = 10.dp)){
+                        scope.launch {
+                            state.callback.onCancle(state)
+                        }
+                    }
+                }
+                SubmitButton("保存",Icons.Default.Send,state.content.isNotEmpty()){
+                    scope.launch {
+                        state.callback.onSubmit(
+                            state,
+                            state.editId,
+                            state.content,
+                            state.resources,
+                            state.visibility
+                        )
+                    }
+                }
+            }
+
+        }
+    }
+}
+
+@Composable
+fun SubmitButton(text: String, icon: ImageVector, enable: Boolean,modifier: Modifier=Modifier, onSubmit: () -> Unit) {
+    Button(
+        enabled = enable,
+        onClick = onSubmit,
+        contentPadding = PaddingValues(horizontal = 10.dp),
+        shape = RoundedCornerShape(5.dp),
+        modifier = modifier
+            .defaultMinSize(minWidth = 1.dp, minHeight = 1.dp)
+            .height(30.dp)
+            .pointerHoverIcon(PointerIcon.Hand)
+    ) {
+        Row(verticalAlignment = Alignment.CenterVertically) {
+            Text(text)
+            Icon(
+                icon, "",
+                tint = Color.White,
+                modifier = Modifier
+                    .size(20.dp)
+                    .padding(start = 5.dp)
+            )
+        }
+    }
+}
+
+@Composable
+fun VisibilityButton() {
+    val notification = LocalNotification.current
+    var expanded by remember { mutableStateOf(false) }
+    Box(Modifier.clip(RoundedCornerShape(5.dp))
+        .background(Color(0x99eeeeee))
+        .clickable {
+            expanded = !expanded
+        }.padding(vertical = 5.dp, horizontal = 10.dp)
+    ) {
+        Row(
+            modifier = Modifier.wrapContentWidth()
+        ) {
+            val tint = Color(156, 163, 175)
+            Icon(
+                imageVector = Icons.Default.Lock,
+                contentDescription = "More",
+                modifier = Modifier.size(20.dp),
+                tint = tint
+            )
+            Text(
+                "私有",
+                style = TextStyle.Default.copy(color = tint),
+                modifier = Modifier.padding(horizontal = 5.dp)
+            )
+            Icon(
+                imageVector = if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
+                contentDescription = "More",
+                modifier = Modifier.size(20.dp),
+                tint = tint
+
+
+            )
+        }
+        DropdownMenu(
+            expanded = expanded,
+            onDismissRequest = { expanded = false }
+        ) {
+            DropdownMenuItem(
+                text = { Text("Load") },
+                onClick = { notification.showPopUpMessage("load") }
+            )
+            DropdownMenuItem(
+                text = { Text("Save") },
+                onClick = { notification.showPopUpMessage("save") }
+            )
+        }
+    }
+}
+
+@Composable
+fun ResourceItem(item: Resource, host: String,removeResource:  (Resource)->Unit) {
+
+    val scope = rememberCoroutineScope()
+    Row(
+        modifier = Modifier
+            .wrapContentWidth()
+            .padding(end = 5.dp)
+            .background(MaterialTheme.colorScheme.background),
+        verticalAlignment = Alignment.CenterVertically
+    ) {
+        KamelImage(
+            asyncPainterResource(item.uri(host)),
+            "",
+            Modifier.size(20.dp)
+
+        )
+        Text(
+            item.filename,
+            style = MaterialTheme.typography.bodySmall,
+            modifier = Modifier.padding(start = 5.dp)
+        )
+        Icon(Icons.Default.Close, "", modifier = Modifier.clickable {
+            removeResource(item)
+        })
+    }
+}
+
+class EditMemoState(
+    private val initContent:String="",
+    private val initResources:List<Resource> = listOf(),
+    private val initVisibility: MemosVisibility = MemosVisibility.PUBLIC,
+    internal val host:String,
+    internal val callback:EditMemoCallback=EditMemoCallback.DEFAULT,
+    internal val scope: CoroutineScope
+    ){
+    var editId by mutableStateOf(0L)
+    var content by mutableStateOf(initContent)
+        private set
+    var resources = mutableStateListOf<Resource>(*initResources.toTypedArray())
+        private set
+    var visibility by mutableStateOf(initVisibility)
+        private set
+    fun updateContent(content: String) {
+        this.content = content
+    }
+    fun addResource(resource: Resource){
+        resources.add(resource);
+    }
+    fun removeResource(resource: Resource) {
+        resources.remove(resource)
+    }
+    fun updateVisibility(visibility: MemosVisibility) {
+        this.visibility = visibility
+    }
+    fun setNewMemo(memo: Memo?=null){
+        if(memo!=null){
+            editId=memo.id;
+            this.content = memo.content
+            this.resources.apply {
+                clear()
+                addAll(memo.resourceList)
+            }
+            this.visibility = memo.visibility
+        }else{
+            editId=0;
+            this.content="";
+            this.resources.clear()
+        }
+    }
+
+}
+
+@Composable
+fun rememberEditMemoState(initMemo:Memo,host:String, callback:EditMemoCallback=EditMemoCallback.DEFAULT, scope: CoroutineScope = rememberCoroutineScope(),) :EditMemoState{
+    val onCallback by rememberUpdatedState(callback)
+    return remember(initMemo,host,scope) {EditMemoState(initMemo.content,initMemo.resourceList,initMemo.visibility,host,onCallback,scope) }
+}
+@Composable
+fun rememberEditMemoState(initContent:String="", initResources:List<Resource> = listOf(),
+                          initVisibility: MemosVisibility = MemosVisibility.PUBLIC,
+                          scope: CoroutineScope = rememberCoroutineScope(),
+                          host:String, callback:EditMemoCallback=EditMemoCallback.DEFAULT) :EditMemoState {
+    val onCallback by rememberUpdatedState(callback)
+    return remember(initContent,initResources,initVisibility,host,scope) {EditMemoState(initContent,initResources,initVisibility,host,onCallback,scope) }
+}
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Heatmap.desktop.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Heatmap.desktop.kt
new file mode 100644
index 0000000..96a465d
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Heatmap.desktop.kt
@@ -0,0 +1,31 @@
+package com.github.springeye.memosc.components
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.Constraints
+import java.time.LocalDate
+import java.time.temporal.ChronoUnit
+import java.time.temporal.TemporalAdjusters
+import java.time.temporal.WeekFields
+import java.util.Locale
+import kotlin.math.ceil
+
+actual fun countHeatmap(constraints: Constraints): Int {
+    val cellSize = ceil(constraints.maxWidth.toDouble() / 7).toInt()
+    if (cellSize <= 0) {
+        return 0
+    }
+    val rows = constraints.maxHeight / cellSize
+    val fullCells = rows * 7
+
+    val firstDayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek
+    val firstDayOfThisWeek = LocalDate.now().with(TemporalAdjusters.previousOrSame(firstDayOfWeek))
+    val lastColumn = ChronoUnit.DAYS.between(firstDayOfThisWeek, LocalDate.now()).toInt() + 1
+    if (lastColumn % 7 == 0) {
+        return fullCells
+    }
+    return fullCells - 7 + lastColumn
+}
+
+@Composable
+actual fun aa() {
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/HeatmapStat.desktop.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/HeatmapStat.desktop.kt
new file mode 100644
index 0000000..709a756
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/HeatmapStat.desktop.kt
@@ -0,0 +1,52 @@
+package com.github.springeye.memosc.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.github.springeye.memosc.model.DailyUsageStat
+import kotlinx.datetime.toJavaLocalDate
+import java.time.LocalDate
+
+@Composable
+actual fun HeatmapStat2(day: DailyUsageStat) {
+    val date = day.date.toJavaLocalDate()
+    val borderWidth = if (date == LocalDate.now()) 1.dp else 0.dp
+    val color = when (day.count) {
+        0 -> Color(0xffeaeaea)
+        1 -> Color(0xff9be9a8)
+        2 -> Color(0xff40c463)
+        in 3..4 -> Color(0xff30a14e)
+        else -> Color(0xff216e39)
+    }
+    var modifier = Modifier
+        .fillMaxSize()
+        .aspectRatio(1F, true)
+        .clip(RoundedCornerShape(2.dp))
+        .background(color = color)
+    var text=""
+    if (date == LocalDate.now()) {
+        modifier = modifier.border(
+            borderWidth,
+            MaterialTheme.colorScheme.onBackground,
+            shape = RoundedCornerShape(2.dp)
+        )
+//        text="${date.dayOfMonth}";
+    }
+
+    Box(modifier = modifier, contentAlignment = Alignment.Center){
+        Text(text, textAlign = TextAlign.Center, style = MaterialTheme.typography.bodySmall.copy(fontSize = 6.sp ),)
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Markdown.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Markdown.kt
new file mode 100644
index 0000000..a229b66
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Markdown.kt
@@ -0,0 +1,121 @@
+package com.github.springeye.memosc.components
+
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.text.InlineTextContent
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.text.*
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.sp
+import com.github.springeye.memosc.ext.appendMarkdown
+import org.intellij.markdown.MarkdownElementTypes
+import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
+import org.intellij.markdown.parser.MarkdownParser
+
+@OptIn(ExperimentalTextApi::class)
+@Composable
+fun Markdown(
+    text: String,
+    modifier: Modifier = Modifier,
+    textAlign: TextAlign? = null,
+    imageContent: @Composable (url: String) -> Unit = {},
+    checkboxChange: (checked: Boolean, startOffset: Int, endOffset: Int) -> Unit = { _, _, _ -> }
+) {
+    val linkColor = MaterialTheme.colorScheme.primary
+    val bulletColor = MaterialTheme.colorScheme.tertiary
+    val headlineLarge = MaterialTheme.typography.headlineLarge
+    val headlineMedium = MaterialTheme.typography.headlineMedium
+    val headlineSmall = MaterialTheme.typography.headlineSmall
+    val uriHandler = LocalUriHandler.current
+
+    BoxWithConstraints {
+        val (annotatedString, inlineContent) = remember(text, maxWidth) {
+            val markdownAst = MarkdownParser(GFMFlavourDescriptor()).parse(MarkdownElementTypes.MARKDOWN_FILE, text, true)
+            val builder = AnnotatedString.Builder()
+            val inlineContent = HashMap<String, InlineTextContent>()
+
+            builder.appendMarkdown(
+                markdownText = text,
+                node = markdownAst,
+                depth = 0,
+                linkColor = linkColor,
+                onImage = { key, url ->
+                    inlineContent[key] = InlineTextContent(
+                        Placeholder(maxWidth.value.sp, (maxWidth.value * 9f / 16f).sp, PlaceholderVerticalAlign.AboveBaseline),
+                    ) {
+                        imageContent(url)
+                    }
+                },
+                onCheckbox = { key, startOffset, endOffset ->
+                    inlineContent[key] = InlineTextContent(
+                        Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.Center)
+                    ) {
+                        val checkboxText = text.substring(startOffset, endOffset)
+                        Checkbox(checked = checkboxText.length > 1 && checkboxText[1] != ' ', onCheckedChange = {
+                            checkboxChange(it, startOffset, endOffset)
+                        })
+                    }
+                },
+                maxWidth = maxWidth.value,
+                bulletColor = bulletColor,
+                headlineLarge = headlineLarge,
+                headlineMedium = headlineMedium,
+                headlineSmall = headlineSmall
+            )
+
+            Pair(builder.toAnnotatedString(), inlineContent)
+        }
+
+        ClickableText(
+            text = annotatedString,
+            modifier = modifier,
+            textAlign = textAlign,
+            inlineContent = inlineContent,
+            onClick = {
+                annotatedString.getUrlAnnotations(it, it)
+                    .firstOrNull()?.let { url ->
+                        uriHandler.openUri(url.item.url)
+                    }
+            }
+        )
+    }
+
+
+}
+
+@Composable
+fun ClickableText(
+    text: AnnotatedString,
+    modifier: Modifier = Modifier,
+    textAlign: TextAlign? = null,
+    inlineContent: Map<String, InlineTextContent> = mapOf(),
+    onClick: (Int) -> Unit
+) {
+    val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
+    val pressIndicator = Modifier.pointerInput(onClick) {
+        detectTapGestures { pos ->
+            layoutResult.value?.let { layoutResult ->
+                onClick(layoutResult.getOffsetForPosition(pos))
+            }
+        }
+    }
+
+    Text(
+        text = text,
+        modifier = modifier.then(pressIndicator),
+        textAlign = textAlign,
+//        style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onPrimary),
+        inlineContent = inlineContent,
+        onTextLayout = {
+            layoutResult.value = it
+        }
+    )
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/MemoList.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/MemoList.kt
new file mode 100644
index 0000000..aa36b24
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/MemoList.kt
@@ -0,0 +1,263 @@
+package com.github.springeye.memosc.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.Icon
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.MoreVert
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Surface
+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.graphics.Color
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import app.cash.paging.compose.LazyPagingItems
+import com.github.springeye.memosc.model.Memo
+import com.mikepenz.markdown.compose.LocalMarkdownColors
+import com.mikepenz.markdown.compose.LocalMarkdownTypography
+import com.mikepenz.markdown.compose.components.MarkdownComponent
+import com.mikepenz.markdown.compose.components.markdownComponents
+import com.mikepenz.markdown.model.markdownColor
+import com.wakaztahir.codeeditor.prettify.PrettifyParser
+import com.wakaztahir.codeeditor.theme.CodeThemeType
+import com.wakaztahir.codeeditor.utils.parseCodeAsAnnotatedString
+import dev.snipme.highlights.Highlights
+import dev.snipme.highlights.model.BoldHighlight
+import dev.snipme.highlights.model.ColorHighlight
+import dev.snipme.highlights.model.SyntaxLanguage
+import dev.snipme.highlights.model.SyntaxThemes
+import kotlinx.datetime.Instant
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.toJavaLocalDateTime
+import kotlinx.datetime.toLocalDateTime
+import org.intellij.markdown.ast.ASTNode
+import java.time.format.DateTimeFormatter
+
+@Composable
+fun MemoList(lazyPagingItems: LazyPagingItems<Memo>,host:String,onEdit: (memo:Memo?) -> Unit={ },onPin: (memo: Memo) -> Unit,onRemove: (memo: Memo) -> Unit) {
+    LazyColumn(modifier = Modifier.fillMaxWidth()) {
+        items(lazyPagingItems.itemCount) { index ->
+            val it = lazyPagingItems[index]!!
+            Box(Modifier.padding(top = 10.dp)) {
+                CardItem(
+                    radius = 10.dp,
+                    modifier = Modifier.fillMaxWidth(),
+                    borderColor = Color.White,
+                    borderWidth = 1.dp,
+                    hoverBorderColor = Color(0xccCCCCCC),
+                ) {
+                    Column {
+                        val dateTime =
+                            Instant.fromEpochSeconds(it.createdTs)
+                                .toLocalDateTime(
+                                    TimeZone.currentSystemDefault()
+                                ).toJavaLocalDateTime()
+                                .format(DateTimeFormatter.ofPattern("YYYY-MM-DD HH:mm:ss"))
+                        Row(modifier = Modifier) {
+                            Text(
+                                dateTime,
+                                style = TextStyle.Default.copy(
+                                    color = Color(
+                                        156,
+                                        163,
+                                        175
+                                    )
+                                )
+                            )
+                            Spacer(Modifier.weight(1F))
+                            ItemEditMenu(it,onEdit,onRemove,onPin)
+                        }
+
+                        val components = markdownComponents(
+                            codeBlock = codeBlockComponent,
+                            codeFence = codeFenceBlockComponent
+                        )
+
+                        com.mikepenz.markdown.compose.Markdown(
+                            it.content, modifier = Modifier.fillMaxWidth(),
+                            components = components,
+                            colors = markdownColor(codeText = Color.Black),
+                            //                                typography = markdownTypography(code = MaterialTheme.typography.body2.copy(fontFamily = FontFamily.Monospace, color = Color.Black))
+                        )
+                        MemoResourceContent(memo = it,host);
+                    }
+                }
+            }
+        }
+    }
+}
+
+private val codeBlockComponent: MarkdownComponent = {
+    MarkdownCodeBlock(it.content, it.node)
+}
+private val codeFenceBlockComponent: MarkdownComponent = {
+    MarkdownCodeFenceBlock(it.content, it.node)
+}
+
+@Composable
+internal fun MarkdownCodeFenceBlock(
+    content: String,
+    node: ASTNode
+) {
+    // CODE_FENCE_START, FENCE_LANG, {content}, CODE_FENCE_END
+    if (node.children.size >= 3) {
+
+        val start = node.children[2].startOffset
+        val end = node.children[node.children.size - 2].endOffset
+        val langStart = node.children[1].startOffset
+        val langEnd = node.children[1].endOffset
+        val lang = content.substring(langStart, langEnd)
+        MarkdownCode(content.subSequence(start, end).toString().replaceIndent(), lang)
+    } else {
+        // invalid code block, skipping
+    }
+}
+
+@Composable
+internal fun MarkdownCodeBlock(
+    content: String,
+    node: ASTNode
+) {
+    val start = node.children[0].startOffset
+    val end = node.children[node.children.size - 1].endOffset
+    MarkdownCode(content.subSequence(start, end).toString().replaceIndent(), "txt")
+}
+
+@Composable
+private fun MarkdownCode(
+    code: String,
+    lang: String,
+    style: TextStyle = LocalMarkdownTypography.current.code
+) {
+    val color = LocalMarkdownColors.current.codeText
+    var language = lang
+    language = language.replace("^golang$".toRegex(), "go")
+//        language=language.replace("^java$".toRegex(),"kotlin")
+    val backgroundCodeColor = LocalMarkdownColors.current.codeBackground
+    Surface(
+        color = backgroundCodeColor,
+        shape = RoundedCornerShape(8.dp),
+        modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp)
+    ) {
+
+        val syn = SyntaxLanguage.getByName(language)
+        if (syn != null) {
+            val highlights = Highlights.Builder()
+                .code(code)
+                .theme(SyntaxThemes.darcula())
+                .language(syn)
+                .build()
+                .getHighlights()
+            Text(
+                text = buildAnnotatedString {
+                    append(code)
+                    highlights
+                        .filterIsInstance<ColorHighlight>()
+                        .forEach {
+                            addStyle(
+                                SpanStyle(color = Color(it.rgb).copy(alpha = 1f)),
+                                start = it.location.start,
+                                end = it.location.end,
+                            )
+                        }
+
+                    highlights
+                        .filterIsInstance<BoldHighlight>()
+                        .forEach {
+                            addStyle(
+                                SpanStyle(fontWeight = FontWeight.Bold),
+                                start = it.location.start,
+                                end = it.location.end,
+                            )
+                        }
+                },
+                color = color,
+                modifier = Modifier.horizontalScroll(rememberScrollState()).padding(8.dp),
+                style = style
+            )
+        } else {
+
+            val parser = remember { PrettifyParser() }
+            var themeState by remember { mutableStateOf(CodeThemeType.Monokai) }
+            val theme = remember(themeState) { themeState.theme }
+            val parsedCode: AnnotatedString = remember {
+                if (!parser.isSupport(language)) {
+                    AnnotatedString(code)
+                } else {
+                    parseCodeAsAnnotatedString(
+                        parser = parser,
+                        theme = theme,
+                        lang = language,
+                        code = code
+                    )
+                }
+            }
+
+            Text(
+                parsedCode,
+                color = color,
+                modifier = Modifier.horizontalScroll(rememberScrollState()).padding(8.dp),
+                style = style
+            )
+        }
+
+    }
+}
+
+@Composable
+fun ItemEditMenu(memo: Memo,onEdit: (memo:Memo?) -> Unit={ },onRemove: (memo:Memo) -> Unit={ },onPin: (memo:Memo) -> Unit={ }) {
+    var expanded by remember { mutableStateOf(false) }
+    Box() {
+        Icon(Icons.Default.MoreVert, "More", Modifier.clickable {
+            expanded = !expanded
+        })
+        DropdownMenu(
+            expanded = expanded,
+            onDismissRequest = { expanded = false }
+        ) {
+            DropdownMenuItem(
+                text = { Text("编辑") },
+                onClick = {
+                    onEdit(memo)
+                    expanded = false
+                }
+            )
+            DropdownMenuItem(
+                text = { Text("置顶") },
+                onClick = {
+                    onPin(memo)
+                    expanded = false
+                }
+            )
+            DropdownMenuItem(
+                text = { Text("删除", style = TextStyle(color = Color.Red)) },
+                onClick = {
+                    onRemove(memo)
+                    expanded = false
+                },
+
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Resources.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Resources.kt
new file mode 100644
index 0000000..eb0ac63
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/components/Resources.kt
@@ -0,0 +1,98 @@
+package com.github.springeye.memosc.components
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Attachment
+import androidx.compose.material3.AssistChip
+import androidx.compose.material3.AssistChipDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.unit.dp
+import cafe.adriel.voyager.koin.getScreenModel
+import com.github.springeye.memosc.model.Memo
+import com.github.springeye.memosc.model.Resource
+import com.github.springeye.memosc.ui.AppModel
+import io.kamel.image.KamelImage
+import io.kamel.image.asyncPainterResource
+import kotlin.math.ceil
+import kotlin.math.min
+
+@Composable
+fun MemoResourceContent(memo: Memo,host:String) {
+    memo.resourceList?.let { resourceList ->
+        val cols = min(3, resourceList.size)
+        val imageList = resourceList.filter { it.type.startsWith("image/") }
+        if (imageList.isNotEmpty()) {
+            val rows = ceil(imageList.size.toFloat() / cols).toInt()
+            for (rowIndex in 0 until rows) {
+                Row {
+                    for (colIndex in 0 until cols) {
+                        val index = rowIndex * cols + colIndex
+                        if (index < imageList.size) {
+                            Box(modifier = Modifier.fillMaxWidth(1f / (cols - colIndex))) {
+                                val uri = imageList[index].uri(host)
+                                KamelImage(
+                                    asyncPainterResource(uri),
+                                    onLoading = {
+                                        Text("加载中")
+                                    },
+                                    onFailure = {
+                                        it.printStackTrace()
+                                        Text("加载失败")
+                                    },
+                                    contentDescription = null,
+                                    contentScale = ContentScale.FillWidth,
+                                    modifier = Modifier
+                                        .padding(2.dp)
+                                        .fillMaxWidth()
+                                        .clip(RoundedCornerShape(4.dp))
+                                )
+                            }
+                        } else {
+                            Spacer(modifier = Modifier.fillMaxWidth(1f / cols))
+                        }
+                    }
+                }
+            }
+        }
+        resourceList.filterNot { it.type.startsWith("image/") }.forEach { resource ->
+            Attachment(resource,host)
+        }
+    }
+}
+
+@Composable
+fun Attachment(
+    resource: Resource,
+    host:String
+) {
+    val uriHandler = LocalUriHandler.current
+
+    AssistChip(
+        modifier = Modifier.padding(bottom = 10.dp),
+        onClick = {
+            uriHandler.openUri(resource.uri(host))
+        },
+        label = { Text(resource.filename) },
+        leadingIcon = {
+            Icon(
+                Icons.Outlined.Attachment,
+                contentDescription = "附件",
+                Modifier.size(AssistChipDefaults.IconSize)
+            )
+        }
+    )
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/core/IFile.desktop.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/core/IFile.desktop.kt
new file mode 100644
index 0000000..602e01b
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/core/IFile.desktop.kt
@@ -0,0 +1,19 @@
+package com.github.springeye.memosc.core
+
+import java.io.File
+
+actual fun createIFile(path: String): IFile {
+    return IFileImpl(path)
+}
+internal class IFileImpl(val path:String): IFile {
+    val file=File(path)
+    override fun readBytes(): ByteArray {
+        return file.readBytes()
+    }
+
+    override val mimeType: String
+        get() = java.nio.file.Files.probeContentType(file.toPath())
+    override val filename: String
+        get() = file.name
+
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/core/Instant.desktop.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/core/Instant.desktop.kt
new file mode 100644
index 0000000..4b21f5e
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/core/Instant.desktop.kt
@@ -0,0 +1,23 @@
+package com.github.springeye.memosc.core
+import kotlinx.datetime.Instant
+import java.text.SimpleDateFormat
+import java.util.Date
+
+actual fun Instant.formatDate(
+    pattern: String,
+    defValue: String
+): String {
+    return try {
+        SimpleDateFormat(pattern).format(Date(this.toEpochMilliseconds()))
+    } catch (e: Exception) {
+        defValue
+    }
+}
+
+actual fun String.parseDate(pattern: String, defValue: Long): Long {
+    return try {
+        SimpleDateFormat(pattern).parse(this).time
+    } catch (e: Exception) {
+        defValue
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/core/LinkBuilder.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/core/LinkBuilder.kt
new file mode 100644
index 0000000..fcbb7e8
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/core/LinkBuilder.kt
@@ -0,0 +1,142 @@
+package com.github.springeye.memosc.core
+
+import java.net.URI
+
+
+class LinkBuilder {
+    protected var scheme: String?
+    protected var host: String?
+    protected var port: Int
+    protected var args: MutableMap<String, String?>  = HashMap<String, String?>()
+    protected var path: String?
+    protected var hash: String?
+
+    constructor() : this(null, null, 0, java.util.HashMap<String, String>(), null, null)
+    protected constructor(other: LinkBuilder) {
+        scheme = other.scheme
+        host = other.host
+        port = other.port
+        args = HashMap<String, String?>()
+        args.putAll(other.args)
+        path = other.path
+        hash = other.hash
+    }
+
+    protected constructor(
+        schema: String?,
+        host: String?,
+        port: Int,
+        args: Map<String, String?>?,
+        path: String?,
+        hash: String?
+    ) {
+        scheme = schema
+        this.host = host
+        this.port = port
+        this.args = java.util.HashMap<String, String?>()
+        if (args != null) {
+            this.args!!.putAll(args)
+        }
+        this.path = path
+        this.hash = hash
+    }
+
+    constructor(url: URI) {
+        val query: String = url.rawQuery
+        for (argLine in query.split("&".toRegex()).dropLastWhile { it.isEmpty() }
+            .toTypedArray()) {
+            if (argLine.isNotEmpty()) {
+                val i = argLine.indexOf('=')
+                if (i != -1) {
+                    args[argLine.substring(0, i)] = argLine.substring(i + 1)
+                } else {
+                    args[argLine] = null
+                }
+            }
+        }
+        scheme = url.getScheme()
+        host = url.getHost()
+        port = url.getPort()
+        path = url.getRawPath()
+        hash = url.getRawFragment()
+    }
+
+    fun url(url: URI): LinkBuilder {
+        return LinkBuilder(url)
+    }
+
+    fun scheme(schema: String?): LinkBuilder {
+        return LinkBuilder(schema, host, port, args, path, hash)
+    }
+
+    fun host(host: String): LinkBuilder {
+        if (host.indexOf('/') != -1) {
+            throw java.lang.IllegalArgumentException("Wrong host name: $host")
+        }
+        return LinkBuilder(scheme, host, port, args, path, hash)
+    }
+
+    fun port(port: Int): LinkBuilder {
+        return LinkBuilder(scheme, host, port, args, path, hash)
+    }
+
+    fun hash(hash: String?): LinkBuilder {
+        return LinkBuilder(scheme, host, port, args, path, hash)
+    }
+
+    fun path(path: String?): LinkBuilder {
+        return LinkBuilder(scheme, host, port, args, path, hash)
+    }
+
+    @JvmOverloads
+    fun arg(name: String, value: Any? = null): LinkBuilder {
+        val newArgs: MutableMap<String, String?> = java.util.HashMap<String, String?>(args)
+        newArgs[name] = value?.toString()
+        return LinkBuilder(scheme, host, port, newArgs, path, hash)
+    }
+
+    fun build(): String {
+        val buf: java.lang.StringBuilder = java.lang.StringBuilder()
+        if (scheme != null) {
+            buf.append(scheme)
+        }
+        buf.append("://")
+        if (host != null) {
+            buf.append(host)
+        }
+        if (port > 0 && "https" != scheme) {
+            buf.append(':').append(port)
+        }
+        if (path != null) {
+            if (path!![0] != '/') {
+                buf.append('/')
+            }
+            buf.append(path)
+        } else if (args!!.size > 0 || hash != null) {
+            buf.append('/')
+        }
+        if (args!!.size > 0) {
+            buf.append('?')
+            var first = true
+            for ((key, value) in args!!) {
+                if (!first) {
+                    buf.append('&')
+                } else {
+                    first = false
+                }
+                buf.append(java.net.URLEncoder.encode(key, "UTF-8"))
+                if (value != null && value.length > 0) {
+                    buf.append("=").append(java.net.URLEncoder.encode(value, "UTF-8"))
+                }
+            }
+        }
+        if (hash != null) {
+            buf.append('#').append(hash)
+        }
+        return buf.toString()
+    }
+
+    override fun toString(): String {
+        return build()
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/db/AppDatabase.desktop.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/db/AppDatabase.desktop.kt
new file mode 100644
index 0000000..2f7e49e
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/db/AppDatabase.desktop.kt
@@ -0,0 +1,24 @@
+package com.github.springeye.memosc.db
+
+import app.cash.sqldelight.EnumColumnAdapter
+import app.cash.sqldelight.db.SqlDriver
+import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
+import app.cash.sqldelight.logs.LogSqliteDriver
+import com.github.springeye.memosc.db.model.AppDatabase
+import io.github.aakira.napier.Napier
+
+actual fun createAppDatabase(): AppDatabase {
+    val driver: SqlDriver = JdbcSqliteDriver("jdbc:sqlite:test.db").run {
+        LogSqliteDriver(this as SqlDriver){
+            Napier.d(it)
+        }
+    }
+    AppDatabase.Schema.create(driver)
+    return AppDatabase(driver,com.github.springeye.memosc.db.model.Memo.Adapter(
+        rowStatusAdapter = EnumColumnAdapter(),
+        visibilityAdapter = EnumColumnAdapter()
+    ),com.github.springeye.memosc.db.model.RemoteKey.Adapter(
+        rowStatusAdapter = EnumColumnAdapter(),
+        visibilityAdapter = EnumColumnAdapter()
+    ));
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/di/CacheCookiesStorage.desktop.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/di/CacheCookiesStorage.desktop.kt
new file mode 100644
index 0000000..b93ffa1
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/di/CacheCookiesStorage.desktop.kt
@@ -0,0 +1,57 @@
+package com.github.springeye.memosc.di
+
+import com.github.springeye.memosc.AppPreferences
+import io.ktor.client.plugins.cookies.CookiesStorage
+import io.ktor.http.Cookie
+import io.ktor.http.Url
+import io.ktor.util.date.getTimeMillis
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import java.util.concurrent.atomic.AtomicLong
+import kotlin.math.min
+
+
+actual class PersistentCookiesStorage actual constructor(val store: AppPreferences) :
+    CookiesStorage {
+    override suspend fun addCookie(requestUrl: Url, cookie: Cookie) {
+        val items = getCookiesFromStorage()
+        items.removeAll { it.name == cookie.name && it.matches(requestUrl) }
+        items.add(cookie.fillDefaults(requestUrl))
+        store.setString("cookies", Json.encodeToString(items.map { it.toSaveString() }.toList()));
+    }
+
+    private suspend fun getCookiesFromStorage(): MutableList<Cookie> {
+        val old = store.getString("cookies") ?: "[]"
+        val items = Json.decodeFromString<MutableList<String>>(old)
+        val cookies = mutableListOf<Cookie>()
+        return cookies.apply {
+            addAll(items.map { fromString(it) })
+        };
+    }
+    private val oldestCookie: AtomicLong = AtomicLong(0L)
+    override suspend fun get(requestUrl: Url): List<Cookie> {
+        val now = getTimeMillis()
+        if (now >= oldestCookie.get()) cleanup(now)
+        val list = getCookiesFromStorage().filter {
+            it.matches(requestUrl)
+        }
+        return list
+    }
+    private suspend fun cleanup(timestamp: Long) {
+        val cookies = getCookiesFromStorage()
+        cookies.removeAll { cookie ->
+            val expires = cookie.expires?.timestamp ?: return@removeAll false
+            expires < timestamp
+        }
+
+        val newOldest = cookies.fold(Long.MAX_VALUE) { acc, cookie ->
+            cookie.expires?.timestamp?.let { min(acc, it) } ?: acc
+        }
+
+        oldestCookie.set(newOldest)
+        store.setString("cookies", Json.encodeToString(cookies.map { it.toSaveString() }.toList()));
+    }
+    override fun close() {
+    }
+}
+
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ext/AnnotatedStringExt.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ext/AnnotatedStringExt.kt
new file mode 100644
index 0000000..a2eff69
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ext/AnnotatedStringExt.kt
@@ -0,0 +1,273 @@
+package com.github.springeye.memosc.ext
+
+import androidx.compose.foundation.text.appendInlineContent
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.*
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+import org.intellij.markdown.MarkdownElementTypes
+import org.intellij.markdown.MarkdownTokenTypes
+import org.intellij.markdown.ast.ASTNode
+import org.intellij.markdown.ast.getTextInNode
+import org.intellij.markdown.flavours.gfm.GFMTokenTypes
+import java.util.UUID
+
+@OptIn(ExperimentalTextApi::class)
+fun AnnotatedString.Builder.appendMarkdown(
+    markdownText: String,
+    node: ASTNode,
+    depth: Int = 0,
+    linkColor: Color,
+    onImage: (id: String, link: String) -> Unit,
+    onCheckbox: (id: String, startOffset: Int, endOffset: Int) -> Unit,
+    maxWidth: Float,
+    bulletColor: Color,
+    headlineLarge: TextStyle,
+    headlineMedium: TextStyle,
+    headlineSmall: TextStyle,
+): AnnotatedString.Builder {
+    when (node.type) {
+        MarkdownElementTypes.MARKDOWN_FILE, MarkdownElementTypes.PARAGRAPH, MarkdownElementTypes.UNORDERED_LIST, MarkdownElementTypes.ORDERED_LIST -> {
+            // Remove EOL after a headline
+            val headlineTypes = listOf(
+                MarkdownElementTypes.ATX_1,
+                MarkdownElementTypes.ATX_2,
+                MarkdownElementTypes.ATX_3,
+                MarkdownElementTypes.SETEXT_1,
+                MarkdownElementTypes.SETEXT_2
+            )
+            val children = node.children.filterIndexed { index, childNode ->
+                !(childNode.type == MarkdownTokenTypes.EOL && index > 0 && headlineTypes.contains(node.children[index - 1].type))
+            }
+
+            children.forEach { childNode ->
+                appendMarkdown(
+                    markdownText = markdownText,
+                    node = childNode,
+                    depth = depth + 1,
+                    linkColor = linkColor,
+                    onImage = onImage,
+                    onCheckbox = onCheckbox,
+                    maxWidth = maxWidth,
+                    bulletColor = bulletColor,
+                    headlineLarge = headlineLarge,
+                    headlineMedium = headlineMedium,
+                    headlineSmall = headlineSmall
+                )
+            }
+        }
+
+        MarkdownElementTypes.INLINE_LINK -> {
+            val linkDestination =
+                node.children.findLast { it.type == MarkdownElementTypes.LINK_DESTINATION }
+                    ?: return this
+            val linkText = node.children.find { it.type == MarkdownElementTypes.LINK_TEXT }?.children
+
+            withAnnotation(UrlAnnotation(linkDestination.getTextInNode(markdownText).toString())) {
+                withStyle(SpanStyle(linkColor)) {
+                    linkText?.filterIndexed { index, _ -> index != 0 && index != linkText.size - 1 }?.forEach { childNode ->
+                        appendMarkdown(
+                            markdownText = markdownText,
+                            node = childNode,
+                            depth = depth + 1,
+                            linkColor = linkColor,
+                            onImage = onImage,
+                            onCheckbox = onCheckbox,
+                            maxWidth = maxWidth,
+                            bulletColor = bulletColor,
+                            headlineLarge = headlineLarge,
+                            headlineMedium = headlineMedium,
+                            headlineSmall = headlineSmall
+                        )
+                    } ?: Unit
+                }
+            }
+        }
+
+        MarkdownElementTypes.AUTOLINK, GFMTokenTypes.GFM_AUTOLINK -> {
+            val linkDestination = node.getTextInNode(markdownText).toString()
+            withAnnotation(UrlAnnotation(linkDestination)) {
+                withStyle(SpanStyle(linkColor)) {
+                    append(linkDestination)
+                }
+            }
+        }
+
+        MarkdownElementTypes.EMPH -> {
+            withStyle(SpanStyle(fontStyle = FontStyle.Italic)) {
+                node.children.filter { it.type != MarkdownTokenTypes.EMPH }.forEach { childNode ->
+                    appendMarkdown(
+                        markdownText = markdownText,
+                        node = childNode,
+                        depth = depth + 1,
+                        linkColor = linkColor,
+                        onImage = onImage,
+                        onCheckbox = onCheckbox,
+                        maxWidth = maxWidth,
+                        bulletColor = bulletColor,
+                        headlineLarge = headlineLarge,
+                        headlineMedium = headlineMedium,
+                        headlineSmall = headlineSmall
+                    )
+                }
+            }
+        }
+
+        MarkdownElementTypes.STRONG -> {
+            withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
+                node.children.filter { it.type != MarkdownTokenTypes.EMPH }.forEach { childNode ->
+                    appendMarkdown(
+                        markdownText = markdownText,
+                        node = childNode,
+                        depth = depth + 1,
+                        linkColor = linkColor,
+                        onImage = onImage,
+                        onCheckbox = onCheckbox,
+                        maxWidth = maxWidth,
+                        bulletColor = bulletColor,
+                        headlineLarge = headlineLarge,
+                        headlineMedium = headlineMedium,
+                        headlineSmall = headlineSmall
+                    )
+                }
+            }
+        }
+
+        MarkdownElementTypes.CODE_SPAN -> {
+            withStyle(SpanStyle(fontFamily = FontFamily.Monospace)) {
+                node.children.filter { it.type != MarkdownTokenTypes.BACKTICK }.forEach { childNode ->
+                    appendMarkdown(
+                        markdownText = markdownText,
+                        node = childNode,
+                        depth = depth + 1,
+                        linkColor = linkColor,
+                        onImage = onImage,
+                        onCheckbox = onCheckbox,
+                        maxWidth = maxWidth,
+                        bulletColor = bulletColor,
+                        headlineLarge = headlineLarge,
+                        headlineMedium = headlineMedium,
+                        headlineSmall = headlineSmall
+                    )
+                }
+            }
+        }
+
+        MarkdownElementTypes.CODE_FENCE -> {
+            withStyle(SpanStyle(fontFamily = FontFamily.Monospace)) {
+                node.children.filter {
+                    it.type != MarkdownTokenTypes.CODE_FENCE_START
+                        && it.type != MarkdownTokenTypes.CODE_FENCE_END
+                        && it.type != MarkdownTokenTypes.FENCE_LANG
+                }.drop(1).dropLast(1).forEach { childNode ->
+                    appendMarkdown(
+                        markdownText = markdownText,
+                        node = childNode,
+                        depth = depth + 1,
+                        linkColor = linkColor,
+                        onImage = onImage,
+                        onCheckbox = onCheckbox,
+                        maxWidth = maxWidth,
+                        bulletColor = bulletColor,
+                        headlineLarge = headlineLarge,
+                        headlineMedium = headlineMedium,
+                        headlineSmall = headlineSmall
+                    )
+                }
+            }
+        }
+
+        MarkdownElementTypes.IMAGE -> {
+            val linkNode = node.children.findLast { it.type == MarkdownElementTypes.INLINE_LINK } ?: return this
+            val imageUrlNode =
+                linkNode.children.findLast { it.type == MarkdownElementTypes.LINK_DESTINATION }
+                    ?: return this
+            val imageUrl = imageUrlNode.getTextInNode(markdownText).toString()
+            val id = UUID.randomUUID().toString()
+
+            onImage(id, imageUrl)
+            withStyle(ParagraphStyle(lineHeight = (maxWidth * 9f / 16f).sp)) {
+                appendInlineContent(id, imageUrl)
+            }
+        }
+
+        MarkdownElementTypes.ATX_1,
+        MarkdownElementTypes.SETEXT_1,
+        MarkdownElementTypes.ATX_2,
+        MarkdownElementTypes.SETEXT_2,
+        MarkdownElementTypes.ATX_3 -> {
+            var content = node.children.find { it.type == MarkdownTokenTypes.ATX_CONTENT || it.type == MarkdownTokenTypes.SETEXT_CONTENT }
+
+            val textStyle = when (node.type) {
+                MarkdownElementTypes.ATX_1, MarkdownElementTypes.SETEXT_1 -> headlineLarge
+                MarkdownElementTypes.ATX_2, MarkdownElementTypes.SETEXT_2 -> headlineMedium
+                else -> headlineSmall
+            }
+
+            if (content != null) {
+                var children = content.children
+                if (children.firstOrNull()?.type == MarkdownTokenTypes.WHITE_SPACE) {
+                    children = children.drop(1)
+                }
+
+                withStyle(textStyle.toParagraphStyle()) {
+                    withStyle(textStyle.toSpanStyle()) {
+                        children.forEach {
+                            appendMarkdown(
+                                markdownText = markdownText,
+                                node = it,
+                                depth = depth + 1,
+                                linkColor = linkColor,
+                                onImage = onImage,
+                                onCheckbox = onCheckbox,
+                                maxWidth = maxWidth,
+                                bulletColor = bulletColor,
+                                headlineLarge = headlineLarge,
+                                headlineMedium = headlineMedium,
+                                headlineSmall = headlineSmall
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        MarkdownElementTypes.LIST_ITEM -> {
+            var children = node.children
+            if (node.children.size >= 2 && node.children[1].type == GFMTokenTypes.CHECK_BOX) {
+                val id = UUID.randomUUID().toString()
+                onCheckbox(id, node.children[1].startOffset, node.children[1].endOffset)
+                appendInlineContent(id, node.children[1].getTextInNode(markdownText).toString())
+                append(' ')
+                children = children.drop(2)
+            } else {
+                withStyle(SpanStyle(color = bulletColor, fontWeight = FontWeight.Bold)) {
+                    append(children[0].getTextInNode(markdownText).toString())
+                }
+                children = children.drop(1)
+            }
+            children.forEach { childNode ->
+                appendMarkdown(
+                    markdownText = markdownText,
+                    node = childNode,
+                    depth = depth + 1,
+                    linkColor = linkColor,
+                    onImage = onImage,
+                    onCheckbox = onCheckbox,
+                    maxWidth = maxWidth,
+                    bulletColor = bulletColor,
+                    headlineLarge = headlineLarge,
+                    headlineMedium = headlineMedium,
+                    headlineSmall = headlineSmall
+                )
+            }
+        }
+
+        else -> {
+            append(node.getTextInNode(markdownText).toString())
+        }
+    }
+    return this
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/logger.desktop.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/logger.desktop.kt
new file mode 100644
index 0000000..aa9db7d
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/logger.desktop.kt
@@ -0,0 +1,8 @@
+package com.github.springeye.memosc
+
+import io.github.aakira.napier.DebugAntilog
+import io.github.aakira.napier.Napier
+
+actual fun initLogger() {
+    Napier.base(DebugAntilog())
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/main.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/main.kt
new file mode 100644
index 0000000..808bf7d
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/main.kt
@@ -0,0 +1,156 @@
+package com.github.springeye.memosc
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.*
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.window.WindowDraggableArea
+import androidx.compose.material.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.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.onPointerEvent
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.WindowPlacement
+import androidx.compose.ui.window.WindowPosition
+import androidx.compose.ui.window.application
+import androidx.compose.ui.window.rememberWindowState
+import javax.swing.JFrame
+
+@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
+fun main() = application {
+    initLogger()
+    val state= rememberWindowState(position = WindowPosition(Alignment.Center), width = 900.dp, height =600.dp )
+
+    Window(state=state,
+        resizable = false,
+        icon = painterResource("logo.png"),
+        onCloseRequest = ::exitApplication, title = "Memosc", undecorated = true) {
+        Column(modifier = Modifier) {
+
+            Row(
+                modifier = Modifier/*.background(color = Color(75, 75, 75))*/
+                    .fillMaxWidth()
+                    .height(35.dp)
+                    .background(Color(0xffdddddd))
+                    .onClick(onDoubleClick = {
+                        if (window.extendedState == JFrame.MAXIMIZED_BOTH) {
+                            window.extendedState = JFrame.NORMAL
+                        } else {
+                            window.extendedState = JFrame.MAXIMIZED_BOTH
+                        }
+
+                    }){}
+                    .padding(start = 20.dp),
+                verticalAlignment = Alignment.CenterVertically
+            ) {
+                WindowDraggableArea(
+                    modifier = Modifier.weight(1f)
+                ) {
+                    Text(text = window.title, color = Color.Black)
+                }
+                Row {
+                    WinButton(
+                        onClick = {
+                            window.extendedState = JFrame.ICONIFIED
+                        },
+                        icon = painterResource("images/ic_chrome_minimize.png")
+                    )
+                    Spacer(modifier = Modifier.width(5.dp))
+                    WinButton(
+                        onClick = {
+                            if (window.extendedState == JFrame.MAXIMIZED_BOTH) {
+                                window.extendedState = JFrame.NORMAL
+                            } else {
+                                window.extendedState = JFrame.MAXIMIZED_BOTH
+                            }
+
+                        },
+                        icon = if(state.placement!=WindowPlacement.Maximized)painterResource("images/ic_chrome_maximize.png") else  painterResource("images/ic_chrome_unmaximize.png")
+                    )
+                    Spacer(modifier = Modifier.width(5.dp))
+                    WinButton(
+                        onClick = {
+                            exitApplication()
+                        },
+                        icon = painterResource("images/ic_chrome_close.png"),
+                        backgroundColor = ColorScheme(Color.Transparent,Color.Red.copy(alpha =0.7F),Color.Red.copy(alpha =1.0F)),
+                        iconColor = ColorScheme(Color.Black.copy(alpha = 0.8956F),Color.White.copy(alpha =0.7F),Color.White.copy(alpha =1.0F))
+                    )
+                }
+            }
+                    Box(Modifier.background(Color(0xffdddddd)).padding(start = 1.dp, end = 1.dp, bottom = 1.dp)){
+                        Bootstrap()
+                    }
+        }
+
+    }
+
+}
+ data class ColorScheme(
+    val normal:Color,
+    val hovered:Color,
+    val pressed:Color,
+)
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun WinButton(
+    text: String = "",
+    icon: Painter,
+    backgroundColor: ColorScheme = ColorScheme(Color.Transparent,Color.Black.copy(alpha =0.0373F),Color.Black.copy(alpha =0.0241F)),
+    iconColor: ColorScheme = ColorScheme(Color.Black.copy(alpha = 0.8956F),Color.Black.copy(alpha =0.8956F),Color.Black.copy(alpha =0.6063F)),
+    onClick: () -> Unit = {},
+) {
+    var bgColor by remember { mutableStateOf(backgroundColor.normal) }
+    var icColor by remember { mutableStateOf(iconColor.normal) }
+    var selected by remember { mutableStateOf(false) }
+    Box(modifier = Modifier
+        .background(bgColor)
+        .clickable {
+            selected=!selected;
+            bgColor = if(selected){
+                backgroundColor.pressed
+            }else{
+                backgroundColor.normal
+            }
+            icColor = if(selected){
+                iconColor.pressed
+            }else{
+                iconColor.normal
+            }
+            onClick.invoke()
+        }
+        .onPointerEvent(PointerEventType.Enter) {
+            bgColor = backgroundColor.hovered
+            icColor = iconColor.hovered
+        }
+        .onPointerEvent(PointerEventType.Exit) {
+            bgColor = backgroundColor.normal
+            icColor = iconColor.normal
+        }        .padding(12.dp)) {
+        Image(icon,"", colorFilter = ColorFilter.tint(icColor))
+    }
+    }
+
+@Preview
+@Composable
+fun AppDesktopPreview() {
+    DesktopApp()
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/model/DailyUsageStat.desktop.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/model/DailyUsageStat.desktop.kt
new file mode 100644
index 0000000..911f7e4
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/model/DailyUsageStat.desktop.kt
@@ -0,0 +1,43 @@
+package com.github.springeye.memosc.model
+
+import kotlinx.datetime.toKotlinLocalDate
+import java.time.DayOfWeek
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.OffsetDateTime
+import java.time.format.TextStyle
+import java.time.temporal.WeekFields
+import java.util.Locale
+
+actual fun initialMatrix(): List<DailyUsageStat> {
+    var now = LocalDate.now()
+    now=now.plusDays(15)
+    val lengthOfYear = now.lengthOfYear()
+    val reversed = (0..lengthOfYear).map { day ->
+        val stat = DailyUsageStat(date = now.minusDays(day - 1L).toKotlinLocalDate())
+        stat
+    }.reversed()
+    return reversed
+}
+
+actual val weekDays: List<String>
+    get(){
+        val day = WeekFields.of(Locale.getDefault()).firstDayOfWeek
+        return DayOfWeek.entries.toTypedArray().mapIndexed { index, _ ->
+            day.plus(index.toLong()).getDisplayName(TextStyle.NARROW, Locale.getDefault())
+        }
+    }
+
+actual fun calculateMatrix(memos: Map<kotlinx.datetime.LocalDate, List<kotlinx.datetime.LocalDate>>): List<DailyUsageStat> {
+//    val countMap = HashMap<kotlinx.datetime.LocalDate, Int>()
+    val countMap=memos.mapValues { it.value.size }
+
+//    for (memo in memos) {
+//        val date = LocalDateTime.ofEpochSecond(memo.createdTs, 0, OffsetDateTime.now().offset).toLocalDate().toKotlinLocalDate()
+//        countMap[date] = (countMap[date] ?: 0) + 1
+//    }
+
+    return initialMatrix().map {
+        it.copy(count = countMap[it.date] ?: 0)
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/app/AppScreen.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/app/AppScreen.kt
new file mode 100644
index 0000000..d716334
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/app/AppScreen.kt
@@ -0,0 +1,69 @@
+package com.github.springeye.memosc.ui.app
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import cafe.adriel.voyager.core.screen.Screen
+import cafe.adriel.voyager.koin.getScreenModel
+import cafe.adriel.voyager.navigator.LocalNavigator
+import cafe.adriel.voyager.navigator.currentOrThrow
+import com.github.springeye.memosc.LoadingAnimation
+import com.github.springeye.memosc.LocalNotification
+import com.github.springeye.memosc.ui.home.HomeScreen
+import com.github.springeye.memosc.ui.login.LoginScreen
+import org.jetbrains.compose.resources.ExperimentalResourceApi
+
+object AppScreen : Screen {
+
+    @OptIn(ExperimentalResourceApi::class)
+    @Composable
+    override fun Content() {
+//        val screenModel = rememberScreenModel { AppScreenModel() }
+        val no= LocalNotification.current
+        val screenModel = getScreenModel<AppScreenModel>()
+        val state by screenModel.state.collectAsState()
+        val navigator = LocalNavigator.currentOrThrow
+        LaunchedEffect(state){
+            println(state)
+            when (state) {
+                is AppScreenModel.State.Loading->{
+//                    no.showLoading()
+                }
+                is AppScreenModel.State.Result->{
+//                    no.hideLoading()
+                    if((state as AppScreenModel.State.Result).isLogin){
+                        navigator.replace(HomeScreen)
+                    }else{
+                        navigator.replace(LoginScreen)
+                    }
+                }
+
+                else -> {
+
+                }
+            }
+        }
+
+        MaterialTheme {
+
+            Column(Modifier.fillMaxWidth().fillMaxHeight(),
+                horizontalAlignment = Alignment.CenterHorizontally,
+                verticalArrangement = Arrangement.Center
+                ) {
+                if(state is AppScreenModel.State.Loading) {
+                    LoadingAnimation()
+                }
+            }
+        }
+        LaunchedEffect(Unit){
+            screenModel.check()
+        }
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/HomeScreen.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/HomeScreen.kt
new file mode 100644
index 0000000..f3fc63b
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/HomeScreen.kt
@@ -0,0 +1,168 @@
+package com.github.springeye.memosc.ui.home
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyItemScope
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.PointerIcon
+import androidx.compose.ui.input.pointer.pointerHoverIcon
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import cafe.adriel.voyager.core.screen.Screen
+import cafe.adriel.voyager.koin.getScreenModel
+import cafe.adriel.voyager.navigator.tab.CurrentTab
+import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
+import cafe.adriel.voyager.navigator.tab.Tab
+import cafe.adriel.voyager.navigator.tab.TabNavigator
+import com.github.springeye.memosc.LocalNotification
+import com.github.springeye.memosc.components.Heatmap
+import com.github.springeye.memosc.components.ITextField
+import com.github.springeye.memosc.core.Base64Image
+import com.github.springeye.memosc.model.weekDays
+import com.github.springeye.memosc.ui.home.tab.ArchivedTab
+import com.github.springeye.memosc.ui.home.tab.DaysReviewTab
+import com.github.springeye.memosc.ui.home.tab.ExploreTab
+import com.github.springeye.memosc.ui.home.tab.HomeTab
+import com.github.springeye.memosc.ui.home.tab.InboxTab
+import com.github.springeye.memosc.ui.home.tab.ProfileTab
+import com.github.springeye.memosc.ui.home.tab.ResourcesTab
+import com.github.springeye.memosc.ui.home.tab.SettingsTab
+
+import io.kamel.image.KamelImage
+import io.kamel.image.asyncPainterResource
+
+object HomeScreen : Screen {
+    private fun readResolve(): Any = HomeScreen
+
+    @Composable
+    override fun Content() {
+        val screenModel = getScreenModel<MemoModel>()
+        val user = screenModel.user
+        val notification = LocalNotification.current
+        val homeTab = remember {
+            HomeTab()
+        }
+        val weekDays = remember { weekDays }
+        TabNavigator(homeTab) {
+            Scaffold() {
+                Row() {
+//                    val menus = listOf(
+//                        MenuItem(Icons.Filled.Home, "主页"),
+//                        MenuItem(Icons.Filled.Call, "每日回顾"),
+//                        MenuItem(Icons.Filled.Call, "资源库"),
+//                        MenuItem(Icons.Filled.Call, "探索"),
+//                        MenuItem(Icons.Filled.Call, "通知"),
+//                        MenuItem(Icons.Filled.Call, "已归档"),
+//                        MenuItem(Icons.Filled.Call, "设置"),
+//                    )
+                    NavMenu(listOf(
+                        homeTab,
+//                        DaysReviewTab,
+                        ResourcesTab,
+//                        ExploreTab,
+//                        InboxTab,
+                        ProfileTab,
+                        ArchivedTab,
+                        SettingsTab
+                    ), width = 200.dp, header = {
+                        Row(
+                            modifier = Modifier.padding(horizontal = 10.dp, vertical = 20.dp)
+                                .height(40.dp), verticalAlignment = Alignment.CenterVertically
+                        ) {
+                            KamelImage(
+                                asyncPainterResource(Base64Image(user?.avatarUrl ?: "")),
+                                contentDescription = null,
+                                contentScale = ContentScale.Crop,            // crop the image if it's not a square
+                                modifier = Modifier
+                                    .aspectRatio(1F)                        // set the image aspect ratio to 1:1
+                                    .clip(CircleShape)                       // clip to the circle shape
+                            )
+                            Text(
+                                user?.displayName ?: "",
+                                modifier = Modifier.padding(start = 5.dp),
+                                style = MaterialTheme.typography.titleMedium
+                            )
+                        }
+                    })
+                    Box(modifier = Modifier.weight(1F)) {
+                        CurrentTab()
+                    }
+
+                }
+            }
+        }
+    }
+}
+
+@Composable
+fun RowScope.NavMenu(
+    menus: List<Tab>,
+    width: Dp = 180.dp,
+    header: (@Composable LazyItemScope.() -> Unit) = {}
+) {
+    val tabNavigator = LocalTabNavigator.current
+    LazyColumn(modifier = Modifier.width(width), horizontalAlignment = Alignment.Start) {
+        item(content = header)
+        items(menus) { item ->
+            NavButton(item, tabNavigator.current == item) {
+                tabNavigator.current = item;
+            }
+        }
+    }
+}
+
+@Composable
+fun NavButton(tab: Tab, selected: Boolean = false, onClick: () -> Unit) {
+    val colors = CardDefaults.cardColors(
+        containerColor = if (selected) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent, //Card background color
+    )
+    Card(elevation = CardDefaults.cardElevation(0.dp),
+        shape = MaterialTheme.shapes.medium.copy(CornerSize(12.dp)),
+        colors = colors,
+        modifier = Modifier.padding(vertical = 5.dp, horizontal = 10.dp)
+            .clip(CardDefaults.shape)
+            .clickable { onClick.invoke() }
+            .pointerHoverIcon(PointerIcon.Hand)
+
+    ) {
+        Row(modifier = Modifier.padding(10.dp).fillMaxWidth()) {
+            val icon = tab.options.icon
+            if (icon != null) Image(icon, "", modifier = Modifier.padding(end = 10.dp).size(24.dp))
+            Text(tab.options.title)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ArchivedTab.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ArchivedTab.kt
new file mode 100644
index 0000000..67cafc9
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ArchivedTab.kt
@@ -0,0 +1,77 @@
+package com.github.springeye.memosc.ui.home.tab
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.outlined.Archive
+import androidx.compose.material.icons.outlined.Inventory2
+import androidx.compose.material.pullrefresh.rememberPullRefreshState
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import app.cash.paging.compose.collectAsLazyPagingItems
+import cafe.adriel.voyager.koin.getScreenModel
+import cafe.adriel.voyager.navigator.tab.Tab
+import cafe.adriel.voyager.navigator.tab.TabOptions
+import com.github.springeye.memosc.components.MemoList
+import com.github.springeye.memosc.core.Base64Image
+import com.github.springeye.memosc.ui.AppModel
+import com.github.springeye.memosc.ui.home.ArchivedModel
+import com.github.springeye.memosc.ui.home.MemoModel
+import com.github.springeye.memosc.ui.home.ProfileModel
+import io.kamel.image.KamelImage
+import io.kamel.image.asyncPainterResource
+
+object ArchivedTab:Tab{
+    @Composable
+    override fun Content() {
+        val screenModel = getScreenModel<MemoModel>()
+        val memoModel  =getScreenModel<ArchivedModel>()
+        val settings   by getScreenModel<AppModel>().state.collectAsState()
+        val user =screenModel.user
+        val items=memoModel.getPagerByUserId(user?.id?:0L).collectAsLazyPagingItems()
+        Scaffold {
+            if(items.itemCount<=0){
+                Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
+                    Text("没有任何数据")
+                }
+            }else{
+                MemoList(items, settings.host,{},{},{})
+            }
+
+        }
+    }
+
+    override val options: TabOptions
+        @Composable
+        get() {
+            val title = "已归档"
+            val icon = rememberVectorPainter(Icons.Outlined.Archive)
+
+            return remember {
+                TabOptions(
+                    index = 5u,
+                    title = title,
+                    icon = icon
+                )
+            }
+        }
+
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/DaysReviewTab.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/DaysReviewTab.kt
new file mode 100644
index 0000000..dd27333
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/DaysReviewTab.kt
@@ -0,0 +1,40 @@
+package com.github.springeye.memosc.ui.home.tab
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.outlined.Archive
+import androidx.compose.material.icons.outlined.CalendarToday
+import androidx.compose.material.icons.outlined.CalendarViewDay
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import cafe.adriel.voyager.navigator.tab.Tab
+import cafe.adriel.voyager.navigator.tab.TabOptions
+import org.jetbrains.compose.resources.ExperimentalResourceApi
+import org.jetbrains.compose.resources.painterResource
+
+@OptIn(ExperimentalResourceApi::class)
+object DaysReviewTab:Tab{
+    @Composable
+    override fun Content() {
+        Text("home2")
+    }
+
+    override val options: TabOptions
+        @Composable
+        get() {
+            val title = "每次回顾"
+            val icon = rememberVectorPainter(Icons.Outlined.CalendarToday)
+
+            return remember {
+                TabOptions(
+                    index = 1u,
+                    title = title,
+                    icon = icon
+                )
+            }
+        }
+
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ExploreTab.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ExploreTab.kt
new file mode 100644
index 0000000..bb69e42
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ExploreTab.kt
@@ -0,0 +1,63 @@
+package com.github.springeye.memosc.ui.home.tab
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.outlined.Explore
+import androidx.compose.material.pullrefresh.PullRefreshIndicator
+import androidx.compose.material.pullrefresh.pullRefresh
+import androidx.compose.material.pullrefresh.rememberPullRefreshState
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import app.cash.paging.LoadStateLoading
+import app.cash.paging.compose.collectAsLazyPagingItems
+import cafe.adriel.voyager.koin.getScreenModel
+import cafe.adriel.voyager.navigator.tab.Tab
+import cafe.adriel.voyager.navigator.tab.TabOptions
+import com.github.springeye.memosc.ui.home.MemoModel
+
+object ExploreTab:Tab{
+    @OptIn(ExperimentalMaterialApi::class)
+    @Composable
+    override fun Content() {
+        val model = getScreenModel<MemoModel>()
+        val lazyPagingItems = model.pager.collectAsLazyPagingItems()
+        val refreshing = lazyPagingItems.loadState.refresh == LoadStateLoading
+        val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = {
+            lazyPagingItems.refresh()
+        })
+        Box(modifier = Modifier.fillMaxWidth().fillMaxHeight()
+            .background(Color.Red)
+            .pullRefresh(pullRefreshState)) {
+            Text("探索")
+            PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
+        }
+
+    }
+
+    override val options: TabOptions
+        @Composable
+        get() {
+            val title = "探索"
+            val icon = rememberVectorPainter(Icons.Outlined.Explore)
+
+            return remember {
+                TabOptions(
+                    index = 3u,
+                    title = title,
+                    icon = icon
+                )
+            }
+        }
+
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/HomeTab.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/HomeTab.kt
new file mode 100644
index 0000000..f35b323
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/HomeTab.kt
@@ -0,0 +1,215 @@
+package com.github.springeye.memosc.ui.home.tab
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material.icons.outlined.Home
+import androidx.compose.material3.FloatingActionButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SmallFloatingActionButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import app.cash.paging.LoadStateLoading
+import app.cash.paging.compose.collectAsLazyPagingItems
+import cafe.adriel.voyager.koin.getScreenModel
+import cafe.adriel.voyager.navigator.tab.Tab
+import cafe.adriel.voyager.navigator.tab.TabOptions
+import com.github.springeye.memosc.components.EditMemo
+import com.github.springeye.memosc.components.EditMemoCallback
+import com.github.springeye.memosc.components.EditMemoState
+import com.github.springeye.memosc.components.Heatmap
+import com.github.springeye.memosc.components.ITextField
+import com.github.springeye.memosc.components.MemoList
+import com.github.springeye.memosc.components.rememberEditMemoState
+import com.github.springeye.memosc.model.MemosVisibility
+import com.github.springeye.memosc.model.Resource
+import com.github.springeye.memosc.model.weekDays
+import com.github.springeye.memosc.ui.AppModel
+import com.github.springeye.memosc.ui.home.MemoModel
+import kotlinx.coroutines.launch
+
+class HomeTab : Tab {
+    @OptIn(ExperimentalMaterialApi::class)
+    @Composable
+    override fun Content() {
+
+        val model = getScreenModel<MemoModel>()
+        val lazyPagingItems = model.pager.collectAsLazyPagingItems()
+        val refreshing = lazyPagingItems.loadState.refresh == LoadStateLoading
+        val appSettings = getScreenModel<AppModel>().state.collectAsState()
+
+        val scope = rememberCoroutineScope()
+        val state = rememberEditMemoState(host = appSettings.value.host, callback = object:EditMemoCallback(){
+            override suspend fun onSubmit(
+                state:EditMemoState,
+                editId: Long,
+                content: String,
+                resources: List<Resource>,
+                visibility: MemosVisibility
+            ) {
+                model.submit(editId,content,resources,visibility)
+                    state.setNewMemo(null)
+                    lazyPagingItems.refresh()
+            }
+
+            override suspend fun onCancle(state: EditMemoState) {
+                super.onCancle(state)
+                state.setNewMemo(null)
+            }
+            override suspend fun onUpload(state: EditMemoState, path: String) {
+                val resource=model.upload(path)
+                state.addResource(resource)
+            }
+
+            override suspend fun onRemoveResourced(state: EditMemoState, resource: Resource) {
+            }
+
+        })
+        LaunchedEffect(lazyPagingItems.itemSnapshotList.items){
+            model.fetchExtraInfo()
+        }
+        Scaffold(modifier = Modifier, floatingActionButton = {
+            SmallFloatingActionButton(onClick = {
+                lazyPagingItems.refresh()
+
+            }, shape = FloatingActionButtonDefaults.smallShape) {
+                androidx.compose.material3.Icon(Icons.Default.Refresh, "")
+            }
+        }) {
+            Row(modifier = Modifier) {
+                Box(modifier = Modifier.weight(1f)) {
+                    Column(modifier = Modifier.fillMaxWidth()) {
+                        Box(Modifier.padding(top = 20.dp)) {
+
+                            EditMemo(state)
+                        }
+                        MemoList(
+                            lazyPagingItems,
+                            appSettings.value.host,
+                            onEdit = {
+                                     state.setNewMemo(it)
+                            } ,
+                            onPin = {
+                                scope.launch {
+                                    model.setPininned(it)
+                                    lazyPagingItems.refresh()
+                                }
+                            },
+                            onRemove = {
+                                scope.launch {
+                                    model.remove(it)
+                                    lazyPagingItems.refresh()
+                                }
+                            })
+                    }
+
+                }
+                Column(
+                    modifier = Modifier.fillMaxHeight().width(200.dp).padding(10.dp)
+                ) {
+                    ITextField(
+                        value = model.query.content?:"",
+                        onValueChange = {
+                            model.filterContent(it)
+                        },
+                        singleLine = true,
+                        placeholder = {
+                            Text(
+                                "搜索备忘录",
+                                style = TextStyle.Default.copy(color = Color(156, 163, 175))
+                            )
+                        },
+                        modifier = Modifier.fillMaxWidth()
+                    )
+                    Box(modifier = Modifier.height(175.dp)) {
+                        Column (
+                            modifier = Modifier
+                                .fillMaxWidth()
+                                .padding(top = 10.dp),
+                        ) {
+                            Row(
+                                modifier = Modifier
+                                    .fillMaxWidth()
+                                    .padding(end = 5.dp),
+                                horizontalArrangement = Arrangement.SpaceBetween
+                            ) {
+                                for (day in weekDays) {
+                                    Text(
+                                        day,
+                                        style = MaterialTheme.typography.labelSmall,
+                                        color = MaterialTheme.colorScheme.outline
+                                    )
+                                }
+//                                    Text(
+//                                        weekDays[0],
+//                                        style = MaterialTheme.typography.labelSmall,
+//                                        color = MaterialTheme.colorScheme.outline
+//                                    )
+//                                    Text(
+//                                        weekDays[3],
+//                                        style = MaterialTheme.typography.labelSmall,
+//                                        color = MaterialTheme.colorScheme.outline
+//                                    )
+//                                    Text(
+//                                        weekDays[6],
+//                                        style = MaterialTheme.typography.labelSmall,
+//                                        color = MaterialTheme.colorScheme.outline
+//                                    )
+                            }
+                            Heatmap(model.matrix)
+                        }
+//                            Text("")
+                    }
+                }
+            }
+            if(state.editId>0){
+                Dialog(onDismissRequest = {
+                    state.setNewMemo(null)
+                }){
+                    EditMemo(state)
+                }
+            }
+        }
+
+    }
+
+
+    override val options: TabOptions
+        @Composable
+        get() {
+            val title = "主页"
+            val icon = rememberVectorPainter(Icons.Outlined.Home)
+
+            return remember {
+                TabOptions(
+                    index = 0u,
+                    title = title,
+                    icon = icon
+                )
+            }
+        }
+
+
+
+
+}
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/InboxTab.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/InboxTab.kt
new file mode 100644
index 0000000..1e4cb86
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/InboxTab.kt
@@ -0,0 +1,49 @@
+package com.github.springeye.memosc.ui.home.tab
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Notifications
+import androidx.compose.material.icons.outlined.Inbox
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import cafe.adriel.voyager.koin.getScreenModel
+import cafe.adriel.voyager.navigator.tab.Tab
+import cafe.adriel.voyager.navigator.tab.TabOptions
+import com.github.springeye.memosc.ui.home.NotifiModel
+
+object InboxTab:Tab{
+    @Composable
+    override fun Content() {
+        val model=getScreenModel<NotifiModel>()
+
+        Column {
+            Text("Inbox ${model.counter}")
+            Button(onClick = {
+                model.addItem("aa")
+            }){
+                Text("aa")
+            }
+        }
+    }
+
+    override val options: TabOptions
+        @Composable
+        get() {
+            val title = "通知"
+            val icon = rememberVectorPainter(Icons.Outlined.Inbox)
+
+            return remember {
+                TabOptions(
+                    index = 4u,
+                    title = title,
+                    icon = icon
+                )
+            }
+        }
+
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ProfileTab.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ProfileTab.kt
new file mode 100644
index 0000000..319c518
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ProfileTab.kt
@@ -0,0 +1,121 @@
+package com.github.springeye.memosc.ui.home.tab
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Person
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import app.cash.paging.compose.collectAsLazyPagingItems
+import cafe.adriel.voyager.koin.getScreenModel
+import cafe.adriel.voyager.navigator.tab.Tab
+import cafe.adriel.voyager.navigator.tab.TabOptions
+import com.github.springeye.memosc.components.EditMemo
+import com.github.springeye.memosc.components.EditMemoCallback
+import com.github.springeye.memosc.components.EditMemoState
+import com.github.springeye.memosc.components.MemoList
+import com.github.springeye.memosc.components.rememberEditMemoState
+import com.github.springeye.memosc.core.Base64Image
+import com.github.springeye.memosc.model.Memo
+import com.github.springeye.memosc.model.MemosVisibility
+import com.github.springeye.memosc.model.Resource
+import com.github.springeye.memosc.ui.AppModel
+import com.github.springeye.memosc.ui.home.MemoModel
+import com.github.springeye.memosc.ui.home.ProfileModel
+import io.kamel.image.KamelImage
+import io.kamel.image.asyncPainterResource
+
+object ProfileTab : Tab {
+
+    override val options: TabOptions
+        @Composable
+        get() {
+            val title = "个人资料"
+            val icon = rememberVectorPainter(Icons.Outlined.Person);
+            return remember {
+                TabOptions(
+                    index = 2u,
+                    title = title,
+                    icon = icon
+                )
+            }
+        }
+
+    @Composable
+    override fun Content() {
+        val profileModel  =getScreenModel<ProfileModel>()
+        val memoModel  =getScreenModel<MemoModel>()
+
+        val settings   by getScreenModel<AppModel>().state.collectAsState()
+        val user =memoModel.user
+        val items=profileModel.getPagerByUserId(user?.id?:0L).collectAsLazyPagingItems()
+        val editState= rememberEditMemoState(Memo.empty(), settings.host, callback = object:
+            EditMemoCallback() {
+            override suspend fun onCancle(state: EditMemoState) {
+                super.onCancle(state)
+                state.setNewMemo(null)
+            }
+            override suspend fun onUpload(state: EditMemoState, path: String) {
+                val resource=memoModel.upload(path)
+                state.addResource(resource)
+            }
+            override suspend fun onSubmit(
+                state: EditMemoState,
+                editId: Long,
+                content: String,
+                resources: List<Resource>,
+                visibility: MemosVisibility
+            ) {
+                memoModel.submit(editId,content,resources,visibility)
+            }
+            })
+        Scaffold {
+            Column(
+                modifier = Modifier.padding(horizontal = 10.dp, vertical = 20.dp).fillMaxSize()
+                , horizontalAlignment = Alignment.CenterHorizontally
+            ) {
+                KamelImage(
+                    asyncPainterResource(Base64Image(user?.avatarUrl ?: "")),
+                    contentDescription = null,
+                    contentScale = ContentScale.Crop,            // crop the image if it's not a square
+                    modifier = Modifier
+                        .height(80.dp)
+                        .aspectRatio(1F)                        // set the image aspect ratio to 1:1
+                        .clip(CircleShape)                       // clip to the circle shape
+                )
+                Text(
+                    user?.displayName ?: "",
+                    modifier = Modifier.padding(start = 5.dp),
+                    style = MaterialTheme.typography.titleMedium
+                )
+                MemoList(items, settings.host,{
+                    editState.setNewMemo(it)
+                },{},{})
+                if(editState.editId>0){
+                    Dialog(onDismissRequest = {
+                        editState.setNewMemo(null)
+                    }){
+                        EditMemo(editState)
+                    }
+                }
+            }
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ResourcesTab.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ResourcesTab.kt
new file mode 100644
index 0000000..efd7f37
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/ResourcesTab.kt
@@ -0,0 +1,90 @@
+package com.github.springeye.memosc.ui.home.tab
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.PhotoLibrary
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import cafe.adriel.voyager.koin.getScreenModel
+import cafe.adriel.voyager.navigator.tab.Tab
+import cafe.adriel.voyager.navigator.tab.TabOptions
+import com.github.springeye.memosc.components.CardItem
+import com.github.springeye.memosc.core.formatDate
+import com.github.springeye.memosc.ui.AppModel
+import com.github.springeye.memosc.ui.home.MemoModel
+import io.kamel.image.KamelImage
+import io.kamel.image.asyncPainterResource
+import org.jetbrains.compose.resources.ExperimentalResourceApi
+
+object ResourcesTab : Tab {
+    @OptIn(ExperimentalLayoutApi::class)
+    @Composable
+    override fun Content() {
+        val model = getScreenModel<MemoModel>()
+        val settings = getScreenModel<AppModel>().state.value
+        val group by model.resourcesGroup.collectAsState(mapOf())
+        CardItem(modifier = Modifier.fillMaxSize()) {
+            Column {
+            for (entry in group) {
+                    Row {
+                        Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(horizontal = 15.dp).padding(top = 10.dp)) {
+                            Text("${entry.key.formatDate("yyyy")}", style = MaterialTheme.typography.bodySmall)
+                            Text("${entry.key.formatDate("MM")}", style = MaterialTheme.typography.titleMedium)
+                        }
+
+                        FlowRow(modifier = Modifier.verticalScroll(rememberScrollState())) {
+                            for (resource in entry.value) {
+                                KamelImage(
+                                    asyncPainterResource(resource.uri(settings.host)),
+                                    "",
+                                    modifier = Modifier.size(100.dp)
+                                        .padding(5.dp)
+                                        .fillMaxWidth()
+                                        .clip(RoundedCornerShape(4.dp)),
+                                    contentScale = ContentScale.FillWidth,
+
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @OptIn(ExperimentalResourceApi::class)
+    override val options: TabOptions
+        @Composable
+        get() {
+            val title = "资源库"
+            val icon = rememberVectorPainter(Icons.Outlined.PhotoLibrary);
+            return remember {
+                TabOptions(
+                    index = 2u,
+                    title = title,
+                    icon = icon
+                )
+            }
+        }
+
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/SettingsTab.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/SettingsTab.kt
new file mode 100644
index 0000000..c4d64d3
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/home/tab/SettingsTab.kt
@@ -0,0 +1,36 @@
+package com.github.springeye.memosc.ui.home.tab
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material.icons.outlined.Settings
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import cafe.adriel.voyager.navigator.tab.Tab
+import cafe.adriel.voyager.navigator.tab.TabOptions
+
+object SettingsTab:Tab{
+    @Composable
+    override fun Content() {
+        Text("home2")
+    }
+
+    override val options: TabOptions
+        @Composable
+        get() {
+            val title = "设置"
+            val icon = rememberVectorPainter(Icons.Outlined.Settings)
+
+            return remember {
+                TabOptions(
+                    index = 6u,
+                    title = title,
+                    icon = icon
+                )
+            }
+        }
+
+}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/login/LoginScreen.kt b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/login/LoginScreen.kt
new file mode 100644
index 0000000..12a0461
--- /dev/null
+++ b/composeApp/src/desktopMain/kotlin/com/github/springeye/memosc/ui/login/LoginScreen.kt
@@ -0,0 +1,131 @@
+package com.github.springeye.memosc.ui.login
+
+import com.github.springeye.memosc.LocalNotification
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Alignment.Companion.CenterVertically
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.unit.dp
+import cafe.adriel.voyager.core.screen.Screen
+import cafe.adriel.voyager.koin.getScreenModel
+import cafe.adriel.voyager.navigator.LocalNavigator
+import cafe.adriel.voyager.navigator.currentOrThrow
+import com.github.springeye.memosc.ui.home.HomeScreen
+import org.jetbrains.compose.resources.ExperimentalResourceApi
+import org.jetbrains.compose.resources.painterResource
+
+object LoginScreen:Screen {
+    @OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class)
+    @Composable
+    override fun Content() {
+        val screenModel = getScreenModel<LoginScreenModel>()
+        val state by screenModel.state.collectAsState()
+        val navigator = LocalNavigator.currentOrThrow
+        var host:String by remember { mutableStateOf("") }
+        var username:String by remember { mutableStateOf("") }
+        var password:String by remember { mutableStateOf("") }
+        var rememberPassword by remember { mutableStateOf(true) }
+        val localNotification= LocalNotification.current
+        LaunchedEffect(state){
+            when (state) {
+
+                is LoginScreenModel.State.Result ->{
+                    localNotification.hideLoading()
+                    if((state as LoginScreenModel.State.Result).isSuccess){
+                        navigator.replace(HomeScreen)
+                    }else{
+                        localNotification.showPopUpMessage("登录失败")
+//                        navigator.replace(LoginScreen)
+                    }
+                }
+                is LoginScreenModel.State.Loading ->{
+                    localNotification.showLoading("登录中")
+                }
+
+                else -> {
+
+                }
+            }
+        }
+            val focusManager = LocalFocusManager.current
+            CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
+                Scaffold {
+
+                    Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
+
+                        Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.width(350.dp)) {
+                            Row(Modifier.wrapContentSize(), verticalAlignment = Alignment.CenterVertically) {
+                                Image(
+                                    painterResource("logo.png"),
+                                    null,
+                                    contentScale = ContentScale.Crop,
+                                    modifier = Modifier.size(50.dp)
+                                        .clip(CircleShape)
+                                    ,)
+                                Text("Memos", style = MaterialTheme.typography.headlineMedium, modifier = Modifier.padding(start = 10.dp))
+                            }
+                            OutlinedTextField(host, onValueChange = {
+                                host = it
+                            }, label = {
+                                Text("服务器")
+                            },  singleLine = true,modifier = Modifier.fillMaxWidth().padding(top = 30.dp),
+
+                                )
+                            OutlinedTextField(username, onValueChange = {
+                                username = it
+                            }, label = {
+                                Text("用户名")
+                            }, singleLine = true, modifier = Modifier.fillMaxWidth().padding(top = 30.dp))
+                            OutlinedTextField(password, onValueChange = {
+                                password = it
+                            }, label = {
+                                Text("密码")
+                            },  singleLine = true,modifier = Modifier.fillMaxWidth().padding(top = 10.dp))
+                            Row(modifier = Modifier.align(Alignment.Start).padding(top = 10.dp)) {
+                                Checkbox(
+                                    rememberPassword,
+                                    onCheckedChange = { rememberPassword = it },
+                                )
+                                Text("保持登录", modifier = Modifier.align(CenterVertically))
+                            }
+                            Button(onClick = {
+                                screenModel.login(host,username,password)
+                            }, modifier = Modifier.fillMaxWidth().padding(top = 10.dp)){
+                                Text("登录")
+                            }
+                        }
+                    }
+
+                }
+            }
+    }
+}
diff --git a/composeApp/src/iosMain/kotlin/DataStorePreferences.ios.kt b/composeApp/src/iosMain/kotlin/DataStorePreferences.ios.kt
new file mode 100644
index 0000000..c870f73
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/DataStorePreferences.ios.kt
@@ -0,0 +1,31 @@
+import androidx.datastore.core.DataMigration
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
+import androidx.datastore.preferences.core.Preferences
+import kotlinx.cinterop.ExperimentalForeignApi
+import kotlinx.coroutines.CoroutineScope
+import platform.Foundation.NSDocumentDirectory
+import platform.Foundation.NSFileManager
+import platform.Foundation.NSURL
+import platform.Foundation.NSUserDomainMask
+
+@OptIn(ExperimentalForeignApi::class)
+actual fun dataStorePreferences(
+    corruptionHandler: ReplaceFileCorruptionHandler<Preferences>?,
+    coroutineScope: CoroutineScope,
+    migrations: List<DataMigration<Preferences>>,
+): DataStore<Preferences> = createDataStoreWithDefaults(
+    corruptionHandler = corruptionHandler,
+    migrations = migrations,
+    coroutineScope = coroutineScope,
+    path = {
+        val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(
+            directory = NSDocumentDirectory,
+            inDomain = NSUserDomainMask,
+            appropriateForURL = null,
+            create = false,
+            error = null,
+        )
+        (requireNotNull(documentDirectory).path + "/$SETTINGS_PREFERENCES.preferences_pb")
+    }
+)
\ No newline at end of file
diff --git a/composeApp/src/iosMain/kotlin/Database.ios.kt b/composeApp/src/iosMain/kotlin/Database.ios.kt
new file mode 100644
index 0000000..3197f50
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/Database.ios.kt
@@ -0,0 +1,5 @@
+import com.ctrip.sqllin.driver.DatabasePath
+
+actual fun getGlobalDatabasePath(): DatabasePath {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/composeApp/src/iosMain/kotlin/MainViewController.kt b/composeApp/src/iosMain/kotlin/MainViewController.kt
new file mode 100755
index 0000000..1182225
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/MainViewController.kt
@@ -0,0 +1,3 @@
+import androidx.compose.ui.window.ComposeUIViewController
+
+fun MainViewController() = ComposeUIViewController { Bootstrap() }
diff --git a/composeApp/src/iosMain/kotlin/Platform.ios.kt b/composeApp/src/iosMain/kotlin/Platform.ios.kt
new file mode 100755
index 0000000..5cef987
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/Platform.ios.kt
@@ -0,0 +1,7 @@
+import platform.UIKit.UIDevice
+
+class IOSPlatform: Platform {
+    override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
+}
+
+actual fun getPlatform(): Platform = IOSPlatform()
\ No newline at end of file
diff --git a/composeApp/src/iosMain/kotlin/PopupNotification.ios.kt b/composeApp/src/iosMain/kotlin/PopupNotification.ios.kt
new file mode 100644
index 0000000..72a4675
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/PopupNotification.ios.kt
@@ -0,0 +1,15 @@
+import androidx.compose.runtime.MutableState
+
+actual fun createPopupNotification(state: MutableState<ToastState>): PopupNotification {
+//    TODO("Not yet implemented")
+    return object:PopupNotification{
+        override fun showPopUpMessage(text: String) {
+        }
+
+        override fun showLoading(text: String) {
+        }
+
+        override fun hideLoading() {
+        }
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/iosMain/kotlin/components/Heatmap.ios.kt b/composeApp/src/iosMain/kotlin/components/Heatmap.ios.kt
new file mode 100644
index 0000000..d0f9515
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/components/Heatmap.ios.kt
@@ -0,0 +1,7 @@
+package components
+
+import androidx.compose.ui.unit.Constraints
+
+actual fun countHeatmap(constraints: Constraints): Int {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/composeApp/src/iosMain/kotlin/core/IFile.ios.kt b/composeApp/src/iosMain/kotlin/core/IFile.ios.kt
new file mode 100644
index 0000000..adda091
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/core/IFile.ios.kt
@@ -0,0 +1,6 @@
+package core
+
+
+actual fun createIFile(path: String): IFile {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/composeApp/src/iosMain/kotlin/core/Instant.ios.kt b/composeApp/src/iosMain/kotlin/core/Instant.ios.kt
new file mode 100644
index 0000000..e6c447d
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/core/Instant.ios.kt
@@ -0,0 +1,32 @@
+import kotlinx.datetime.Instant
+import kotlinx.datetime.toNSDate
+import platform.Foundation.NSDateFormatter
+import platform.Foundation.timeIntervalSince1970
+
+actual fun Instant.formatDate(pattern: String, defValue: String): String {
+    return try {
+        val dateFormatter = NSDateFormatter()
+        dateFormatter.dateFormat = pattern
+        dateFormatter.stringFromDate(
+            toNSDate()
+        )
+    } catch (e: Exception) {
+        defValue
+    }
+
+}
+
+actual fun String.parseDate(pattern: String, defValue: Long): Long {
+    return try {
+        val dateFormatter = NSDateFormatter()
+        dateFormatter.dateFormat = pattern
+        val result = dateFormatter.dateFromString(this)?.timeIntervalSince1970?.toLong()
+        if (result != null) {
+            result * 1000
+        } else {
+            defValue
+        }
+    } catch (e: Exception) {
+        defValue
+    }
+}
\ No newline at end of file
diff --git a/composeApp/src/iosMain/kotlin/core/loader/MemoDao.ios.kt b/composeApp/src/iosMain/kotlin/core/loader/MemoDao.ios.kt
new file mode 100644
index 0000000..286aec3
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/core/loader/MemoDao.ios.kt
@@ -0,0 +1,5 @@
+package core.loader
+
+actual fun createMemoDao(): MemoDao {
+    return MemoDao()
+}
\ No newline at end of file
diff --git a/composeApp/src/iosMain/kotlin/di/CacheCookiesStorage.ios.kt b/composeApp/src/iosMain/kotlin/di/CacheCookiesStorage.ios.kt
new file mode 100644
index 0000000..d24b741
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/di/CacheCookiesStorage.ios.kt
@@ -0,0 +1,64 @@
+package di
+
+import AppPreferences
+import io.ktor.client.plugins.cookies.CookiesStorage
+import io.ktor.http.Cookie
+import io.ktor.http.CookieEncoding
+import io.ktor.http.Url
+import io.ktor.util.date.GMTDate
+import io.ktor.util.date.getTimeMillis
+import kotlinx.datetime.Clock
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import okio.FileSystem
+import okio.Path
+import okio.Path.Companion.toPath
+import okio.buffer
+import okio.use
+import kotlin.concurrent.AtomicLong
+import kotlin.math.min
+
+
+actual class PersistentCookiesStorage actual constructor(val store: AppPreferences) :
+    CookiesStorage {
+    override suspend fun addCookie(requestUrl: Url, cookie: Cookie) {
+        val items = getCookiesFromStorage()
+        items.removeAll { it.name == cookie.name && it.matches(requestUrl) }
+        items.add(cookie.fillDefaults(requestUrl))
+        store.setString("cookies", Json.encodeToString(items.map { it.toSaveString() }.toList()));
+    }
+
+    private suspend fun getCookiesFromStorage(): MutableList<Cookie> {
+        val old = store.getString("cookies") ?: "[]"
+        val items = Json.decodeFromString<MutableList<String>>(old)
+        val cookies = mutableListOf<Cookie>()
+        return cookies.apply {
+            addAll(items.map { fromString(it) })
+        };
+    }
+    private val oldestCookie: AtomicLong = AtomicLong(0L)
+    override suspend fun get(requestUrl: Url): List<Cookie> {
+        val now = getTimeMillis()
+        if (now >= oldestCookie.value) cleanup(now)
+        return getCookiesFromStorage().filter {
+            it.matches(requestUrl)
+        }
+    }
+    private suspend fun cleanup(timestamp: Long) {
+        val cookies = getCookiesFromStorage()
+        cookies.removeAll { cookie ->
+            val expires = cookie.expires?.timestamp ?: return@removeAll false
+            expires < timestamp
+        }
+
+        val newOldest = cookies.fold(Long.MAX_VALUE) { acc, cookie ->
+            cookie.expires?.timestamp?.let { min(acc, it) } ?: acc
+        }
+
+        oldestCookie.value=newOldest
+        store.setString("cookies", Json.encodeToString(cookies.map { it.toSaveString() }.toList()));
+    }
+    override fun close() {
+    }
+}
+
diff --git a/composeApp/src/iosMain/kotlin/logger.ios.kt b/composeApp/src/iosMain/kotlin/logger.ios.kt
new file mode 100644
index 0000000..8fc3a0f
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/logger.ios.kt
@@ -0,0 +1,6 @@
+import io.github.aakira.napier.DebugAntilog
+import io.github.aakira.napier.Napier
+
+actual fun initLogger() {
+    Napier.base(DebugAntilog())
+}
\ No newline at end of file
diff --git a/composeApp/src/iosMain/kotlin/model/DailyUsageStat.ios.kt b/composeApp/src/iosMain/kotlin/model/DailyUsageStat.ios.kt
new file mode 100644
index 0000000..3d38af1
--- /dev/null
+++ b/composeApp/src/iosMain/kotlin/model/DailyUsageStat.ios.kt
@@ -0,0 +1,5 @@
+package model
+
+actual fun initialMatrix(): List<DailyUsageStat> {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100755
index 0000000..61ff430
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,19 @@
+kotlin.code.style=official
+
+#Gradle
+org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
+
+
+#Android
+android.nonTransitiveRClass=true
+android.useAndroidX=true
+
+#MPP
+kotlin.mpp.androidSourceSetLayoutVersion=2
+kotlin.mpp.enableCInteropCommonization=true
+
+#Development
+development=true
+
+
+kotlin.native.ignoreDisabledTargets=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100755
index 0000000..c3c8b65
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,135 @@
+[versions]
+agp = "8.2.1"
+android-compileSdk = "34"
+android-minSdk = "26"
+android-targetSdk = "34"
+androidDriver = "2.0.1"
+androidx-activityCompose = "1.8.2"
+androidx-appcompat = "1.6.1"
+androidx-constraintlayout = "2.1.4"
+androidx-core-ktx = "1.12.0"
+androidx-espresso-core = "3.5.1"
+androidx-material = "1.10.0"
+androidx-test-junit = "1.1.5"
+compose = "1.5.4"
+compose-compiler = "1.5.6"
+compose-plugin = "1.5.11"
+datastorePreferences = "1.1.0-alpha07"
+github-napier = "2.7.1"
+highlights = "0.7.1"
+jetbrains-uiToolingPreviewDesktop = "1.5.11"
+junit = "4.13.2"
+kamelImage = "0.9.1"
+kotlin = "1.9.21"
+kotlinxCoroutinesSwing = "1.7.3"
+kotlinxDatetime = "0.5.0"
+kotlinxSerializationJson = "1.6.0"
+ktor = "2.3.7"
+mpfilepicker = "3.1.0"
+multiplatformMarkdownRenderer = "0.10.0"
+ktorfitVersion = "1.11.0"
+napier = "2.6.1"
+okhttp = "4.12.0"
+okio = "3.7.0"
+pagingComposeCommon = "3.3.0-alpha02-0.4.0"
+retrofit = "2.9.0"
+statelyCommon = "2.0.5"
+voyager = "1.0.0"
+koin="3.5.3"
+koin-compose="1.1.2"
+annotationJvm = "1.7.1"
+coroutines = "1.7.3"
+androidx-startup = "1.1.1"
+composeIcons = "1.1.0"
+uiGraphicsDesktop = "1.6.0-beta03"
+packageVersion="1.0.0"
+
+
+[libraries]
+android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "androidDriver" }
+androidx-paging3-extensions = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "androidDriver" }
+androidx-startup = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" }
+androidx-datastore-preferences-core = { module = "androidx.datastore:datastore-preferences-core", version.ref = "datastorePreferences" }
+highlights = { module = "dev.snipme:highlights", version.ref = "highlights" }
+jetbrains-ui-tooling-preview-desktop = { module = "org.jetbrains.compose.ui:ui-tooling-preview-desktop", version.ref = "jetbrains-uiToolingPreviewDesktop" }
+kamel-image = { module = "media.kamel:kamel-image", version.ref = "kamelImage" }
+koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
+koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" }
+koin-androidx-navigation = { module = "io.insert-koin:koin-androidx-navigation", version.ref = "koin" }
+koin-androidx-workmanager = { module = "io.insert-koin:koin-androidx-workmanager", version.ref = "koin" }
+kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
+kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
+kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" }
+androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" }
+androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" }
+androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" }
+androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
+compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
+compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
+compose-ui-tooling-preview-android = { module = "androidx.compose.ui:ui-tooling-preview-android", version.ref = "compose" }
+
+compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
+compose-material = { module = "androidx.compose.material:material", version.ref = "compose" }
+kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinxCoroutinesSwing" }
+kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
+kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
+
+
+kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
+ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
+ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
+ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
+ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
+ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
+ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
+
+ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
+mpfilepicker = { module = "com.darkrockstudios:mpfilepicker", version.ref = "mpfilepicker" }
+multiplatform-markdown-renderer = { module = "com.mikepenz:multiplatform-markdown-renderer", version.ref = "multiplatformMarkdownRenderer" }
+ktorfit-lib = { module = "de.jensklingenberg.ktorfit:ktorfit-lib", version.ref = "ktorfitVersion" }
+napier-v271 = { module = "io.github.aakira:napier", version.ref = "github-napier" }
+okhttp-interceptor-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
+napier = { module = "io.github.aakira:napier", version.ref = "napier" }
+okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
+okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
+paging-compose-common = { module = "app.cash.paging:paging-compose-common", version.ref = "pagingComposeCommon" }
+retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
+retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
+
+sqlite-driver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "androidDriver" }
+stately-common = { module = "co.touchlab:stately-common", version.ref = "statelyCommon" }
+voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
+voyager-screenModel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" }
+voyager-bottomSheetNavigator = { module = "cafe.adriel.voyager:voyager-bottom-sheet-navigator", version.ref = "voyager" }
+voyager-tabNavigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
+voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
+voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" }
+voyager-hilt = { module = "cafe.adriel.voyager:voyager-hilt", version.ref = "voyager" }
+voyager-kodein = { module = "cafe.adriel.voyager:voyager-kodein", version.ref = "voyager" }
+voyager-rxjava = { module = "cafe.adriel.voyager:voyager-rxjava", version.ref = "voyager" }
+koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
+koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin-compose" }
+androidx-annotation-jvm = { group = "androidx.annotation", name = "annotation-jvm", version.ref = "annotationJvm" }
+composeIcons-cssGg = { module = "br.com.devsrsouza.compose.icons:css-gg", version.ref = "composeIcons" }
+composeIcons-weatherIcons = { module = "br.com.devsrsouza.compose.icons:erikflowers-weather-icons", version.ref = "composeIcons" }
+composeIcons-evaIcons = { module = "br.com.devsrsouza.compose.icons:eva-icons", version.ref = "composeIcons" }
+composeIcons-feather = { module = "br.com.devsrsouza.compose.icons:feather", version.ref = "composeIcons" }
+composeIcons-fontAwesome = { module = "br.com.devsrsouza.compose.icons:font-awesome", version.ref = "composeIcons" }
+composeIcons-lineAwesome = { module = "br.com.devsrsouza.compose.icons:line-awesome", version.ref = "composeIcons" }
+composeIcons-linea = { module = "br.com.devsrsouza.compose.icons:linea", version.ref = "composeIcons" }
+composeIcons-octicons = { module = "br.com.devsrsouza.compose.icons:octicons", version.ref = "composeIcons" }
+composeIcons-simpleIcons = { module = "br.com.devsrsouza.compose.icons:simple-icons", version.ref = "composeIcons" }
+composeIcons-tablerIcons = { module = "br.com.devsrsouza.compose.icons:tabler-icons", version.ref = "composeIcons" }
+androidx-ui-graphics-desktop = { group = "androidx.compose.ui", name = "ui-graphics-desktop", version.ref = "uiGraphicsDesktop" }
+
+
+[plugins]
+androidApplication = { id = "com.android.application", version.ref = "agp" }
+androidLibrary = { id = "com.android.library", version.ref = "agp" }
+jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
+kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100755
index 0000000..033e24c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100755
index 0000000..3fa8f86
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..fcb6fca
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    if ! command -v java >/dev/null 2>&1
+    then
+        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC3045
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC3045
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100755
index 0000000..93e3f59
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig
new file mode 100755
index 0000000..50f07bb
--- /dev/null
+++ b/iosApp/Configuration/Config.xcconfig
@@ -0,0 +1,3 @@
+TEAM_ID=
+BUNDLE_ID=com.github.springeye.memosc.memosc
+APP_NAME=memosc
\ No newline at end of file
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj
new file mode 100755
index 0000000..66c59c0
--- /dev/null
+++ b/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -0,0 +1,383 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 50;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
+		058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
+		2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
+		7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
+		2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
+		7555FF7B242A565900829871 /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = .app; sourceTree = BUILT_PRODUCTS_DIR; };
+		7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
+		7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXGroup section */
+		058557D7273AAEEB004C7B11 /* Preview Content */ = {
+			isa = PBXGroup;
+			children = (
+				058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
+			);
+			path = "Preview Content";
+			sourceTree = "<group>";
+		};
+		42799AB246E5F90AF97AA0EF /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		7555FF72242A565900829871 = {
+			isa = PBXGroup;
+			children = (
+				AB1DB47929225F7C00F7AF9C /* Configuration */,
+				7555FF7D242A565900829871 /* iosApp */,
+				7555FF7C242A565900829871 /* Products */,
+				42799AB246E5F90AF97AA0EF /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		7555FF7C242A565900829871 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				7555FF7B242A565900829871 /* .app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		7555FF7D242A565900829871 /* iosApp */ = {
+			isa = PBXGroup;
+			children = (
+				058557BA273AAA24004C7B11 /* Assets.xcassets */,
+				7555FF82242A565900829871 /* ContentView.swift */,
+				7555FF8C242A565B00829871 /* Info.plist */,
+				2152FB032600AC8F00CF470E /* iOSApp.swift */,
+				058557D7273AAEEB004C7B11 /* Preview Content */,
+			);
+			path = iosApp;
+			sourceTree = "<group>";
+		};
+		AB1DB47929225F7C00F7AF9C /* Configuration */ = {
+			isa = PBXGroup;
+			children = (
+				AB3632DC29227652001CCB65 /* Config.xcconfig */,
+			);
+			path = Configuration;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		7555FF7A242A565900829871 /* iosApp */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
+			buildPhases = (
+				F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */,
+				7555FF77242A565900829871 /* Sources */,
+				7555FF79242A565900829871 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = iosApp;
+			productName = iosApp;
+			productReference = 7555FF7B242A565900829871 /* .app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		7555FF73242A565900829871 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastSwiftUpdateCheck = 1130;
+				LastUpgradeCheck = 1130;
+				ORGANIZATIONNAME = orgName;
+				TargetAttributes = {
+					7555FF7A242A565900829871 = {
+						CreatedOnToolsVersion = 11.3.1;
+					};
+				};
+			};
+			buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 7555FF72242A565900829871;
+			productRefGroup = 7555FF7C242A565900829871 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				7555FF7A242A565900829871 /* iosApp */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		7555FF79242A565900829871 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
+				058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+			);
+			name = "Compile Kotlin Framework";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n  echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n  exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		7555FF77242A565900829871 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
+				7555FF83242A565900829871 /* ContentView.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		7555FFA3242A565B00829871 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+			};
+			name = Debug;
+		};
+		7555FFA4242A565B00829871 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				MTL_FAST_MATH = YES;
+				SDKROOT = iphoneos;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		7555FFA6242A565B00829871 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CODE_SIGN_IDENTITY = "Apple Development";
+				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
+				DEVELOPMENT_TEAM = "${TEAM_ID}";
+				ENABLE_PREVIEWS = YES;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
+				);
+				INFOPLIST_FILE = iosApp/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				OTHER_LDFLAGS = (
+					"$(inherited)",
+					"-framework",
+					composeApp,
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
+				PRODUCT_NAME = "${APP_NAME}";
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		7555FFA7242A565B00829871 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CODE_SIGN_IDENTITY = "Apple Development";
+				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
+				DEVELOPMENT_TEAM = "${TEAM_ID}";
+				ENABLE_PREVIEWS = YES;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
+				);
+				INFOPLIST_FILE = iosApp/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				OTHER_LDFLAGS = (
+					"$(inherited)",
+					"-framework",
+					composeApp,
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
+				PRODUCT_NAME = "${APP_NAME}";
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				7555FFA3242A565B00829871 /* Debug */,
+				7555FFA4242A565B00829871 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				7555FFA6242A565B00829871 /* Debug */,
+				7555FFA7242A565B00829871 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 7555FF73242A565900829871 /* Project object */;
+}
diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100755
index 0000000..ee7e3ca
--- /dev/null
+++ b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+  "colors" : [
+    {
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
\ No newline at end of file
diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100755
index 0000000..8edf56e
--- /dev/null
+++ b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,14 @@
+{
+  "images" : [
+    {
+      "filename" : "app-icon-1024.png",
+      "idiom" : "universal",
+      "platform" : "ios",
+      "size" : "1024x1024"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png
new file mode 100755
index 0000000..53fc536
Binary files /dev/null and b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png differ
diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json
new file mode 100755
index 0000000..4aa7c53
--- /dev/null
+++ b/iosApp/iosApp/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
\ No newline at end of file
diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift
new file mode 100755
index 0000000..3cd5c32
--- /dev/null
+++ b/iosApp/iosApp/ContentView.swift
@@ -0,0 +1,21 @@
+import UIKit
+import SwiftUI
+import ComposeApp
+
+struct ComposeView: UIViewControllerRepresentable {
+    func makeUIViewController(context: Context) -> UIViewController {
+        MainViewControllerKt.MainViewController()
+    }
+
+    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
+}
+
+struct ContentView: View {
+    var body: some View {
+        ComposeView()
+                .ignoresSafeArea(.keyboard) // Compose has own keyboard handler
+    }
+}
+
+
+
diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist
new file mode 100755
index 0000000..412e378
--- /dev/null
+++ b/iosApp/iosApp/Info.plist
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>CADisableMinimumFrameDurationOnPhone</key>
+	<true/>
+	<key>UIApplicationSceneManifest</key>
+	<dict>
+		<key>UIApplicationSupportsMultipleScenes</key>
+		<false/>
+	</dict>
+	<key>UILaunchScreen</key>
+	<dict/>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>armv7</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+</dict>
+</plist>
diff --git a/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100755
index 0000000..4aa7c53
--- /dev/null
+++ b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json	
@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
\ No newline at end of file
diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift
new file mode 100755
index 0000000..0648e86
--- /dev/null
+++ b/iosApp/iosApp/iOSApp.swift
@@ -0,0 +1,10 @@
+import SwiftUI
+
+@main
+struct iOSApp: App {
+	var body: some Scene {
+		WindowGroup {
+			ContentView()
+		}
+	}
+}
\ No newline at end of file
diff --git a/launcher/logo.ico b/launcher/logo.ico
new file mode 100644
index 0000000..8c9100d
Binary files /dev/null and b/launcher/logo.ico differ
diff --git a/launcher/logo.png b/launcher/logo.png
new file mode 100644
index 0000000..029df08
Binary files /dev/null and b/launcher/logo.png differ
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100755
index 0000000..2d074b5
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,25 @@
+rootProject.name = "memosc"
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
+
+pluginManagement {
+    repositories {
+//        maven("https://maven.aliyun.com/repository/public/")
+        maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
+        google()
+        gradlePluginPortal()
+        mavenCentral()
+    }
+}
+
+dependencyResolutionManagement {
+    repositories {
+
+        google()
+        mavenCentral()
+        maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
+
+
+    }
+}
+
+include(":composeApp")
\ No newline at end of file