Browse Source

Copy codeviewer from examples to experimental/examples as is (#2508)

pull/2515/head
Nikita Lipsky 2 years ago committed by GitHub
parent
commit
0d6271c7a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      experimental/examples/codeviewer/.gitignore
  2. 21
      experimental/examples/codeviewer/.run/desktop.run.xml
  3. 20
      experimental/examples/codeviewer/README.md
  4. 26
      experimental/examples/codeviewer/android/build.gradle.kts
  5. 24
      experimental/examples/codeviewer/android/src/main/AndroidManifest.xml
  6. 183
      experimental/examples/codeviewer/android/src/main/assets/data/EditorView.kt
  7. 32
      experimental/examples/codeviewer/android/src/main/java/org/jetbrains/codeviewer/MainActivity.kt
  8. 15
      experimental/examples/codeviewer/android/src/main/res/drawable/ic_launcher_foreground.xml
  9. 5
      experimental/examples/codeviewer/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  10. 5
      experimental/examples/codeviewer/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  11. 4
      experimental/examples/codeviewer/android/src/main/res/values/ic_launcher_background.xml
  12. 3
      experimental/examples/codeviewer/android/src/main/res/values/strings.xml
  13. 18
      experimental/examples/codeviewer/build.gradle.kts
  14. 50
      experimental/examples/codeviewer/common/build.gradle.kts
  15. 2
      experimental/examples/codeviewer/common/src/androidMain/AndroidManifest.xml
  16. 4
      experimental/examples/codeviewer/common/src/androidMain/kotlin/org/jetbrains/codeviewer/platform/File.kt
  17. 6
      experimental/examples/codeviewer/common/src/androidMain/kotlin/org/jetbrains/codeviewer/platform/Mouse.kt
  18. 16
      experimental/examples/codeviewer/common/src/androidMain/kotlin/org/jetbrains/codeviewer/platform/Resources.kt
  19. 18
      experimental/examples/codeviewer/common/src/androidMain/kotlin/org/jetbrains/codeviewer/platform/Scrollbar.kt
  20. 15
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/platform/File.kt
  21. 5
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/platform/Mouse.kt
  22. 9
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/platform/Resources.kt
  23. 19
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/platform/Scrollbar.kt
  24. 11
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/CodeViewer.kt
  25. 107
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/CodeViewerView.kt
  26. 35
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/MainView.kt
  27. 64
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/common/Fonts.kt
  28. 11
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/common/Settings.kt
  29. 32
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/common/Theme.kt
  30. 60
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/editor/Editor.kt
  31. 35
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/editor/EditorEmptyView.kt
  32. 78
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/editor/EditorTabsView.kt
  33. 185
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/editor/EditorView.kt
  34. 32
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/editor/Editors.kt
  35. 72
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/filetree/FileTree.kt
  36. 125
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/filetree/FileTreeView.kt
  37. 47
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/statusbar/StatusBar.kt
  38. 11
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/util/LayoutModifiers.kt
  39. 27
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/util/Loadable.kt
  40. 9
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/util/SingleSelection.kt
  41. 13
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/util/TextLines.kt
  42. 95
      experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/util/VerticalSplittable.kt
  43. BIN
      experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_bold.ttf
  44. BIN
      experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_bold_italic.ttf
  45. BIN
      experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_extrabold.ttf
  46. BIN
      experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_extrabold_italic.ttf
  47. BIN
      experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_italic.ttf
  48. BIN
      experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_medium.ttf
  49. BIN
      experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_medium_italic.ttf
  50. BIN
      experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_regular.ttf
  51. 5
      experimental/examples/codeviewer/common/src/desktopMain/kotlin/org/jetbrains/codeviewer/platform/File.kt
  52. 10
      experimental/examples/codeviewer/common/src/desktopMain/kotlin/org/jetbrains/codeviewer/platform/Mouse.kt
  53. 10
      experimental/examples/codeviewer/common/src/desktopMain/kotlin/org/jetbrains/codeviewer/platform/Resources.kt
  54. 25
      experimental/examples/codeviewer/common/src/desktopMain/kotlin/org/jetbrains/codeviewer/platform/Scrollbar.kt
  55. 145
      experimental/examples/codeviewer/common/src/jvmMain/kotlin/org/jetbrains/codeviewer/platform/JvmFile.kt
  56. 41
      experimental/examples/codeviewer/desktop/build.gradle.kts
  57. 17
      experimental/examples/codeviewer/desktop/src/jvmMain/kotlin/org/jetbrains/codeviewer/main.kt
  58. BIN
      experimental/examples/codeviewer/desktop/src/jvmMain/resources/ic_launcher.png
  59. 24
      experimental/examples/codeviewer/gradle.properties
  60. BIN
      experimental/examples/codeviewer/gradle/wrapper/gradle-wrapper.jar
  61. 5
      experimental/examples/codeviewer/gradle/wrapper/gradle-wrapper.properties
  62. 183
      experimental/examples/codeviewer/gradlew
  63. 100
      experimental/examples/codeviewer/gradlew.bat
  64. BIN
      experimental/examples/codeviewer/screenshots/codeviewer.png
  65. BIN
      experimental/examples/codeviewer/screenshots/desktop-run-configuration.png
  66. 23
      experimental/examples/codeviewer/settings.gradle.kts

15
experimental/examples/codeviewer/.gitignore vendored

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
build/
/captures
.externalNativeBuild
.cxx

21
experimental/examples/codeviewer/.run/desktop.run.xml

@ -0,0 +1,21 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="desktop" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/desktop" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="run" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
<method v="2" />
</configuration>
</component>

20
experimental/examples/codeviewer/README.md

@ -0,0 +1,20 @@
MPP Code Viewer example for desktop/android written in Multiplatform Compose library.
### Running desktop application
* To run, launch command: `./gradlew :desktop:run`
* Or choose **desktop** configuration in IDE and run it.
![desktop-run-configuration.png](screenshots/desktop-run-configuration.png)
### Building native desktop distribution
```
./gradlew :desktop:packageDistributionForCurrentOS
# outputs are written to desktop/build/compose/binaries
```
### Installing Android application on device/emulator
```
./gradlew installDebug
```
![Desktop](screenshots/codeviewer.png)

26
experimental/examples/codeviewer/android/build.gradle.kts

@ -0,0 +1,26 @@
plugins {
id("com.android.application")
kotlin("android")
id("org.jetbrains.compose")
}
android {
compileSdk = 32
defaultConfig {
minSdk = 26
targetSdk = 32
versionCode = 1
versionName = "1.0"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
implementation(project(":common"))
implementation("androidx.activity:activity-compose:1.5.0")
}

24
experimental/examples/codeviewer/android/src/main/AndroidManifest.xml

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jetbrains.codeviewer">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity
android:exported="true"
android:name="MainActivity"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

183
experimental/examples/codeviewer/android/src/main/assets/data/EditorView.kt

@ -0,0 +1,183 @@
/**
* This file is an example (we can open it in android application)
*/
package org.jetbrains.codeviewer.ui.editor
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.selection.DisableSelection
import androidx.compose.material.AmbientContentColor
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.*
import androidx.compose.ui.unit.dp
import org.jetbrains.codeviewer.platform.SelectionContainer
import org.jetbrains.codeviewer.ui.common.AppTheme
import org.jetbrains.codeviewer.ui.common.Fonts
import org.jetbrains.codeviewer.ui.common.Settings
import org.jetbrains.codeviewer.util.loadableScoped
import org.jetbrains.codeviewer.util.withoutWidthConstraints
import kotlin.text.Regex.Companion.fromLiteral
@Composable
fun EditorView(model: Editor, settings: Settings) = key(model) {
with (LocalDensity.current) {
SelectionContainer {
Surface(
Modifier.fillMaxSize(),
color = AppTheme.colors.backgroundDark,
) {
val lines by loadableScoped(model.lines)
if (lines != null) {
Box {
Lines(lines!!, settings)
Box(
Modifier
.offset(
x = settings.fontSize.toDp() * 0.5f * settings.maxLineSymbols
)
.width(1.dp)
.fillMaxHeight()
.background(AppTheme.colors.backgroundLight)
)
}
} else {
CircularProgressIndicator(
modifier = Modifier
.size(36.dp)
.padding(4.dp)
)
}
}
}
}
}
@Composable
private fun Lines(lines: Editor.Lines, settings: Settings) = with(DensityAmbient.current) {
val maxNumber = remember(lines.lineNumberDigitCount) {
(1..lines.lineNumberDigitCount).joinToString(separator = "") { "9" }
}
Box(Modifier.fillMaxSize()) {
val scrollState = rememberLazyListState()
val lineHeight = settings.fontSize.toDp() * 1.6f
LazyColumnFor(
lines.size,
modifier = Modifier.fillMaxSize(),
state = scrollState,
itemContent = { index ->
val line: Editor.Line? by loadable { lines.get(index) }
Box(Modifier.height(lineHeight)) {
if (line != null) {
Line(Modifier.align(Alignment.CenterStart), maxNumber, line!!, settings)
}
}
}
)
VerticalScrollbar(
Modifier.align(Alignment.CenterEnd),
scrollState,
lines.size,
lineHeight
)
}
}
// Поддержка русского языка
// دعم اللغة العربية
// 中文支持
@Composable
private fun Line(modifier: Modifier, maxNumber: String, line: Editor.Line, settings: Settings) {
Row(modifier = modifier) {
DisableSelection {
Box {
LineNumber(maxNumber, Modifier.alpha(0f), settings)
LineNumber(line.number.toString(), Modifier.align(Alignment.CenterEnd), settings)
}
}
LineContent(
line.content,
modifier = Modifier
.weight(1f)
.withoutWidthConstraints()
.padding(start = 28.dp, end = 12.dp),
settings = settings
)
}
}
@Composable
private fun LineNumber(number: String, modifier: Modifier, settings: Settings) = Text(
text = number,
fontSize = settings.fontSize,
fontFamily = Fonts.jetbrainsMono(),
color = AmbientContentColor.current.copy(alpha = 0.30f),
modifier = modifier.padding(start = 12.dp)
)
@Composable
private fun LineContent(content: Editor.Content, modifier: Modifier, settings: Settings) = Text(
text = if (content.isCode) {
codeString(content.value.value)
} else {
AnnotatedString(content.value.value)
},
fontSize = settings.fontSize,
fontFamily = Fonts.jetbrainsMono(),
modifier = modifier,
softWrap = false
)
private fun codeString(str: String) = buildAnnotatedString {
withStyle(AppTheme.code.simple) {
append(str.replace("\t", " "))
addStyle(AppTheme.code.punctuation, ":")
addStyle(AppTheme.code.punctuation, "=")
addStyle(AppTheme.code.punctuation, "\"")
addStyle(AppTheme.code.punctuation, "[")
addStyle(AppTheme.code.punctuation, "]")
addStyle(AppTheme.code.punctuation, "{")
addStyle(AppTheme.code.punctuation, "}")
addStyle(AppTheme.code.punctuation, "(")
addStyle(AppTheme.code.punctuation, ")")
addStyle(AppTheme.code.punctuation, ",")
addStyle(AppTheme.code.keyword, "fun ")
addStyle(AppTheme.code.keyword, "val ")
addStyle(AppTheme.code.keyword, "var ")
addStyle(AppTheme.code.keyword, "private ")
addStyle(AppTheme.code.keyword, "internal ")
addStyle(AppTheme.code.keyword, "for ")
addStyle(AppTheme.code.keyword, "expect ")
addStyle(AppTheme.code.keyword, "actual ")
addStyle(AppTheme.code.keyword, "import ")
addStyle(AppTheme.code.keyword, "package ")
addStyle(AppTheme.code.value, "true")
addStyle(AppTheme.code.value, "false")
addStyle(AppTheme.code.value, Regex("[0-9]*"))
addStyle(AppTheme.code.annotation, Regex("^@[a-zA-Z_]*"))
addStyle(AppTheme.code.comment, Regex("^\\s*//.*"))
}
}
private fun AnnotatedString.Builder.addStyle(style: SpanStyle, regexp: String) {
addStyle(style, fromLiteral(regexp))
}
private fun AnnotatedString.Builder.addStyle(style: SpanStyle, regexp: Regex) {
for (result in regexp.findAll(toString())) {
addStyle(style, result.range.first, result.range.last + 1)
}
}

32
experimental/examples/codeviewer/android/src/main/java/org/jetbrains/codeviewer/MainActivity.kt

@ -0,0 +1,32 @@
package org.jetbrains.codeviewer
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import org.jetbrains.codeviewer.platform._HomeFolder
import org.jetbrains.codeviewer.ui.MainView
import java.io.File
import java.io.FileOutputStream
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
copyAssets()
_HomeFolder = filesDir
setContent {
MainView()
}
}
private fun copyAssets() {
for (filename in assets.list("data")!!) {
assets.open("data/$filename").use { assetStream ->
val file = File(filesDir, filename)
FileOutputStream(file).use { fileStream ->
assetStream.copyTo(fileStream)
}
}
}
}
}

15
experimental/examples/codeviewer/android/src/main/res/drawable/ic_launcher_foreground.xml

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108"
android:tint="#FFFFFF">
<group android:scaleX="2.61"
android:scaleY="2.61"
android:translateX="22.68"
android:translateY="22.68">
<path
android:fillColor="@android:color/white"
android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/>
</group>
</vector>

5
experimental/examples/codeviewer/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

5
experimental/examples/codeviewer/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

4
experimental/examples/codeviewer/android/src/main/res/values/ic_launcher_background.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#3C3F41</color>
</resources>

3
experimental/examples/codeviewer/android/src/main/res/values/strings.xml

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Code Viewer</string>
</resources>

18
experimental/examples/codeviewer/build.gradle.kts

@ -0,0 +1,18 @@
plugins {
// this is necessary to avoid the plugins to be loaded multiple times
// in each subproject's classloader
kotlin("jvm") apply false
kotlin("multiplatform") apply false
kotlin("android") apply false
id("com.android.application") apply false
id("com.android.library") apply false
id("org.jetbrains.compose") apply false
}
subprojects {
repositories {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}

50
experimental/examples/codeviewer/common/build.gradle.kts

@ -0,0 +1,50 @@
plugins {
id("com.android.library")
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
android()
jvm("desktop")
sourceSets {
named("commonMain") {
dependencies {
api(compose.runtime)
api(compose.foundation)
api(compose.material)
api(compose.materialIconsExtended)
}
}
named("androidMain") {
kotlin.srcDirs("src/jvmMain/kotlin")
dependencies {
api("androidx.appcompat:appcompat:1.5.1")
api("androidx.core:core-ktx:1.8.0")
}
}
named("desktopMain") {
kotlin.srcDirs("src/jvmMain/kotlin")
dependencies {
api(compose.desktop.common)
}
}
}
}
android {
compileSdk = 32
defaultConfig {
minSdk = 26
targetSdk = 32
}
sourceSets {
named("main") {
manifest.srcFile("src/androidMain/AndroidManifest.xml")
res.srcDirs("src/androidMain/res", "src/commonMain/resources")
}
}
}

2
experimental/examples/codeviewer/common/src/androidMain/AndroidManifest.xml

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="org.jetbrains.codeviewer.common"/>

4
experimental/examples/codeviewer/common/src/androidMain/kotlin/org/jetbrains/codeviewer/platform/File.kt

@ -0,0 +1,4 @@
package org.jetbrains.codeviewer.platform
lateinit var _HomeFolder: java.io.File
actual val HomeFolder: File get() = _HomeFolder.toProjectFile()

6
experimental/examples/codeviewer/common/src/androidMain/kotlin/org/jetbrains/codeviewer/platform/Mouse.kt

@ -0,0 +1,6 @@
package org.jetbrains.codeviewer.platform
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
actual fun Modifier.cursorForHorizontalResize() = this

16
experimental/examples/codeviewer/common/src/androidMain/kotlin/org/jetbrains/codeviewer/platform/Resources.kt

@ -0,0 +1,16 @@
package org.jetbrains.codeviewer.platform
import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@SuppressLint("DiscouragedApi")
@Composable
actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font {
val context = LocalContext.current
val id = context.resources.getIdentifier(res, "font", context.packageName)
return Font(id, weight, style)
}

18
experimental/examples/codeviewer/common/src/androidMain/kotlin/org/jetbrains/codeviewer/platform/Scrollbar.kt

@ -0,0 +1,18 @@
package org.jetbrains.codeviewer.platform
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
actual fun VerticalScrollbar(
modifier: Modifier,
scrollState: ScrollState
) = Unit
@Composable
actual fun VerticalScrollbar(
modifier: Modifier,
scrollState: LazyListState
) = Unit

15
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/platform/File.kt

@ -0,0 +1,15 @@
package org.jetbrains.codeviewer.platform
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.codeviewer.util.TextLines
expect val HomeFolder: File
interface File {
val name: String
val isDirectory: Boolean
val children: List<File>
val hasChildren: Boolean
fun readLines(scope: CoroutineScope): TextLines
}

5
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/platform/Mouse.kt

@ -0,0 +1,5 @@
package org.jetbrains.codeviewer.platform
import androidx.compose.ui.Modifier
expect fun Modifier.cursorForHorizontalResize(): Modifier

9
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/platform/Resources.kt

@ -0,0 +1,9 @@
package org.jetbrains.codeviewer.platform
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@Composable
expect fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font

19
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/platform/Scrollbar.kt

@ -0,0 +1,19 @@
package org.jetbrains.codeviewer.platform
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
@Composable
expect fun VerticalScrollbar(
modifier: Modifier,
scrollState: ScrollState
)
@Composable
expect fun VerticalScrollbar(
modifier: Modifier,
scrollState: LazyListState
)

11
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/CodeViewer.kt

@ -0,0 +1,11 @@
package org.jetbrains.codeviewer.ui
import org.jetbrains.codeviewer.ui.common.Settings
import org.jetbrains.codeviewer.ui.editor.Editors
import org.jetbrains.codeviewer.ui.filetree.FileTree
class CodeViewer(
val editors: Editors,
val fileTree: FileTree,
val settings: Settings
)

107
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/CodeViewerView.kt

@ -0,0 +1,107 @@
package org.jetbrains.codeviewer.ui
import androidx.compose.animation.core.Spring.StiffnessLow
import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentColor
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import org.jetbrains.codeviewer.ui.editor.EditorEmptyView
import org.jetbrains.codeviewer.ui.editor.EditorTabsView
import org.jetbrains.codeviewer.ui.editor.EditorView
import org.jetbrains.codeviewer.ui.filetree.FileTreeView
import org.jetbrains.codeviewer.ui.filetree.FileTreeViewTabView
import org.jetbrains.codeviewer.ui.statusbar.StatusBar
import org.jetbrains.codeviewer.util.SplitterState
import org.jetbrains.codeviewer.util.VerticalSplittable
@Composable
fun CodeViewerView(model: CodeViewer) {
val panelState = remember { PanelState() }
val animatedSize = if (panelState.splitter.isResizing) {
if (panelState.isExpanded) panelState.expandedSize else panelState.collapsedSize
} else {
animateDpAsState(
if (panelState.isExpanded) panelState.expandedSize else panelState.collapsedSize,
SpringSpec(stiffness = StiffnessLow)
).value
}
VerticalSplittable(
Modifier.fillMaxSize(),
panelState.splitter,
onResize = {
panelState.expandedSize =
(panelState.expandedSize + it).coerceAtLeast(panelState.expandedSizeMin)
}
) {
ResizablePanel(Modifier.width(animatedSize).fillMaxHeight(), panelState) {
Column {
FileTreeViewTabView()
FileTreeView(model.fileTree)
}
}
Box {
if (model.editors.active != null) {
Column(Modifier.fillMaxSize()) {
EditorTabsView(model.editors)
Box(Modifier.weight(1f)) {
EditorView(model.editors.active!!, model.settings)
}
StatusBar(model.settings)
}
} else {
EditorEmptyView()
}
}
}
}
private class PanelState {
val collapsedSize = 24.dp
var expandedSize by mutableStateOf(300.dp)
val expandedSizeMin = 90.dp
var isExpanded by mutableStateOf(true)
val splitter = SplitterState()
}
@Composable
private fun ResizablePanel(
modifier: Modifier,
state: PanelState,
content: @Composable () -> Unit,
) {
val alpha by animateFloatAsState(if (state.isExpanded) 1f else 0f, SpringSpec(stiffness = StiffnessLow))
Box(modifier) {
Box(Modifier.fillMaxSize().graphicsLayer(alpha = alpha)) {
content()
}
Icon(
if (state.isExpanded) Icons.Default.ArrowBack else Icons.Default.ArrowForward,
contentDescription = if (state.isExpanded) "Collapse" else "Expand",
tint = LocalContentColor.current,
modifier = Modifier
.padding(top = 4.dp)
.width(24.dp)
.clickable {
state.isExpanded = !state.isExpanded
}
.padding(4.dp)
.align(Alignment.TopEnd)
)
}
}

35
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/MainView.kt

@ -0,0 +1,35 @@
package org.jetbrains.codeviewer.ui
import androidx.compose.foundation.text.selection.DisableSelection
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import org.jetbrains.codeviewer.platform.HomeFolder
import org.jetbrains.codeviewer.ui.common.AppTheme
import org.jetbrains.codeviewer.ui.common.Settings
import org.jetbrains.codeviewer.ui.editor.Editors
import org.jetbrains.codeviewer.ui.filetree.FileTree
@Composable
fun MainView() {
val codeViewer = remember {
val editors = Editors()
CodeViewer(
editors = editors,
fileTree = FileTree(HomeFolder, editors),
settings = Settings()
)
}
DisableSelection {
MaterialTheme(
colors = AppTheme.colors.material
) {
Surface {
CodeViewerView(codeViewer)
}
}
}
}

64
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/common/Fonts.kt

@ -0,0 +1,64 @@
package org.jetbrains.codeviewer.ui.common
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import org.jetbrains.codeviewer.platform.font
object Fonts {
@Composable
fun jetbrainsMono() = FontFamily(
font(
"JetBrains Mono",
"jetbrainsmono_regular",
FontWeight.Normal,
FontStyle.Normal
),
font(
"JetBrains Mono",
"jetbrainsmono_italic",
FontWeight.Normal,
FontStyle.Italic
),
font(
"JetBrains Mono",
"jetbrainsmono_bold",
FontWeight.Bold,
FontStyle.Normal
),
font(
"JetBrains Mono",
"jetbrainsmono_bold_italic",
FontWeight.Bold,
FontStyle.Italic
),
font(
"JetBrains Mono",
"jetbrainsmono_extrabold",
FontWeight.ExtraBold,
FontStyle.Normal
),
font(
"JetBrains Mono",
"jetbrainsmono_extrabold_italic",
FontWeight.ExtraBold,
FontStyle.Italic
),
font(
"JetBrains Mono",
"jetbrainsmono_medium",
FontWeight.Medium,
FontStyle.Normal
),
font(
"JetBrains Mono",
"jetbrainsmono_medium_italic",
FontWeight.Medium,
FontStyle.Italic
)
)
}

11
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/common/Settings.kt

@ -0,0 +1,11 @@
package org.jetbrains.codeviewer.ui.common
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.sp
class Settings {
var fontSize by mutableStateOf(13.sp)
val maxLineSymbols = 120
}

32
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/common/Theme.kt

@ -0,0 +1,32 @@
package org.jetbrains.codeviewer.ui.common
import androidx.compose.material.darkColors
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
object AppTheme {
val colors: Colors = Colors()
val code: Code = Code()
class Colors(
val backgroundDark: Color = Color(0xFF2B2B2B),
val backgroundMedium: Color = Color(0xFF3C3F41),
val backgroundLight: Color = Color(0xFF4E5254),
val material: androidx.compose.material.Colors = darkColors(
background = backgroundDark,
surface = backgroundMedium,
primary = Color.White
),
)
class Code(
val simple: SpanStyle = SpanStyle(Color(0xFFA9B7C6)),
val value: SpanStyle = SpanStyle(Color(0xFF6897BB)),
val keyword: SpanStyle = SpanStyle(Color(0xFFCC7832)),
val punctuation: SpanStyle = SpanStyle(Color(0xFFA1C17E)),
val annotation: SpanStyle = SpanStyle(Color(0xFFBBB529)),
val comment: SpanStyle = SpanStyle(Color(0xFF808080))
)
}

60
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/editor/Editor.kt

@ -0,0 +1,60 @@
package org.jetbrains.codeviewer.ui.editor
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.codeviewer.platform.File
import org.jetbrains.codeviewer.util.EmptyTextLines
import org.jetbrains.codeviewer.util.SingleSelection
class Editor(
val fileName: String,
val lines: (backgroundScope: CoroutineScope) -> Lines,
) {
var close: (() -> Unit)? = null
lateinit var selection: SingleSelection
val isActive: Boolean
get() = selection.selected === this
fun activate() {
selection.selected = this
}
class Line(val number: Int, val content: Content)
interface Lines {
val lineNumberDigitCount: Int get() = size.toString().length
val size: Int
operator fun get(index: Int): Line
}
class Content(val value: State<String>, val isCode: Boolean)
}
fun Editor(file: File) = Editor(
fileName = file.name
) { backgroundScope ->
val textLines = try {
file.readLines(backgroundScope)
} catch (e: Throwable) {
e.printStackTrace()
EmptyTextLines
}
val isCode = file.name.endsWith(".kt", ignoreCase = true)
fun content(index: Int): Editor.Content {
val text = textLines.get(index)
val state = mutableStateOf(text)
return Editor.Content(state, isCode)
}
object : Editor.Lines {
override val size get() = textLines.size
override fun get(index: Int) = Editor.Line(
number = index + 1,
content = content(index)
)
}
}

35
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/editor/EditorEmptyView.kt

@ -0,0 +1,35 @@
package org.jetbrains.codeviewer.ui.editor
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Code
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun EditorEmptyView() = Box(Modifier.fillMaxSize()) {
Column(Modifier.align(Alignment.Center)) {
Icon(
Icons.Default.Code,
contentDescription = null,
tint = LocalContentColor.current.copy(alpha = 0.60f),
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Text(
"To view file open it from the file tree",
color = LocalContentColor.current.copy(alpha = 0.60f),
fontSize = 20.sp,
modifier = Modifier.align(Alignment.CenterHorizontally).padding(16.dp)
)
}
}

78
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/editor/EditorTabsView.kt

@ -0,0 +1,78 @@
package org.jetbrains.codeviewer.ui.editor
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
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.unit.dp
import androidx.compose.ui.unit.sp
import org.jetbrains.codeviewer.ui.common.AppTheme
@Composable
fun EditorTabsView(model: Editors) = Row(Modifier.horizontalScroll(rememberScrollState())) {
for (editor in model.editors) {
EditorTabView(editor)
}
}
@Composable
fun EditorTabView(model: Editor) = Surface(
color = if (model.isActive) {
AppTheme.colors.backgroundDark
} else {
Color.Transparent
}
) {
Row(
Modifier
.clickable(remember(::MutableInteractionSource), indication = null) {
model.activate()
}
.padding(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
model.fileName,
color = LocalContentColor.current,
fontSize = 12.sp,
modifier = Modifier.padding(horizontal = 4.dp)
)
val close = model.close
if (close != null) {
Icon(
Icons.Default.Close,
tint = LocalContentColor.current,
contentDescription = "Close",
modifier = Modifier
.size(24.dp)
.padding(4.dp)
.clickable {
close()
}
)
} else {
Box(
modifier = Modifier
.size(24.dp, 24.dp)
.padding(4.dp)
)
}
}
}

185
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/editor/EditorView.kt

@ -0,0 +1,185 @@
package org.jetbrains.codeviewer.ui.editor
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.selection.DisableSelection
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import org.jetbrains.codeviewer.platform.VerticalScrollbar
import org.jetbrains.codeviewer.ui.common.AppTheme
import org.jetbrains.codeviewer.ui.common.Fonts
import org.jetbrains.codeviewer.ui.common.Settings
import org.jetbrains.codeviewer.util.loadableScoped
import org.jetbrains.codeviewer.util.withoutWidthConstraints
import kotlin.text.Regex.Companion.fromLiteral
@Composable
fun EditorView(model: Editor, settings: Settings) = key(model) {
with (LocalDensity.current) {
SelectionContainer {
Surface(
Modifier.fillMaxSize(),
color = AppTheme.colors.backgroundDark,
) {
val lines by loadableScoped(model.lines)
if (lines != null) {
Box {
Lines(lines!!, settings)
Box(
Modifier
.offset(
x = settings.fontSize.toDp() * 0.5f * settings.maxLineSymbols
)
.width(1.dp)
.fillMaxHeight()
.background(AppTheme.colors.backgroundLight)
)
}
} else {
CircularProgressIndicator(
modifier = Modifier
.size(36.dp)
.padding(4.dp)
)
}
}
}
}
}
@Composable
private fun Lines(lines: Editor.Lines, settings: Settings) = with(LocalDensity.current) {
val maxNum = remember(lines.lineNumberDigitCount) {
(1..lines.lineNumberDigitCount).joinToString(separator = "") { "9" }
}
Box(Modifier.fillMaxSize()) {
val scrollState = rememberLazyListState()
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = scrollState
) {
items(lines.size) { index ->
Box(Modifier.height(settings.fontSize.toDp() * 1.6f)) {
Line(Modifier.align(Alignment.CenterStart), maxNum, lines[index], settings)
}
}
}
VerticalScrollbar(
Modifier.align(Alignment.CenterEnd),
scrollState
)
}
}
// Поддержка русского языка
// دعم اللغة العربية
// 中文支持
@Composable
private fun Line(modifier: Modifier, maxNum: String, line: Editor.Line, settings: Settings) {
Row(modifier = modifier) {
DisableSelection {
Box {
LineNumber(maxNum, Modifier.alpha(0f), settings)
LineNumber(line.number.toString(), Modifier.align(Alignment.CenterEnd), settings)
}
}
LineContent(
line.content,
modifier = Modifier
.weight(1f)
.withoutWidthConstraints()
.padding(start = 28.dp, end = 12.dp),
settings = settings
)
}
}
@Composable
private fun LineNumber(number: String, modifier: Modifier, settings: Settings) = Text(
text = number,
fontSize = settings.fontSize,
fontFamily = Fonts.jetbrainsMono(),
color = LocalContentColor.current.copy(alpha = 0.30f),
modifier = modifier.padding(start = 12.dp)
)
@Composable
private fun LineContent(content: Editor.Content, modifier: Modifier, settings: Settings) = Text(
text = if (content.isCode) {
codeString(content.value.value)
} else {
buildAnnotatedString {
withStyle(AppTheme.code.simple) {
append(content.value.value)
}
}
},
fontSize = settings.fontSize,
fontFamily = Fonts.jetbrainsMono(),
modifier = modifier,
softWrap = false
)
private fun codeString(str: String) = buildAnnotatedString {
withStyle(AppTheme.code.simple) {
val strFormatted = str.replace("\t", " ")
append(strFormatted)
addStyle(AppTheme.code.punctuation, strFormatted, ":")
addStyle(AppTheme.code.punctuation, strFormatted, "=")
addStyle(AppTheme.code.punctuation, strFormatted, "\"")
addStyle(AppTheme.code.punctuation, strFormatted, "[")
addStyle(AppTheme.code.punctuation, strFormatted, "]")
addStyle(AppTheme.code.punctuation, strFormatted, "{")
addStyle(AppTheme.code.punctuation, strFormatted, "}")
addStyle(AppTheme.code.punctuation, strFormatted, "(")
addStyle(AppTheme.code.punctuation, strFormatted, ")")
addStyle(AppTheme.code.punctuation, strFormatted, ",")
addStyle(AppTheme.code.keyword, strFormatted, "fun ")
addStyle(AppTheme.code.keyword, strFormatted, "val ")
addStyle(AppTheme.code.keyword, strFormatted, "var ")
addStyle(AppTheme.code.keyword, strFormatted, "private ")
addStyle(AppTheme.code.keyword, strFormatted, "internal ")
addStyle(AppTheme.code.keyword, strFormatted, "for ")
addStyle(AppTheme.code.keyword, strFormatted, "expect ")
addStyle(AppTheme.code.keyword, strFormatted, "actual ")
addStyle(AppTheme.code.keyword, strFormatted, "import ")
addStyle(AppTheme.code.keyword, strFormatted, "package ")
addStyle(AppTheme.code.value, strFormatted, "true")
addStyle(AppTheme.code.value, strFormatted, "false")
addStyle(AppTheme.code.value, strFormatted, Regex("[0-9]*"))
addStyle(AppTheme.code.annotation, strFormatted, Regex("^@[a-zA-Z_]*"))
addStyle(AppTheme.code.comment, strFormatted, Regex("^\\s*//.*"))
}
}
private fun AnnotatedString.Builder.addStyle(style: SpanStyle, text: String, regexp: String) {
addStyle(style, text, fromLiteral(regexp))
}
private fun AnnotatedString.Builder.addStyle(style: SpanStyle, text: String, regexp: Regex) {
for (result in regexp.findAll(text)) {
addStyle(style, result.range.first, result.range.last + 1)
}
}

32
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/editor/Editors.kt

@ -0,0 +1,32 @@
package org.jetbrains.codeviewer.ui.editor
import androidx.compose.runtime.mutableStateListOf
import org.jetbrains.codeviewer.platform.File
import org.jetbrains.codeviewer.util.SingleSelection
class Editors {
private val selection = SingleSelection()
var editors = mutableStateListOf<Editor>()
private set
val active: Editor? get() = selection.selected as Editor?
fun open(file: File) {
val editor = Editor(file)
editor.selection = selection
editor.close = {
close(editor)
}
editors.add(editor)
editor.activate()
}
private fun close(editor: Editor) {
val index = editors.indexOf(editor)
editors.remove(editor)
if (editor.isActive) {
selection.selected = editors.getOrNull(index.coerceAtMost(editors.lastIndex))
}
}
}

72
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/filetree/FileTree.kt

@ -0,0 +1,72 @@
package org.jetbrains.codeviewer.ui.filetree
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import org.jetbrains.codeviewer.platform.File
import org.jetbrains.codeviewer.ui.editor.Editors
class ExpandableFile(
val file: File,
val level: Int,
) {
var children: List<ExpandableFile> by mutableStateOf(emptyList())
val canExpand: Boolean get() = file.hasChildren
fun toggleExpanded() {
children = if (children.isEmpty()) {
file.children
.map { ExpandableFile(it, level + 1) }
.sortedWith(compareBy({ it.file.isDirectory }, { it.file.name }))
.sortedBy { !it.file.isDirectory }
} else {
emptyList()
}
}
}
class FileTree(root: File, private val editors: Editors) {
private val expandableRoot = ExpandableFile(root, 0).apply {
toggleExpanded()
}
val items: List<Item> get() = expandableRoot.toItems()
inner class Item constructor(
private val file: ExpandableFile
) {
val name: String get() = file.file.name
val level: Int get() = file.level
val type: ItemType
get() = if (file.file.isDirectory) {
ItemType.Folder(isExpanded = file.children.isNotEmpty(), canExpand = file.canExpand)
} else {
ItemType.File(ext = file.file.name.substringAfterLast(".").lowercase())
}
fun open() = when (type) {
is ItemType.Folder -> file.toggleExpanded()
is ItemType.File -> editors.open(file.file)
}
}
sealed class ItemType {
class Folder(val isExpanded: Boolean, val canExpand: Boolean) : ItemType()
class File(val ext: String) : ItemType()
}
private fun ExpandableFile.toItems(): List<Item> {
fun ExpandableFile.addTo(list: MutableList<Item>) {
list.add(Item(this))
for (child in children) {
child.addTo(list)
}
}
val list = mutableListOf<Item>()
addTo(list)
return list
}
}

125
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/filetree/FileTreeView.kt

@ -0,0 +1,125 @@
package org.jetbrains.codeviewer.ui.filetree
import androidx.compose.foundation.clickable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.jetbrains.codeviewer.platform.VerticalScrollbar
import org.jetbrains.codeviewer.util.withoutWidthConstraints
@Composable
fun FileTreeViewTabView() = Surface {
Row(
Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
"Files",
color = LocalContentColor.current.copy(alpha = 0.60f),
fontSize = 12.sp,
modifier = Modifier.padding(horizontal = 4.dp)
)
}
}
@Composable
fun FileTreeView(model: FileTree) = Surface(
modifier = Modifier.fillMaxSize()
) {
with(LocalDensity.current) {
Box {
val scrollState = rememberLazyListState()
LazyColumn(
modifier = Modifier.fillMaxSize().withoutWidthConstraints(),
state = scrollState
) {
items(model.items.size) {
FileTreeItemView(14.sp, 14.sp.toDp() * 1.5f, model.items[it])
}
}
VerticalScrollbar(
Modifier.align(Alignment.CenterEnd),
scrollState
)
}
}
}
@Composable
private fun FileTreeItemView(fontSize: TextUnit, height: Dp, model: FileTree.Item) = Row(
modifier = Modifier
.wrapContentHeight()
.clickable { model.open() }
.padding(start = 24.dp * model.level)
.height(height)
.fillMaxWidth()
) {
val interactionSource = remember { MutableInteractionSource() }
val active by interactionSource.collectIsHoveredAsState()
FileItemIcon(Modifier.align(Alignment.CenterVertically), model)
Text(
text = model.name,
color = if (active) LocalContentColor.current.copy(alpha = 0.60f) else LocalContentColor.current,
modifier = Modifier
.align(Alignment.CenterVertically)
.clipToBounds()
.hoverable(interactionSource),
softWrap = true,
fontSize = fontSize,
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
}
@Composable
private fun FileItemIcon(modifier: Modifier, model: FileTree.Item) = Box(modifier.size(24.dp).padding(4.dp)) {
when (val type = model.type) {
is FileTree.ItemType.Folder -> when {
!type.canExpand -> Unit
type.isExpanded -> Icon(
Icons.Default.KeyboardArrowDown, contentDescription = null, tint = LocalContentColor.current
)
else -> Icon(
Icons.Default.KeyboardArrowRight, contentDescription = null, tint = LocalContentColor.current
)
}
is FileTree.ItemType.File -> when (type.ext) {
"kt" -> Icon(Icons.Default.Code, contentDescription = null, tint = Color(0xFF3E86A0))
"xml" -> Icon(Icons.Default.Code, contentDescription = null, tint = Color(0xFFC19C5F))
"txt" -> Icon(Icons.Default.Description, contentDescription = null, tint = Color(0xFF87939A))
"md" -> Icon(Icons.Default.Description, contentDescription = null, tint = Color(0xFF87939A))
"gitignore" -> Icon(Icons.Default.BrokenImage, contentDescription = null, tint = Color(0xFF87939A))
"gradle" -> Icon(Icons.Default.Build, contentDescription = null, tint = Color(0xFF87939A))
"kts" -> Icon(Icons.Default.Build, contentDescription = null, tint = Color(0xFF3E86A0))
"properties" -> Icon(Icons.Default.Settings, contentDescription = null, tint = Color(0xFF62B543))
"bat" -> Icon(Icons.Default.Launch, contentDescription = null, tint = Color(0xFF87939A))
else -> Icon(Icons.Default.TextSnippet, contentDescription = null, tint = Color(0xFF87939A))
}
}
}

47
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/ui/statusbar/StatusBar.kt

@ -0,0 +1,47 @@
package org.jetbrains.codeviewer.ui.statusbar
import androidx.compose.foundation.layout.*
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Slider
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.*
import org.jetbrains.codeviewer.ui.common.Settings
private val MinFontSize = 6.sp
private val MaxFontSize = 40.sp
@Composable
fun StatusBar(settings: Settings) = Box(
Modifier
.height(32.dp)
.fillMaxWidth()
.padding(4.dp)
) {
Row(Modifier.fillMaxHeight().align(Alignment.CenterEnd)) {
Text(
text = "Text size",
modifier = Modifier.align(Alignment.CenterVertically),
color = LocalContentColor.current.copy(alpha = 0.60f),
fontSize = 12.sp
)
Spacer(Modifier.width(8.dp))
CompositionLocalProvider(LocalDensity provides LocalDensity.current.scale(0.5f)) {
Slider(
(settings.fontSize - MinFontSize) / (MaxFontSize - MinFontSize),
onValueChange = { settings.fontSize = lerp(MinFontSize, MaxFontSize, it) },
modifier = Modifier.width(240.dp).align(Alignment.CenterVertically)
)
}
}
}
private fun Density.scale(scale: Float) = Density(density * scale, fontScale * scale)
private operator fun TextUnit.minus(other: TextUnit) = (value - other.value).sp
private operator fun TextUnit.div(other: TextUnit) = value / other.value

11
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/util/LayoutModifiers.kt

@ -0,0 +1,11 @@
package org.jetbrains.codeviewer.util
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layout
fun Modifier.withoutWidthConstraints() = layout { measurable, constraints ->
val placeable = measurable.measure(constraints.copy(maxWidth = Int.MAX_VALUE))
layout(constraints.maxWidth, placeable.height) {
placeable.place(0, 0)
}
}

27
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/util/Loadable.kt

@ -0,0 +1,27 @@
package org.jetbrains.codeviewer.util
import androidx.compose.runtime.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
@Composable
fun <T : Any> loadable(load: () -> T): MutableState<T?> {
return loadableScoped { load() }
}
private val loadingKey = Any()
@Composable
fun <T : Any> loadableScoped(load: CoroutineScope.() -> T): MutableState<T?> {
val state: MutableState<T?> = remember { mutableStateOf(null) }
LaunchedEffect(loadingKey) {
try {
state.value = load()
} catch (e: CancellationException) {
// ignore
} catch (e: Exception) {
e.printStackTrace()
}
}
return state
}

9
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/util/SingleSelection.kt

@ -0,0 +1,9 @@
package org.jetbrains.codeviewer.util
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
class SingleSelection {
var selected: Any? by mutableStateOf(null)
}

13
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/util/TextLines.kt

@ -0,0 +1,13 @@
package org.jetbrains.codeviewer.util
interface TextLines {
val size: Int
fun get(index: Int): String
}
object EmptyTextLines : TextLines {
override val size: Int
get() = 0
override fun get(index: Int): String = ""
}

95
experimental/examples/codeviewer/common/src/commonMain/kotlin/org/jetbrains/codeviewer/util/VerticalSplittable.kt

@ -0,0 +1,95 @@
package org.jetbrains.codeviewer.util
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import org.jetbrains.codeviewer.platform.cursorForHorizontalResize
import org.jetbrains.codeviewer.ui.common.AppTheme
@Composable
fun VerticalSplittable(
modifier: Modifier,
splitterState: SplitterState,
onResize: (delta: Dp) -> Unit,
children: @Composable () -> Unit
) = Layout({
children()
VerticalSplitter(splitterState, onResize)
}, modifier, measurePolicy = { measurables, constraints ->
require(measurables.size == 3)
val firstPlaceable = measurables[0].measure(constraints.copy(minWidth = 0))
val secondWidth = constraints.maxWidth - firstPlaceable.width
val secondPlaceable = measurables[1].measure(
Constraints(
minWidth = secondWidth,
maxWidth = secondWidth,
minHeight = constraints.maxHeight,
maxHeight = constraints.maxHeight
)
)
val splitterPlaceable = measurables[2].measure(constraints)
layout(constraints.maxWidth, constraints.maxHeight) {
firstPlaceable.place(0, 0)
secondPlaceable.place(firstPlaceable.width, 0)
splitterPlaceable.place(firstPlaceable.width, 0)
}
})
class SplitterState {
var isResizing by mutableStateOf(false)
var isResizeEnabled by mutableStateOf(true)
}
@Composable
fun VerticalSplitter(
splitterState: SplitterState,
onResize: (delta: Dp) -> Unit,
color: Color = AppTheme.colors.backgroundDark
) = Box {
val density = LocalDensity.current
Box(
Modifier
.width(8.dp)
.fillMaxHeight()
.run {
if (splitterState.isResizeEnabled) {
this.draggable(
state = rememberDraggableState {
with(density) {
onResize(it.toDp())
}
},
orientation = Orientation.Horizontal,
startDragImmediately = true,
onDragStarted = { splitterState.isResizing = true },
onDragStopped = { splitterState.isResizing = false }
).cursorForHorizontalResize()
} else {
this
}
}
)
Box(
Modifier
.width(1.dp)
.fillMaxHeight()
.background(color)
)
}

BIN
experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_bold.ttf

Binary file not shown.

BIN
experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_bold_italic.ttf

Binary file not shown.

BIN
experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_extrabold.ttf

Binary file not shown.

BIN
experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_extrabold_italic.ttf

Binary file not shown.

BIN
experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_italic.ttf

Binary file not shown.

BIN
experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_medium.ttf

Binary file not shown.

BIN
experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_medium_italic.ttf

Binary file not shown.

BIN
experimental/examples/codeviewer/common/src/commonMain/resources/font/jetbrainsmono_regular.ttf

Binary file not shown.

5
experimental/examples/codeviewer/common/src/desktopMain/kotlin/org/jetbrains/codeviewer/platform/File.kt

@ -0,0 +1,5 @@
@file:Suppress("NewApi")
package org.jetbrains.codeviewer.platform
actual val HomeFolder: File get() = java.io.File(System.getProperty("user.home")).toProjectFile()

10
experimental/examples/codeviewer/common/src/desktopMain/kotlin/org/jetbrains/codeviewer/platform/Mouse.kt

@ -0,0 +1,10 @@
package org.jetbrains.codeviewer.platform
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import java.awt.Cursor
actual fun Modifier.cursorForHorizontalResize(): Modifier =
this.pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))

10
experimental/examples/codeviewer/common/src/desktopMain/kotlin/org/jetbrains/codeviewer/platform/Resources.kt

@ -0,0 +1,10 @@
package org.jetbrains.codeviewer.platform
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@Composable
actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font =
androidx.compose.ui.text.platform.Font("font/$res.ttf", weight, style)

25
experimental/examples/codeviewer/common/src/desktopMain/kotlin/org/jetbrains/codeviewer/platform/Scrollbar.kt

@ -0,0 +1,25 @@
package org.jetbrains.codeviewer.platform
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
actual fun VerticalScrollbar(
modifier: Modifier,
scrollState: ScrollState
) = androidx.compose.foundation.VerticalScrollbar(
rememberScrollbarAdapter(scrollState),
modifier
)
@Composable
actual fun VerticalScrollbar(
modifier: Modifier,
scrollState: LazyListState
) = androidx.compose.foundation.VerticalScrollbar(
rememberScrollbarAdapter(scrollState),
modifier
)

145
experimental/examples/codeviewer/common/src/jvmMain/kotlin/org/jetbrains/codeviewer/platform/JvmFile.kt

@ -0,0 +1,145 @@
package org.jetbrains.codeviewer.platform
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import kotlinx.coroutines.*
import org.jetbrains.codeviewer.util.TextLines
import java.io.FileInputStream
import java.io.FilenameFilter
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
import java.nio.charset.StandardCharsets
fun java.io.File.toProjectFile(): File = object : File {
override val name: String get() = this@toProjectFile.name
override val isDirectory: Boolean get() = this@toProjectFile.isDirectory
override val children: List<File>
get() = this@toProjectFile
.listFiles(FilenameFilter { _, name -> !name.startsWith(".")})
.orEmpty()
.map { it.toProjectFile() }
override val hasChildren: Boolean
get() = isDirectory && listFiles()?.size ?: 0 > 0
override fun readLines(scope: CoroutineScope): TextLines {
var byteBufferSize: Int
val byteBuffer = RandomAccessFile(this@toProjectFile, "r").use { file ->
byteBufferSize = file.length().toInt()
file.channel
.map(FileChannel.MapMode.READ_ONLY, 0, file.length())
}
val lineStartPositions = IntList()
var size by mutableStateOf(0)
val refreshJob = scope.launch {
delay(100)
size = lineStartPositions.size
while (true) {
delay(1000)
size = lineStartPositions.size
}
}
scope.launch(Dispatchers.IO) {
readLinePositions(lineStartPositions)
refreshJob.cancel()
size = lineStartPositions.size
}
return object : TextLines {
override val size get() = size
override fun get(index: Int): String {
val startPosition = lineStartPositions[index]
val length = if (index + 1 < size) lineStartPositions[index + 1] - startPosition else
byteBufferSize - startPosition
// Only JDK since 13 has slice() method we need, so do ugly for now.
byteBuffer.position(startPosition)
val slice = byteBuffer.slice()
slice.limit(length)
return StandardCharsets.UTF_8.decode(slice).toString()
}
}
}
}
private fun java.io.File.readLinePositions(
starts: IntList
) {
require(length() <= Int.MAX_VALUE) {
"Files with size over ${Int.MAX_VALUE} aren't supported"
}
val averageLineLength = 200
starts.clear(length().toInt() / averageLineLength)
try {
FileInputStream(this@readLinePositions).use {
val channel = it.channel
val ib = channel.map(
FileChannel.MapMode.READ_ONLY, 0, channel.size()
)
var isBeginOfLine = true
var position = 0L
while (ib.hasRemaining()) {
val byte = ib.get()
if (isBeginOfLine) {
starts.add(position.toInt())
}
isBeginOfLine = byte.toInt().toChar() == '\n'
position++
}
channel.close()
}
} catch (e: IOException) {
e.printStackTrace()
starts.clear(1)
starts.add(0)
}
starts.compact()
}
/**
* Compact version of List<Int> (without unboxing Int and using IntArray under the hood)
*/
private class IntList(initialCapacity: Int = 16) {
@Volatile
private var array = IntArray(initialCapacity)
@Volatile
var size: Int = 0
private set
fun clear(capacity: Int) {
array = IntArray(capacity)
size = 0
}
fun add(value: Int) {
if (size == array.size) {
doubleCapacity()
}
array[size++] = value
}
operator fun get(index: Int) = array[index]
private fun doubleCapacity() {
val newArray = IntArray(array.size * 2 + 1)
System.arraycopy(array, 0, newArray, 0, size)
array = newArray
}
fun compact() {
array = array.copyOfRange(0, size)
}
}

41
experimental/examples/codeviewer/desktop/build.gradle.kts

@ -0,0 +1,41 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform") // kotlin("jvm") doesn't work well in IDEA/AndroidStudio (https://github.com/JetBrains/compose-jb/issues/22)
id("org.jetbrains.compose")
}
kotlin {
jvm {}
sourceSets {
named("jvmMain") {
dependencies {
implementation(compose.desktop.currentOs)
implementation(project(":common"))
}
}
}
}
compose.desktop {
application {
mainClass = "org.jetbrains.codeviewer.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "ComposeCodeViewer"
packageVersion = "1.0.0"
windows {
menu = true
// see https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html
upgradeUuid = "AF792DA6-2EA3-495A-95E5-C3C6CBCB9948"
}
macOS {
// Use -Pcompose.desktop.mac.sign=true to sign and notarize.
bundleID = "com.jetbrains.compose.codeviewer"
}
}
}
}

17
experimental/examples/codeviewer/desktop/src/jvmMain/kotlin/org/jetbrains/codeviewer/main.kt

@ -0,0 +1,17 @@
package org.jetbrains.codeviewer
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.res.loadImageBitmap
import androidx.compose.ui.res.useResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.singleWindowApplication
import org.jetbrains.codeviewer.ui.MainView
fun main() = singleWindowApplication(
title = "Code Viewer",
state = WindowState(width = 1280.dp, height = 768.dp),
icon = BitmapPainter(useResource("ic_launcher.png", ::loadImageBitmap)),
) {
MainView()
}

BIN
experimental/examples/codeviewer/desktop/src/jvmMain/resources/ic_launcher.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

24
experimental/examples/codeviewer/gradle.properties

@ -0,0 +1,24 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
kotlin.version=1.7.20
agp.version=7.1.3
compose.version=1.2.1

BIN
experimental/examples/codeviewer/gradle/wrapper/gradle-wrapper.jar vendored

Binary file not shown.

5
experimental/examples/codeviewer/gradle/wrapper/gradle-wrapper.properties vendored

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

183
experimental/examples/codeviewer/gradlew vendored

@ -0,0 +1,183 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or 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 UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# 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"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# 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
;;
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"
which java >/dev/null 2>&1 || 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
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

100
experimental/examples/codeviewer/gradlew.bat vendored

@ -0,0 +1,100 @@
@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=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@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%" == "0" goto init
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 init
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
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
: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 %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="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!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
experimental/examples/codeviewer/screenshots/codeviewer.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

BIN
experimental/examples/codeviewer/screenshots/desktop-run-configuration.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

23
experimental/examples/codeviewer/settings.gradle.kts

@ -0,0 +1,23 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
google()
}
plugins {
val kotlinVersion = extra["kotlin.version"] as String
val agpVersion = extra["agp.version"] as String
val composeVersion = extra["compose.version"] as String
kotlin("jvm").version(kotlinVersion)
kotlin("multiplatform").version(kotlinVersion)
kotlin("android").version(kotlinVersion)
id("com.android.base").version(agpVersion)
id("com.android.application").version(agpVersion)
id("com.android.library").version(agpVersion)
id("org.jetbrains.compose").version(composeVersion)
}
}
include(":common", ":android", ":desktop")
Loading…
Cancel
Save