From 3a33f62cdd54d6799fa1efdc029a056319c4a58f Mon Sep 17 00:00:00 2001 From: arun28082007 Date: Wed, 12 Aug 2026 07:01:32 +0000 Subject: [PATCH 1/6] fixed textmate and syntex highlighting bug --- .../cosmicide/editor/lsp/LspEditorAdapter.kt | 57 ++++--------------- 1 file changed, 10 insertions(+), 47 deletions(-) diff --git a/app/src/main/kotlin/org/cosmicide/editor/lsp/LspEditorAdapter.kt b/app/src/main/kotlin/org/cosmicide/editor/lsp/LspEditorAdapter.kt index bf7ca3dcf..4362cb05e 100644 --- a/app/src/main/kotlin/org/cosmicide/editor/lsp/LspEditorAdapter.kt +++ b/app/src/main/kotlin/org/cosmicide/editor/lsp/LspEditorAdapter.kt @@ -32,7 +32,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.cosmicide.common.AppDispatchers -import org.cosmicide.common.IndexManager import org.cosmicide.editor.LspServerConnection import org.cosmicide.editor.LspServerDefinition import org.cosmicide.editor.LspServerRequest @@ -47,6 +46,7 @@ import java.io.InputStream import java.io.OutputStream import java.net.URI import java.net.URL + import java.security.MessageDigest import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap @@ -447,44 +447,24 @@ private fun createTextMateLanguage( return createTextMateLanguage(definition, grammarText) } - var cachedGrammarText: String? = readGrammarViaIndexManager(grammarLink) + val cachedGrammarText = runCatching { + grammarCacheFile(context, grammarLink).takeIf(File::exists)?.readText() + }.getOrNull() if (cachedGrammarText != null) { try { return createTextMateLanguage(definition, cachedGrammarText) } catch (e: Exception) { Log.w(TAG, "Discarding invalid grammar cache for $grammarLink", e) - IndexManager.invalidateProject("grammar:$grammarLink") - cachedGrammarText = null + runCatching { grammarCacheFile(context, grammarLink).delete() } } } - val refreshedGrammarText = try { - openGrammarStream(context, grammarLink).readGrammarText() - } catch (refreshFailure: Exception) { - val staleGrammarText = cachedGrammarText ?: throw refreshFailure - Log.w(TAG, "Grammar refresh failed; using stale cache for $grammarLink", refreshFailure) - return createTextMateLanguage(definition, staleGrammarText) - } - - return try { - createTextMateLanguage(definition, refreshedGrammarText).also { - runCatching { cacheGrammarViaIndexManager(grammarLink, refreshedGrammarText) } - .onFailure { error -> - Log.w(TAG, "Unable to cache grammar from $grammarLink", error) - } - } - } catch (refreshFailure: Exception) { - val staleGrammarText = cachedGrammarText ?: throw refreshFailure - Log.w( - TAG, - "Refreshed grammar is invalid; using stale cache for $grammarLink", - refreshFailure - ) - runCatching { createTextMateLanguage(definition, staleGrammarText) } - .getOrElse { staleFailure -> - refreshFailure.addSuppressed(staleFailure) - throw refreshFailure + val refreshedGrammarText = openGrammarStream(context, grammarLink).readGrammarText() + return createTextMateLanguage(definition, refreshedGrammarText).also { + runCatching { grammarCacheFile(context, grammarLink).writeText(refreshedGrammarText) } + .onFailure { error -> + Log.w(TAG, "Unable to cache grammar from $grammarLink", error) } } } @@ -542,23 +522,6 @@ private fun grammarCacheFile(context: Context, grammarLink: String): File { .resolve("$cacheKey.grammar") } -private fun cacheGrammarViaIndexManager(grammarLink: String, grammarText: String) { - val key = "grammar:${grammarLink}" - IndexManager.getOrBuildIndex(key, "text") { grammarText.toByteArray(Charsets.UTF_8) } -} - -private fun readGrammarViaIndexManager(grammarLink: String): String? { - val key = "grammar:${grammarLink}" - return try { - val segment = IndexManager.getOrBuildIndex(key, "text") { - throw IllegalStateException("Grammar not cached: $grammarLink") - } - String(segment.readBlock(0, segment.size.toInt()), Charsets.UTF_8) - } catch (e: Exception) { - null - } -} - private fun openGrammarStream(context: Context, link: String): InputStream { val uri = link.toUri() return when (uri.scheme?.lowercase()) { From cc3321b9fa133dbd3f3806311753bcf53d0d443d Mon Sep 17 00:00:00 2001 From: arun28082007 Date: Wed, 12 Aug 2026 09:00:23 +0000 Subject: [PATCH 2/6] fixed create composition error in lsp logs --- .../editor/lsp/ComposeSignatureHelpLayout.kt | 13 ++++++++++--- .../kotlin/org/cosmicide/editor/lsp/HoverLayout.kt | 7 +++++-- .../kotlin/org/cosmicide/ui/editor/EditorToolbar.kt | 4 ++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/org/cosmicide/editor/lsp/ComposeSignatureHelpLayout.kt b/app/src/main/kotlin/org/cosmicide/editor/lsp/ComposeSignatureHelpLayout.kt index cac78c65c..0dccaed4e 100644 --- a/app/src/main/kotlin/org/cosmicide/editor/lsp/ComposeSignatureHelpLayout.kt +++ b/app/src/main/kotlin/org/cosmicide/editor/lsp/ComposeSignatureHelpLayout.kt @@ -117,8 +117,11 @@ class ComposeSignatureHelpLayout : SignatureHelpLayout { } } - // SignatureHelpWindow measures before attaching its PopupWindow. - createComposition(ComposeViewContext(window.editor)) + // SignatureHelpWindow measures before attaching its PopupWindow. Only possible once + // the editor is attached; otherwise the strategy creates composition on attach. + if (window.editor.isAttachedToWindow) { + createComposition(ComposeViewContext(window.editor)) + } } return composeView } @@ -171,7 +174,11 @@ class ComposeSignatureHelpLayout : SignatureHelpLayout { * initial empty state. */ private fun composeBeforeHostMeasurement() { - if (!::composeView.isInitialized || composeView.isAttachedToWindow) return + if ( + !::composeView.isInitialized || + composeView.isAttachedToWindow || + !window.editor.isAttachedToWindow + ) return composeView.disposeComposition() composeView.createComposition(ComposeViewContext(window.editor)) } diff --git a/app/src/main/kotlin/org/cosmicide/editor/lsp/HoverLayout.kt b/app/src/main/kotlin/org/cosmicide/editor/lsp/HoverLayout.kt index 9169e753b..c22df9a5e 100644 --- a/app/src/main/kotlin/org/cosmicide/editor/lsp/HoverLayout.kt +++ b/app/src/main/kotlin/org/cosmicide/editor/lsp/HoverLayout.kt @@ -102,8 +102,11 @@ class HoverLayout : HoverLayout { } } - // HoverWindow measures before attaching the PopupWindow. - createComposition(ComposeViewContext(window.editor)) + // HoverWindow measures before attaching the PopupWindow. Only possible once the + // editor is attached; otherwise the strategy creates composition on attach. + if (window.editor.isAttachedToWindow) { + createComposition(ComposeViewContext(window.editor)) + } } } diff --git a/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt b/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt index f8d50817e..80784bc93 100644 --- a/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt +++ b/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt @@ -234,6 +234,10 @@ internal fun EditorToolbar( showGoToLineDialog = true showMenu = false }) + DropdownMenuItem(text = { Text("Statistics") }, onClick = { + showStatsDialog = true + showMenu = false + }) }) } } From e972be9f09bd1682c47b91056d423dc5ebfe971a Mon Sep 17 00:00:00 2001 From: arun28082007 Date: Wed, 12 Aug 2026 09:02:50 +0000 Subject: [PATCH 3/6] added find and replace feature --- app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt b/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt index 80784bc93..1c5a57b74 100644 --- a/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt +++ b/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt @@ -226,6 +226,10 @@ internal fun EditorToolbar( } } DropdownMenuItem(text = { Text("Editor") }, children = { + DropdownMenuItem(text = { Text("Find & Replace") }, onClick = { + editor.beginSearchMode() + showMenu = false + }) DropdownMenuItem(text = { Text("Format") }, onClick = { editor.formatCodeAsync() showMenu = false From e227adef18c2de19952856bce17b5cf5c49a1b6c Mon Sep 17 00:00:00 2001 From: arun28082007 Date: Wed, 12 Aug 2026 09:54:30 +0000 Subject: [PATCH 4/6] fixed replace bug --- .../main/kotlin/org/cosmicide/editor/lsp/LspEditorAdapter.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/kotlin/org/cosmicide/editor/lsp/LspEditorAdapter.kt b/app/src/main/kotlin/org/cosmicide/editor/lsp/LspEditorAdapter.kt index 4362cb05e..123dc129a 100644 --- a/app/src/main/kotlin/org/cosmicide/editor/lsp/LspEditorAdapter.kt +++ b/app/src/main/kotlin/org/cosmicide/editor/lsp/LspEditorAdapter.kt @@ -164,6 +164,9 @@ fun CodeEditor.configureLspLanguage( ) } } catch (e: Exception) { + withContext(Dispatchers.Main) { + editable = true + } lspEditor.dispose() Log.w(TAG, "Failed to connect to ${definition.displayName}", e) LspLogStore.error(definition.displayName, "Failed to connect", e) From 6a1c143e134271cc1c1e063c7066d358071d1477 Mon Sep 17 00:00:00 2001 From: arun28082007 Date: Thu, 13 Aug 2026 13:13:21 +0000 Subject: [PATCH 5/6] added background service --- app/src/main/AndroidManifest.xml | 12 +++ .../main/kotlin/org/cosmicide/MainActivity.kt | 20 +++++ .../org/cosmicide/service/KeepAliveService.kt | 77 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 4 files changed, 110 insertions(+) create mode 100644 app/src/main/kotlin/org/cosmicide/service/KeepAliveService.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ecd8baa8c..6aa7554fd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,9 @@ xmlns:tools="http://schemas.android.com/tools"> + + + @@ -74,5 +77,14 @@ android:name="android.app.shortcuts" android:resource="@xml/shortcuts" /> + + + + diff --git a/app/src/main/kotlin/org/cosmicide/MainActivity.kt b/app/src/main/kotlin/org/cosmicide/MainActivity.kt index 91999ef76..6ed0c6ce4 100644 --- a/app/src/main/kotlin/org/cosmicide/MainActivity.kt +++ b/app/src/main/kotlin/org/cosmicide/MainActivity.kt @@ -7,11 +7,16 @@ package org.cosmicide +import android.Manifest +import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.CompositionLocalProvider @@ -24,6 +29,7 @@ import org.cosmicide.common.Prefs import org.cosmicide.editor.EditorExtensionPoints import org.cosmicide.editor.lsp.LspEditorLanguageProvider import org.cosmicide.plugin.CosmicPluginHost +import org.cosmicide.service.KeepAliveService import org.cosmicide.ui.IDENavigation import org.cosmicide.ui.donation.DonationPromptTracker import org.cosmicide.ui.editor.resolveTheme @@ -41,6 +47,7 @@ class MainActivity : ComponentActivity() { if (savedInstanceState == null) { DonationPromptTracker.recordLaunch(applicationContext) } + startKeepAliveService() val appContainer = AppContainer(applicationContext) setContent { @@ -95,6 +102,19 @@ class MainActivity : ComponentActivity() { LspEditorLanguageProvider.updateColors(colorScheme) } + private fun startKeepAliveService() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && + ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions( + this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 100 + ) + } + runCatching { KeepAliveService.start(this) } + .onFailure { Log.w(TAG, "Could not start keep-alive service", it) } + } + private companion object { const val TAG = "MainActivity" } diff --git a/app/src/main/kotlin/org/cosmicide/service/KeepAliveService.kt b/app/src/main/kotlin/org/cosmicide/service/KeepAliveService.kt new file mode 100644 index 000000000..75175179f --- /dev/null +++ b/app/src/main/kotlin/org/cosmicide/service/KeepAliveService.kt @@ -0,0 +1,77 @@ +/* + * This file is part of Cosmic IDE. + * Cosmic IDE is a free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * Cosmic IDE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with Cosmic IDE. If not, see . + */ + +package org.cosmicide.service + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.pm.ServiceInfo +import android.os.Build +import android.os.IBinder +import org.cosmicide.MainActivity +import org.cosmicide.R + +/** + * Foreground service that keeps the app process alive in the background, like Termux. + */ +class KeepAliveService : Service() { + + override fun onCreate() { + super.onCreate() + startForegroundCompat() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + startForeground(NotificationId, buildNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE) + } else { + startForeground(NotificationId, buildNotification()) + } + return START_STICKY + } + + override fun onBind(intent: Intent?): IBinder? = null + + private fun startForegroundCompat() = onStartCommand(null, 0, 0) + + private fun buildNotification(): Notification { + val channelId = "keepalive" + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + channelId, "Foreground service", NotificationManager.IMPORTANCE_MIN + ) + getSystemService(NotificationManager::class.java).createNotificationChannel(channel) + } + + val contentIntent = PendingIntent.getActivity( + this, 0, + Intent(this, MainActivity::class.java), + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + + return Notification.Builder(this, channelId) + .setContentTitle(getString(R.string.app_name)) + .setContentText(getString(R.string.keep_alive_notification_text)) + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setOngoing(true) + .setContentIntent(contentIntent) + .build() + } + + companion object { + private const val NotificationId = 1 + + fun start(context: Context) { + context.startForegroundService(Intent(context, KeepAliveService::class.java)) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 981ce54a8..cb8b96734 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,6 +25,7 @@ Create java class Terminal + Cosmic IDE is running in the background Copy Quick fixes From e76d72be74d404000d02029fdcbf618b63e022a1 Mon Sep 17 00:00:00 2001 From: Arun Anand <68988353+arun28082007@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:25:23 +0530 Subject: [PATCH 6/6] Remove 'Statistics' option from dropdown menu Removed the 'Statistics' option from the editor toolbar dropdown menu. --- app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt b/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt index 1c5a57b74..ee47e0eb1 100644 --- a/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt +++ b/app/src/main/kotlin/org/cosmicide/ui/editor/EditorToolbar.kt @@ -238,10 +238,6 @@ internal fun EditorToolbar( showGoToLineDialog = true showMenu = false }) - DropdownMenuItem(text = { Text("Statistics") }, onClick = { - showStatsDialog = true - showMenu = false - }) }) } }